diff --git a/.lgtm.yml b/.lgtm.yml index 52e5d90b9f0..b0978fd288d 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -2,6 +2,10 @@ queries: - exclude: py/call/wrong-named-class-argument - exclude: py/call/wrong-number-class-arguments - exclude: py/unsafe-cyclic-import +path_classifiers: + imports_only: + - "**/all.py" + - "**/*catalog*.py" extraction: python: python_setup: diff --git a/VERSION.txt b/VERSION.txt index 0c5f5563dc4..d44f31ea138 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.1.beta0, Release Date: 2020-01-10 +SageMath version 9.1.beta1, Release Date: 2020-01-21 diff --git a/build/make/Makefile.in b/build/make/Makefile.in index eea7ceb7dce..8dbbdb2ece7 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -35,6 +35,11 @@ PYTHON = python@SAGE_PYTHON_VERSION@ MP_LIBRARY = @SAGE_MP_LIBRARY@ BLAS = @SAGE_BLAS@ +# Because some .pc files are generated at configure time we can't write their +# names explicitly the Makefile, so we use the wildcard function here at make +# runtime +PCFILES = $(subst $(SAGE_SRC),$(SAGE_LOCAL),$(wildcard $(SAGE_SRC)/lib/pkgconfig/*.pc)) + # Files to track installation of packages BUILT_PACKAGES = @SAGE_BUILT_PACKAGES@ DUMMY_PACKAGES = @SAGE_DUMMY_PACKAGES@ diff --git a/build/make/deps b/build/make/deps index f9bc4711ad4..d12ae30da24 100644 --- a/build/make/deps +++ b/build/make/deps @@ -64,7 +64,7 @@ download-for-sdist: # TOOLCHAIN consists of dependencies determined by configure. # These are built after the "base" target but before anything else. -toolchain: $(foreach pkgname,$(TOOLCHAIN),$(inst_$(pkgname))) +toolchain: $(foreach pkgname,$(TOOLCHAIN),$(inst_$(pkgname))) $(PCFILES) # Build all packages that GCC links against serially, otherwise this # leads to race conditions where some library which is used by GCC gets @@ -174,7 +174,8 @@ sagelib: \ $(inst_six) \ $(inst_symmetrica) \ $(inst_zn_poly) \ - $(EXTCODE) + $(EXTCODE) \ + $(PCFILES) $(AM_V_at)if [ -z "$$SAGE_INSTALL_FETCH_ONLY" ]; then \ cd $(SAGE_SRC) && source bin/sage-env && \ sage-logger -p 'time $(MAKE) sage' '$(SAGE_LOGS)/sagelib-$(SAGE_VERSION).log'; \ @@ -197,6 +198,11 @@ $(SAGE_EXTCODE)/%: $(SAGE_SRC)/ext/% @mkdir -p "$(@D)" $(AM_V_at)cp $< $@ +# Install sage-specific generated .pc files +$(SAGE_PKGCONFIG)/%.pc: $(SAGE_SRC)/lib/pkgconfig/%.pc + @mkdir -p "$(@D)" + $(AM_V_at)cp -P $< $@ + ############################################################################### # Building the documentation diff --git a/build/make/install b/build/make/install index 46267c4278f..70459c0138f 100755 --- a/build/make/install +++ b/build/make/install @@ -17,6 +17,7 @@ fi export SAGE_SHARE="$SAGE_LOCAL/share" export SAGE_EXTCODE="$SAGE_SHARE/sage/ext" +export SAGE_PKGCONFIG="$SAGE_LOCAL/lib/pkgconfig" export SAGE_LOGS="$SAGE_ROOT/logs/pkgs" export SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" . "$SAGE_SRC"/bin/sage-version.sh diff --git a/build/pkgs/bzip2/spkg-configure.m4 b/build/pkgs/bzip2/spkg-configure.m4 index 5a210179a00..40b8fe79220 100644 --- a/build/pkgs/bzip2/spkg-configure.m4 +++ b/build/pkgs/bzip2/spkg-configure.m4 @@ -1,5 +1,8 @@ SAGE_SPKG_CONFIGURE([bzip2], [ - AC_CHECK_HEADER(bzlib.h, [], [sage_spkg_install_bzip2=yes]) - AC_SEARCH_LIBS([BZ2_bzCompress], [bz2], [], [sage_spkg_install_bzip2=yes]) - AC_CHECK_PROG(bzip2, [break], [sage_spkg_install_bzip2=yes]) + AC_CHECK_HEADER(bzlib.h, [ + AC_SEARCH_LIBS([BZ2_bzCompress], [bz2], [ + AC_PATH_PROG([bzip2_prog], [bzip2]) + AS_IF([test x$bzip2_prog = x], [sage_spkg_install_bzip2=yes]) + ], [sage_spkg_install_bzip2=yes]) + ], [sage_spkg_install_bzip2=yes]) ]) diff --git a/build/pkgs/cbc/spkg-configure.m4 b/build/pkgs/cbc/spkg-configure.m4 new file mode 100644 index 00000000000..999d4b7497b --- /dev/null +++ b/build/pkgs/cbc/spkg-configure.m4 @@ -0,0 +1,6 @@ +SAGE_SPKG_CONFIGURE([cbc], [ + SAGE_SPKG_DEPCHECK([atlas openblas zlib bzip2], [ + dnl checking with pkg-config + PKG_CHECK_MODULES([CBC], [cbc >= 2.9.4], [], [sage_spkg_install_cbc=yes]) + ]) +]) diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 5190342898b..1537fb00361 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=fef64fcd9915ea4f881bc5cfeecb8b3864bc555f -md5=0b23947f22228ef6d5d1143a906cd4a6 -cksum=844195685 +sha1=029fe778cefcb82231353c0dab72a75769ffdcf9 +md5=b4231b3e4ce67fc656ba9532b123485a +cksum=3808756319 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index e1aa201befd..ce2624fb22f 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -7140d0e7bb897e008d315fdcd371c11528aa70d3 +4fd5d56ecea2c81d58e9e43bd5f07b05e863bc7c diff --git a/build/pkgs/cvxopt/spkg-install b/build/pkgs/cvxopt/spkg-install index 47a2d68a103..e5cfab9e1b6 100644 --- a/build/pkgs/cvxopt/spkg-install +++ b/build/pkgs/cvxopt/spkg-install @@ -18,7 +18,7 @@ pkg_libs() { # Note that *_INC_DIR variables have to be non-empty. # Compilers don't like "-I ". export CVXOPT_BLAS_LIB="$(pkg_libs blas)" -export CVXOPT_BLAS_LIB_DIR="${SAGE_LOCAL}" +export CVXOPT_BLAS_LIB_DIR="$(pkg-config --variable=libdir blas)" export CVXOPT_LAPACK_LIB="$(pkg_libs lapack)" export CVXOPT_SUITESPARSE_LIB_DIR="${SAGE_LOCAL}" @@ -29,8 +29,8 @@ export CVXOPT_GLPK_LIB_DIR="${SAGE_LOCAL}" export CVXOPT_GLPK_INC_DIR="${SAGE_LOCAL}/include" export CVXOPT_BUILD_GSL=1 -export CVXOPT_GSL_LIB_DIR="${SAGE_LOCAL}" -export CVXOPT_GSL_INC_DIR="${SAGE_LOCAL}/include" +export CVXOPT_GSL_LIB_DIR="$(pkg-config --variable=libdir gsl)" +export CVXOPT_GSL_INC_DIR="$(pkg-config --variable=includedir gsl)" sdh_pip_install . diff --git a/build/pkgs/fplll/checksums.ini b/build/pkgs/fplll/checksums.ini index abd69023c55..371a327766d 100644 --- a/build/pkgs/fplll/checksums.ini +++ b/build/pkgs/fplll/checksums.ini @@ -1,4 +1,4 @@ tarball=fplll-VERSION.tar.gz -sha1=4144f5fa3d85132585b1b17aed18865c7a146611 -md5=bc3569303ce36d731d21b5419ec733ac -cksum=1239204327 +sha1=67b70f4dcbb835025abce879b9acb4500e2f1d2c +md5=e72082af9084c5b2d427977c9b79a602 +cksum=65319984 diff --git a/build/pkgs/fplll/package-version.txt b/build/pkgs/fplll/package-version.txt index 26d99a283f2..84197c89467 100644 --- a/build/pkgs/fplll/package-version.txt +++ b/build/pkgs/fplll/package-version.txt @@ -1 +1 @@ -5.2.1 +5.3.2 diff --git a/build/pkgs/fpylll/checksums.ini b/build/pkgs/fpylll/checksums.ini index 7a690a0a830..70998dc7e4a 100644 --- a/build/pkgs/fpylll/checksums.ini +++ b/build/pkgs/fpylll/checksums.ini @@ -1,4 +1,4 @@ tarball=fpylll-VERSION.tar.gz -sha1=2f8f83038f7015b2c55d0ec21448ffb27d1d233f -md5=83f57447a1643d0b5087ee3944ca90a8 -cksum=2076741335 +sha1=2d35022ac75457606d7e5d3497f6d9dd75deb19c +md5=1c0d100203cb340cb3160ab9692f9208 +cksum=1547949297 diff --git a/build/pkgs/fpylll/package-version.txt b/build/pkgs/fpylll/package-version.txt index d6f84d0ae4b..3eda70ef84a 100644 --- a/build/pkgs/fpylll/package-version.txt +++ b/build/pkgs/fpylll/package-version.txt @@ -1 +1 @@ -0.4.1dev +0.5.1dev diff --git a/build/pkgs/fpylll/patches/cython3.patch b/build/pkgs/fpylll/patches/cython3.patch deleted file mode 100644 index 844a8999c0c..00000000000 --- a/build/pkgs/fpylll/patches/cython3.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 28e5fcaeabe26d46167e75815e0d9a4fdfa1a973 Mon Sep 17 00:00:00 2001 -From: "Martin R. Albrecht" -Date: Wed, 5 Dec 2018 11:47:33 +0000 -Subject: [PATCH] set language_version - -fixes #127 ---- - setup.py | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/setup.py b/setup.py -index 8f916a9..a5125f8 100755 ---- a/setup.py -+++ b/setup.py -@@ -110,7 +110,9 @@ def run(self): - self.extensions, - include_path=["src"], - build_dir=self.cythonize_dir, -- compiler_directives={'binding': True, "embedsignature": True}) -+ compiler_directives={'binding': True, -+ 'embedsignature': True, -+ 'language_level': 2}) - super(build_ext, self).run() - - def _generate_config_pxi(self): diff --git a/build/pkgs/gcc/spkg-configure.m4 b/build/pkgs/gcc/spkg-configure.m4 index 76647d9cb65..19c55b08aad 100644 --- a/build/pkgs/gcc/spkg-configure.m4 +++ b/build/pkgs/gcc/spkg-configure.m4 @@ -159,9 +159,9 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ cmd_AS=`command -v $AS` if ! (test "$CXX_as" = "" -o "$CXX_as" -ef "$cmd_AS"); then - SAGE_SHOULD_INSTALL_GCC([there is a mismatch of assemblers]) - AC_MSG_NOTICE([ $CXX uses $CXX_as]) - AC_MSG_NOTICE([ \$AS equal to $AS]) + AC_MSG_NOTICE([ $CXX uses $CXX_as]) + AC_MSG_NOTICE([ \$AS equal to $AS]) + AC_MSG_ERROR([unset \$AS or set it to match your compiler's assembler]) fi fi if test -n "$LD"; then @@ -169,9 +169,9 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ CXX_ld=`command -v $CXX_ld 2>/dev/null` cmd_LD=`command -v $LD` if ! (test "$CXX_ld" = "" -o "$CXX_ld" -ef "$cmd_LD"); then - SAGE_SHOULD_INSTALL_GCC([there is a mismatch of linkers]) - AC_MSG_NOTICE([ $CXX uses $CXX_ld]) - AC_MSG_NOTICE([ \$LD equal to $LD]) + AC_MSG_NOTICE([ $CXX uses $CXX_ld]) + AC_MSG_NOTICE([ \$LD equal to $LD]) + AC_MSG_ERROR([unset \$LD or set it to match your compiler's linker]) fi fi diff --git a/build/pkgs/gfan/spkg-configure.m4 b/build/pkgs/gfan/spkg-configure.m4 new file mode 100644 index 00000000000..aec11130f67 --- /dev/null +++ b/build/pkgs/gfan/spkg-configure.m4 @@ -0,0 +1,14 @@ +SAGE_SPKG_CONFIGURE( + [gfan], [ + AC_CACHE_CHECK([for gfan >= 0.6.2], [ac_cv_path_GFAN], [ + AC_PATH_PROGS_FEATURE_CHECK([GFAN_VERSION], [gfan_version], [ + gfan_version=`$ac_path_GFAN_VERSION | $SED -n '/^gfan/s/gfan//p'` + AS_IF([test -n "$gfan_version"], [ + AX_COMPARE_VERSION([$gfan_version], [ge], [0.6.2], [ + ac_cv_path_GFAN_VERSION="$ac_path_GFAN_VERSION" + ]) + ]) + ]) + ]) + AS_IF([test -z "$ac_cv_path_GFAN_VERSION"], [sage_spkg_install_gfan=yes]) +]) diff --git a/build/pkgs/gsl/spkg-configure.m4 b/build/pkgs/gsl/spkg-configure.m4 index 97d4e44330b..486fd67e867 100644 --- a/build/pkgs/gsl/spkg-configure.m4 +++ b/build/pkgs/gsl/spkg-configure.m4 @@ -6,7 +6,7 @@ SAGE_SPKG_CONFIGURE([gsl], [ AC_CONFIG_COMMANDS([GSLPCPROCESS], [ $SED -e 's/\${GSL_CBLAS_LIB}\ //' \ -e 's/GSL_CBLAS_LIB.*/Requires: cblas/' $GSL_PC \ - > "$SAGE_LOCAL"/lib/pkgconfig/gsl.pc + > "$SAGE_SRC"/lib/pkgconfig/gsl.pc ], [ SED=$ac_cv_path_SED GSL_PC="$GSLPCDIR"/gsl.pc diff --git a/build/pkgs/libatomic_ops/spkg-configure.m4 b/build/pkgs/libatomic_ops/spkg-configure.m4 new file mode 100644 index 00000000000..e796277cc1e --- /dev/null +++ b/build/pkgs/libatomic_ops/spkg-configure.m4 @@ -0,0 +1,6 @@ +SAGE_SPKG_CONFIGURE([libatomic_ops], [ + PKG_CHECK_MODULES([LIBATOMIC_OPS], + [atomic_ops >= 7.6.2], + [], + [sage_spkg_install_libatomic_ops=yes]) +]) diff --git a/build/pkgs/nauty/spkg-configure.m4 b/build/pkgs/nauty/spkg-configure.m4 new file mode 100644 index 00000000000..6648afd0876 --- /dev/null +++ b/build/pkgs/nauty/spkg-configure.m4 @@ -0,0 +1,19 @@ +AC_DEFUN([SAGE_TEST_NAUTY_PROGS], [ + m4_foreach([nautyprog], [directg, gentourng, geng, genbg], [ + AC_PATH_PROG([$2]nautyprog, [[$1]nautyprog]) + AS_IF([test x$[$2]nautyprog = x], [sage_spkg_install_nauty=yes]) + ]) + AC_SUBST(SAGE_NAUTY_BINS_PREFIX, ['$1']) +]) +SAGE_SPKG_CONFIGURE([nauty], [ + AC_PATH_PROG([GENGCHECK],[geng]) + AS_IF([test x$GENGCHECK = x], [ + AC_PATH_PROG([GENGnautyCHECK],[nauty-geng]) + AS_IF([test x$GENGnautyCHECK = x], [sage_spkg_install_nauty=yes], + [SAGE_TEST_NAUTY_PROGS(nauty-,nau)]) + ], [SAGE_TEST_NAUTY_PROGS(,foo)]) + ], [], [], [ + AS_IF([test x$sage_spkg_install_nauty = xyes], [ + AC_SUBST(SAGE_NAUTY_BINS_PREFIX, ['']) + ]) +]) diff --git a/build/pkgs/numpy/lapack_conf.py b/build/pkgs/numpy/lapack_conf.py index d127d08def8..9ded057c3c2 100644 --- a/build/pkgs/numpy/lapack_conf.py +++ b/build/pkgs/numpy/lapack_conf.py @@ -8,6 +8,11 @@ conf_file.write('library_dirs = '+ os.environ['SAGE_LOCAL']+ '/lib\n') conf_file.write('include_dirs = '+ os.environ['SAGE_LOCAL']+ '/include\n') +conf_file.write('[DEFAULT]\n') +conf_file.write('library_dirs = '+ os.environ['SAGE_LOCAL']+ '/lib\n') +conf_file.write('include_dirs = '+ os.environ['SAGE_LOCAL']+ '/include\n') + + pc_blas = pkgconfig.parse('cblas blas') pc_lapack = pkgconfig.parse('lapack') diff --git a/build/pkgs/openblas/spkg-configure.m4 b/build/pkgs/openblas/spkg-configure.m4 index d25ace4ea59..dd529fdc32f 100644 --- a/build/pkgs/openblas/spkg-configure.m4 +++ b/build/pkgs/openblas/spkg-configure.m4 @@ -2,9 +2,9 @@ SAGE_SPKG_CONFIGURE([openblas], [ PKG_CHECK_MODULES([OPENBLAS], [openblas >= 0.2.20], [ PKG_CHECK_VAR([OPENBLASPCDIR], [openblas], [pcfiledir], [ AC_CONFIG_LINKS([ - $SAGE_LOCAL/lib/pkgconfig/blas.pc:$OPENBLASPCDIR/openblas.pc - $SAGE_LOCAL/lib/pkgconfig/cblas.pc:$OPENBLASPCDIR/openblas.pc - $SAGE_LOCAL/lib/pkgconfig/lapack.pc:$OPENBLASPCDIR/openblas.pc]) + $SAGE_SRC/lib/pkgconfig/blas.pc:$OPENBLASPCDIR/openblas.pc + $SAGE_SRC/lib/pkgconfig/cblas.pc:$OPENBLASPCDIR/openblas.pc + $SAGE_SRC/lib/pkgconfig/lapack.pc:$OPENBLASPCDIR/openblas.pc]) ], [ AC_MSG_WARN([Unable to locate the directory of openblas.pc. This should not happen!]) sage_spkg_install_openblas=yes diff --git a/build/pkgs/pillow/patches/setup.py.patch b/build/pkgs/pillow/patches/setup.py.patch index 67d8ef46883..e620b00d0a4 100644 --- a/build/pkgs/pillow/patches/setup.py.patch +++ b/build/pkgs/pillow/patches/setup.py.patch @@ -10,13 +10,15 @@ index 15d81e4..5893d3d 100755 import warnings from distutils import ccompiler, sysconfig from distutils.command.build_ext import build_ext -@@ -311,6 +312,9 @@ class pil_build_ext(build_ext): +@@ -311,6 +312,11 @@ class pil_build_ext(build_ext): # darwin ports installation directories _add_directory(library_dirs, "/opt/local/lib") _add_directory(include_dirs, "/opt/local/include") ++ # Check variable introduced in #27631. + sysroot = sysconfig.get_config_var('Py_MACOS_SYSROOT') -+ _add_directory(library_dirs, sysroot+"/usr/lib") -+ _add_directory(include_dirs, sysroot+"/usr/include") ++ if sysroot is not None: ++ _add_directory(library_dirs, sysroot+"/usr/lib") ++ _add_directory(include_dirs, sysroot+"/usr/include") # if Homebrew is installed, use its lib and include directories try: diff --git a/build/pkgs/pillow/spkg-install b/build/pkgs/pillow/spkg-install index c3a809c3912..18bc8c730df 100644 --- a/build/pkgs/pillow/spkg-install +++ b/build/pkgs/pillow/spkg-install @@ -6,10 +6,18 @@ rm -rf \ "$SAGE_LOCAL"/lib/python*/site-packages/PIL-*.egg* \ "$SAGE_LOCAL"/lib/python*/site-packages/Pillow-*.egg* +if [ "$UNAME" = "Darwin" ] ; then + # #29019 + # https://github.com/python-pillow/Pillow/issues/3438#issuecomment-555019284 + # https://apple.stackexchange.com/questions/372032/usr-include-missing-on-macos-catalina-with-xcode-11/372600#372600 + export CPATH="$CPATH:`xcrun --show-sdk-path`/usr/include" +fi + # Note: Avoid shared libraries inside egg files, Trac #19467 sage-python23 setup.py \ --no-user-cfg \ build_ext \ + --debug \ --disable-jpeg \ install \ --single-version-externally-managed \ diff --git a/build/pkgs/r/spkg-install b/build/pkgs/r/spkg-install index 3f85e847deb..e83ca680cda 100644 --- a/build/pkgs/r/spkg-install +++ b/build/pkgs/r/spkg-install @@ -160,12 +160,6 @@ if [ $? -ne 0 ]; then exit 1 fi -# Remove old install -rm -rf "$SAGE_LOCAL"/lib/r -rm -rf "$SAGE_LOCAL"/lib/R -rm -rf "$SAGE_LOCAL"/lib/R.old -rm -rf "$SAGE_LOCAL"/lib/libRblas.* "$SAGE_LOCAL"/lib/libRlapack.* "$SAGE_LOCAL"/lib/libR.* - # Install new version $MAKE install if [ $? -ne 0 ]; then diff --git a/build/pkgs/r/spkg-legacy-uninstall b/build/pkgs/r/spkg-legacy-uninstall new file mode 100644 index 00000000000..234d37746fa --- /dev/null +++ b/build/pkgs/r/spkg-legacy-uninstall @@ -0,0 +1,6 @@ +# Remove old install +rm -rf "$SAGE_LOCAL"/lib/r +rm -rf "$SAGE_LOCAL"/lib/R +rm -rf "$SAGE_LOCAL"/lib/R.old +rm -rf "$SAGE_LOCAL"/lib/libRblas.* "$SAGE_LOCAL"/lib/libRlapack.* "$SAGE_LOCAL"/lib/libR.* +rm -f "$SAGE_LOCAL"/bin/R* diff --git a/build/pkgs/sage_numerical_backends_gurobi/checksums.ini b/build/pkgs/sage_numerical_backends_gurobi/checksums.ini index 5f14b7f8f73..44dbf4cee30 100644 --- a/build/pkgs/sage_numerical_backends_gurobi/checksums.ini +++ b/build/pkgs/sage_numerical_backends_gurobi/checksums.ini @@ -1,4 +1,4 @@ tarball=sage_numerical_backends_gurobi-VERSION.tar.gz -sha1=1a3ea79350cb6bffaff0c785bdb310f4c21475f4 -md5=31e4da1d0538422c370a0149b4da809d -cksum=2128488628 +sha1=64253f735ee6779b6ded809643b455061a105dc4 +md5=3df7d18fe53af89e0a796974a2d788b5 +cksum=3824076545 diff --git a/build/pkgs/sage_numerical_backends_gurobi/package-version.txt b/build/pkgs/sage_numerical_backends_gurobi/package-version.txt index 1700f7be00d..f7ee06693c1 100644 --- a/build/pkgs/sage_numerical_backends_gurobi/package-version.txt +++ b/build/pkgs/sage_numerical_backends_gurobi/package-version.txt @@ -1 +1 @@ -9.0b12 +9.0.0 diff --git a/build/sage_bootstrap/app.py b/build/sage_bootstrap/app.py index 495c7b7e61d..20f5043d201 100644 --- a/build/sage_bootstrap/app.py +++ b/build/sage_bootstrap/app.py @@ -4,29 +4,26 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2016 Volker Braun # # 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/ +# **************************************************************************** import os -import sys import logging log = logging.getLogger() -from sage_bootstrap.env import SAGE_DISTFILES from sage_bootstrap.package import Package from sage_bootstrap.tarball import Tarball from sage_bootstrap.updater import ChecksumUpdater, PackageUpdater from sage_bootstrap.creator import PackageCreator - class Application(object): def config(self): diff --git a/build/sage_bootstrap/cksum.py b/build/sage_bootstrap/cksum.py index eaba1e87619..e44e8241bac 100644 --- a/build/sage_bootstrap/cksum.py +++ b/build/sage_bootstrap/cksum.py @@ -4,8 +4,6 @@ This is a weak checksum, only included for legacy reasons. """ -import sys - # Fun table, e.g. http://www.nco.ncep.noaa.gov/pmb/codes/nwprod/util/sorc/wgrib2.cd/grib2/wgrib2/crc32.c crctab = [ diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index 9515c61c54c..07925fdec11 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -6,18 +6,16 @@ is also exposed as "sage --package". """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2016 Volker Braun # # 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/ +# **************************************************************************** -import os import sys import logging log = logging.getLogger() diff --git a/build/sage_bootstrap/compat/__init__.py b/build/sage_bootstrap/compat/__init__.py index e5c485a1bd7..7fb303e4edc 100644 --- a/build/sage_bootstrap/compat/__init__.py +++ b/build/sage_bootstrap/compat/__init__.py @@ -11,7 +11,7 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #***************************************************************************** diff --git a/build/sage_bootstrap/compat/argparse.py b/build/sage_bootstrap/compat/argparse.py index 2e537741d09..e3ddc96dc15 100644 --- a/build/sage_bootstrap/compat/argparse.py +++ b/build/sage_bootstrap/compat/argparse.py @@ -96,28 +96,12 @@ from gettext import gettext as _ -try: - set -except NameError: - # for python < 2.4 compatibility (sets module is there since 2.3): - from sets import Set as set try: basestring except NameError: basestring = str -try: - sorted -except NameError: - # for python < 2.4 compatibility: - def sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - def _callable(obj): return hasattr(obj, '__call__') or hasattr(obj, '__bases__') diff --git a/build/sage_bootstrap/config.py b/build/sage_bootstrap/config.py index 666411e28ff..b96948d5d33 100644 --- a/build/sage_bootstrap/config.py +++ b/build/sage_bootstrap/config.py @@ -18,20 +18,19 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # 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/ +# **************************************************************************** import sys import os -import logging LOG_LEVELS = ( @@ -43,8 +42,6 @@ ) - - class Configuration(object): _initialized = False diff --git a/build/sage_bootstrap/logger.py b/build/sage_bootstrap/logger.py index 894aecefd8c..88c19fc4800 100644 --- a/build/sage_bootstrap/logger.py +++ b/build/sage_bootstrap/logger.py @@ -8,18 +8,17 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # 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/ +# **************************************************************************** import sys -import os import logging logger = logging.getLogger() diff --git a/build/sage_bootstrap/package.py b/build/sage_bootstrap/package.py index aff921299cc..08ed0b4d550 100644 --- a/build/sage_bootstrap/package.py +++ b/build/sage_bootstrap/package.py @@ -3,7 +3,7 @@ Sage Packages """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # This program is free software: you can redistribute it and/or modify @@ -11,13 +11,13 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** import re import os import logging -from sage_bootstrap.env import SAGE_ROOT, SAGE_DISTFILES +from sage_bootstrap.env import SAGE_ROOT log = logging.getLogger() diff --git a/build/sage_bootstrap/stdio.py b/build/sage_bootstrap/stdio.py index e3984595aba..d4df3f05b84 100644 --- a/build/sage_bootstrap/stdio.py +++ b/build/sage_bootstrap/stdio.py @@ -6,19 +6,16 @@ the terminal. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # 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/ +# **************************************************************************** import sys -import os class UnbufferedStream(object): diff --git a/build/sage_bootstrap/updater.py b/build/sage_bootstrap/updater.py index a24fffa9261..496b579cd37 100644 --- a/build/sage_bootstrap/updater.py +++ b/build/sage_bootstrap/updater.py @@ -3,28 +3,25 @@ Package Updater """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # 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/ +# **************************************************************************** -import re import os import logging log = logging.getLogger() -from sage_bootstrap.env import SAGE_ROOT from sage_bootstrap.package import Package from sage_bootstrap.download import Download - class ChecksumUpdater(object): def __init__(self, package_name): diff --git a/m4/sage_spkg_collect.m4 b/m4/sage_spkg_collect.m4 index 304d1988b15..e6a507b349f 100644 --- a/m4/sage_spkg_collect.m4 +++ b/m4/sage_spkg_collect.m4 @@ -52,6 +52,22 @@ # type which are installed by running a custom script, which may # download additional source files. # + +dnl ========================================================================== +dnl define PKG_CHECK_VAR for old pkg-config < 0.28; see Trac #29001 +m4_ifndef([PKG_CHECK_VAR], [ +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])dnl PKG_CHECK_VAR +]) +dnl ========================================================================== + AC_DEFUN_ONCE([SAGE_SPKG_COLLECT], [ # Configure all spkgs with configure-time checks m4_include([m4/sage_spkg_configures.m4]) diff --git a/src/bin/sage-env b/src/bin/sage-env index 49f29942df0..9801342a32f 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -284,6 +284,7 @@ fi export SAGE_ETC="$SAGE_LOCAL/etc" export SAGE_SHARE="$SAGE_LOCAL/share" export SAGE_EXTCODE="$SAGE_SHARE/sage/ext" +export SAGE_PKGCONFIG="$SAGE_LOCAL/lib/pkgconfig" export SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" export SAGE_SPKG_SCRIPTS="$SAGE_LOCAL/var/lib/sage/scripts" export SAGE_LOGS="$SAGE_ROOT/logs/pkgs" @@ -500,7 +501,7 @@ fi # Set AS to assembler used by $CC ("as" by default) if [ "$AS" = "" ]; then - CC_as=`$CC -print-file-name=as 2>/dev/null` + CC_as=`$CC -print-prog-name=as 2>/dev/null` if command -v $CC_as >/dev/null 2>/dev/null; then AS="$CC_as" fi @@ -512,7 +513,7 @@ export AS # Set LD to linker used by $CC ("ld" by default) if [ "$LD" = "" ]; then - CC_ld=`$CC -print-file-name=ld 2>/dev/null` + CC_ld=`$CC -print-prog-name=ld 2>/dev/null` if command -v $CC_ld >/dev/null 2>/dev/null; then LD="$CC_ld" fi diff --git a/src/bin/sage-env-config.in b/src/bin/sage-env-config.in index 487f2c97b0c..4e177c53395 100644 --- a/src/bin/sage-env-config.in +++ b/src/bin/sage-env-config.in @@ -109,3 +109,5 @@ if [ -n "$SAGE_PKG_CONFIG_PATH" ]; then # (Sage's pkgconf spkg takes care of this, if installed) export PKG_CONFIG_PATH="$SAGE_PKG_CONFIG_PATH${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" fi + +export SAGE_NAUTY_BINS_PREFIX="@SAGE_NAUTY_BINS_PREFIX@" diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index f23c185f22a..4f8eb29ebcc 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -46,8 +46,8 @@ if __name__ == "__main__": parser.add_option("-m", "--memlimit", type=int, default=3300, help='maximum virtual memory to allow each test ' 'process, in megabytes; no limit if zero or less, ' - 'but tests tagged "high mem" are skipped if no limit ' - 'is set (default: 3300 MB)') + 'but tests tagged "optional - memlimit" are ' + 'skipped if no limit is set (default: 3300 MB)') parser.add_option("-a", "--all", action="store_true", default=False, help="test all files in the Sage library") parser.add_option("--logfile", metavar="FILE", help="log all output to FILE") parser.add_option("--sagenb", action="store_true", default=False, help="test all files from the Sage notebook sources") diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 57f8fb95cc0..dc09574db4e 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.1.beta0' -SAGE_RELEASE_DATE='2020-01-10' -SAGE_VERSION_BANNER='SageMath version 9.1.beta0, Release Date: 2020-01-10' +SAGE_VERSION='9.1.beta1' +SAGE_RELEASE_DATE='2020-01-21' +SAGE_VERSION_BANNER='SageMath version 9.1.beta1, Release Date: 2020-01-21' diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index e380c28f8f4..a3ba6ea46e7 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -87,6 +87,7 @@ Libraries of algorithms sage/graphs/graph_decompositions/cutwidth sage/graphs/graph_decompositions/graph_products sage/graphs/graph_decompositions/modular_decomposition + sage/graphs/graph_decompositions/clique_separators sage/graphs/convexity_properties sage/graphs/weakly_chordal sage/graphs/distances_all_pairs diff --git a/src/doc/en/reference/number_fields/index.rst b/src/doc/en/reference/number_fields/index.rst index 50d71420349..0fecfe43518 100644 --- a/src/doc/en/reference/number_fields/index.rst +++ b/src/doc/en/reference/number_fields/index.rst @@ -24,6 +24,7 @@ Morphisms :maxdepth: 1 sage/rings/number_field/morphism + sage/rings/number_field/homset sage/rings/number_field/number_field_morphisms sage/rings/number_field/maps sage/rings/number_field/structure diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 285026ec531..c7010319690 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -416,6 +416,11 @@ REFERENCES: Thue-Morse sequence. Pure Math. Appl., 19(2-3):39--52, 2008. +.. [BBHP2004] Anne Berry, Jean R. S. Blair, Pinar Heggernes, + Barry W. Peyton. *Maximum Cardinality Search for Computing Minimal + Triangulations of Graphs*. Algorithmica 39(4):287-298, 2004. + :doi:`10.1007/s00453-004-1084-3` + .. [BBISHAR2015] \S. Banik, A. Bogdanov, T. Isobe, K. Shibutani, H. Hiwatari, \T. Akishita, and F. Regazzoni, *Midori: A block cipher for low energy*; in ASIACRYPT, (2015), pp. 411-436. @@ -974,6 +979,11 @@ REFERENCES: *Sequences with constant number of return words*. Monatsh. Math, 155 (2008) 251-263. +.. [BPS2010] Anne Berry, Romain Pogorelcnik and Genevieve Simonet. + *An Introduction to Clique Minimal Separator Decomposition*. + Algorithms 3(2):197-215, 2010. + :doi:`10.3390/a3020197` + .. [BPU2016] Alex Biryukov, Léo Perrin, Aleksei Udovenko, *Reverse-Engineering the S-Box of Streebog, Kuznyechik and STRIBOBr1*; in EuroCrypt'16, pp. 372-402. @@ -2515,6 +2525,10 @@ REFERENCES: .. [HC2006] Mark van Hoeij and John Cremona, Solving Conics over function fields. J. Théor. Nombres Bordeaux, 2006. +.. [He2006] Pinar Heggernes. *Minimal triangulations of graphs: A survey*. + Discrete Mathematics, 306(3):297-317, 2006. + :doi:`10.1016/j.disc.2005.12.003` + .. [He2002] \H. Heys *A Tutorial on Linear and Differential Cryptanalysis* ; 2002' available at http://www.engr.mun.ca/~howard/PAPERS/ldc_tutorial.pdf @@ -3549,6 +3563,11 @@ REFERENCES: IMA Volumes in Math and its Applications (D. Stanton, ED.). Southend on Sea, UK, 19 (1990). 125-144. +.. [LS1994] Eike Lau and Dierk Schleicher. + *Internal addresses in the Mandelbrot set and irreducibility of + polynomials*. Stony Brook Preprint #19 (1994). + https://www.math.stonybrook.edu/theses/thesis94-2/part1.pdf + .. [LS2007] Thomas Lam and Mark Shimozono. *Dual graded graphs for Kac-Moody algebras*. Algebra & Number Theory 1.4 (2007) pp. 451-488. @@ -5080,6 +5099,12 @@ REFERENCES: .. [TW1980] \A.D. Thomas and G.V. Wood, Group Tables (Exeter: Shiva Publishing, 1980) +.. [TY1984] Robert Endre Tarjan, Mihalis Yannakakis. *Simple linear-time + algorithms to test chordality of graphs, test acyclicity of + hypergraphs, and selectively reduce acyclic hypergraphs*. + SIAM Journal on Computing, 13:566-579, 1984. + :doi:`10.1137/0213035` + .. [TY2009] Hugh Thomas and Alexander Yong, *A jeu de taquin theory for increasing tableaux, with applications to K-theoretic Schubert calculus*, Algebra and Number Theory 3 (2009), 121-148, diff --git a/src/lib/pkgconfig/.gitignore b/src/lib/pkgconfig/.gitignore new file mode 100644 index 00000000000..1b806dad94b --- /dev/null +++ b/src/lib/pkgconfig/.gitignore @@ -0,0 +1,5 @@ +# .pc files created at configure time +blas.pc +cblas.pc +gsl.pc +lapack.pc diff --git a/src/mac-app/tools/createDSStore/mac_alias/alias.py b/src/mac-app/tools/createDSStore/mac_alias/alias.py index 512d2221bcb..b984598f1f9 100644 --- a/src/mac-app/tools/createDSStore/mac_alias/alias.py +++ b/src/mac-app/tools/createDSStore/mac_alias/alias.py @@ -5,10 +5,8 @@ import struct import datetime import io -import re import os import os.path -import stat import sys if sys.platform == 'darwin': @@ -19,7 +17,7 @@ except NameError: long = int -from .utils import * +from .utils import mac_epoch ALIAS_KIND_FILE = 0 ALIAS_KIND_FOLDER = 1 diff --git a/src/mac-app/tools/createDSStore/mac_alias/bookmark.py b/src/mac-app/tools/createDSStore/mac_alias/bookmark.py index 0de6b9404b6..37b8f946edc 100644 --- a/src/mac-app/tools/createDSStore/mac_alias/bookmark.py +++ b/src/mac-app/tools/createDSStore/mac_alias/bookmark.py @@ -11,7 +11,6 @@ import datetime import os import sys -import pprint try: from urlparse import urljoin @@ -223,12 +222,12 @@ def absolute(self): if self.base is None: return self.relative else: - base_abs = self.base.absolute return urljoin(self.base.absolute, self.relative) def __repr__(self): return 'URL(%r)' % self.absolute + class Bookmark (object): def __init__(self, tocs=None): if tocs is None: diff --git a/src/mac-app/tools/createDSStore/mac_alias/osx.py b/src/mac-app/tools/createDSStore/mac_alias/osx.py index d4af65d756c..29c2e7a916e 100644 --- a/src/mac-app/tools/createDSStore/mac_alias/osx.py +++ b/src/mac-app/tools/createDSStore/mac_alias/osx.py @@ -2,12 +2,11 @@ from __future__ import unicode_literals from ctypes import * -import struct import os import datetime import uuid -from .utils import * +from .utils import unix_epoch libc = cdll.LoadLibrary('/usr/lib/libc.dylib') diff --git a/src/module_list.py b/src/module_list.py index 163aa11d3a9..c1adcf006cc 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -426,6 +426,9 @@ def uname_specific(name, value, alternative): language="c++", package = 'tdlib'), + Extension('sage.graphs.graph_decompositions.clique_separators', + sources = ['sage/graphs/graph_decompositions/clique_separators.pyx']), + Extension('sage.graphs.spanning_tree', sources = ['sage/graphs/spanning_tree.pyx']), @@ -931,6 +934,9 @@ def uname_specific(name, value, alternative): Extension('sage.modular.arithgroup.arithgroup_element', sources = ['sage/modular/arithgroup/arithgroup_element.pyx']), + Extension('sage.modular.hypergeometric_misc', + sources = ['sage/modular/hypergeometric_misc.pyx']), + Extension('sage.modular.modform.eis_series_cython', sources = ['sage/modular/modform/eis_series_cython.pyx']), diff --git a/src/sage/algebras/hall_algebra.py b/src/sage/algebras/hall_algebra.py index 707a90abc12..cbfc5494f1b 100644 --- a/src/sage/algebras/hall_algebra.py +++ b/src/sage/algebras/hall_algebra.py @@ -97,7 +97,7 @@ class HallAlgebra(CombinatorialFreeModule): I_\mu \cdot I_\lambda = \sum_\nu P^{\nu}_{\mu, \lambda}(q) I_\nu, where `P^{\nu}_{\mu, \lambda}` is a Hall polynomial (see - :meth:`~sage.combinat.hall_polynomial.hall_polynomial`). The + :func:`~sage.combinat.hall_polynomial.hall_polynomial`). The unity of this algebra is `I_{\emptyset}`. The (classical) Hall algebra is also known as the Hall-Steinitz diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index 10a9e647abb..30e5176fdde 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -33,7 +33,7 @@ from sage.categories.lie_algebras import LieAlgebras from sage.categories.triangular_kac_moody_algebras import TriangularKacMoodyAlgebras -from sage.algebras.lie_algebras.lie_algebra import LieAlgebraFromAssociative, FinitelyGeneratedLieAlgebra +from sage.algebras.lie_algebras.lie_algebra import MatrixLieAlgebraFromAssociative, FinitelyGeneratedLieAlgebra from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients from sage.combinat.root_system.cartan_type import CartanType from sage.combinat.root_system.cartan_matrix import CartanMatrix @@ -43,10 +43,13 @@ from sage.modules.free_module import FreeModule -class ClassicalMatrixLieAlgebra(LieAlgebraFromAssociative): +class ClassicalMatrixLieAlgebra(MatrixLieAlgebraFromAssociative): """ A classical Lie algebra represented using matrices. + This means a classical Lie algebra given as a Lie + algebra of matrices, with commutator as Lie bracket. + INPUT: - ``R`` -- the base ring @@ -122,6 +125,13 @@ def __init__(self, R, ct, e, f, h): sage: sl2 = lie_algebras.sl(QQ, 2, 'matrix') sage: isinstance(sl2.indices(), FiniteEnumeratedSet) True + + Check that elements are hashable (see :trac:`28961`):: + + sage: sl2 = lie_algebras.sl(QQ, 2, 'matrix') + sage: e,f,h = list(sl2.basis()) + sage: len(set([e, e+f])) + 2 """ n = len(e) names = ['e%s'%i for i in range(1, n+1)] @@ -130,11 +140,11 @@ def __init__(self, R, ct, e, f, h): category = LieAlgebras(R).FiniteDimensional().WithBasis() from sage.sets.finite_enumerated_set import FiniteEnumeratedSet index_set = FiniteEnumeratedSet(names) - LieAlgebraFromAssociative.__init__(self, e[0].parent(), - gens=tuple(e + f + h), - names=tuple(names), - index_set=index_set, - category=category) + MatrixLieAlgebraFromAssociative.__init__(self, e[0].parent(), + gens=tuple(e + f + h), + names=tuple(names), + index_set=index_set, + category=category) self._cartan_type = ct gens = tuple(self.gens()) @@ -159,7 +169,7 @@ def e(self, i): def f(self, i): r""" - Return the generator `f_i`.- + Return the generator `f_i`. EXAMPLES:: @@ -331,43 +341,8 @@ def affine(self, kac_moody=False): from sage.algebras.lie_algebras.affine_lie_algebra import AffineLieAlgebra return AffineLieAlgebra(self, kac_moody) - class Element(LieAlgebraFromAssociative.Element): - def matrix(self): - r""" - Return ``self`` as element of the underlying matrix algebra. - - OUTPUT: - - An instance of the element class of MatrixSpace. - - EXAMPLES:: - - sage: sl3m = lie_algebras.sl(ZZ, 3, representation='matrix') - sage: e1,e2, f1, f2, h1, h2 = sl3m.gens() - sage: h1m = h1.matrix(); h1m - [ 1 0 0] - [ 0 -1 0] - [ 0 0 0] - sage: h1m.parent() - Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring - sage: matrix(h2) - [ 0 0 0] - [ 0 1 0] - [ 0 0 -1] - sage: L = lie_algebras.so(QQ['z'], 5, representation='matrix') - sage: matrix(L.an_element()) - [ 1 1 0 0 0] - [ 1 1 0 0 2] - [ 0 0 -1 -1 0] - [ 0 0 -1 -1 -1] - [ 0 1 0 -2 0] - """ - return self.value - - _matrix_ = matrix - -class gl(LieAlgebraFromAssociative): +class gl(MatrixLieAlgebraFromAssociative): r""" The matrix Lie algebra `\mathfrak{gl}_n`. @@ -413,10 +388,10 @@ def __init__(self, R, n): category = LieAlgebras(R).FiniteDimensional().WithBasis() from sage.sets.finite_enumerated_set import FiniteEnumeratedSet index_set = FiniteEnumeratedSet(names) - LieAlgebraFromAssociative.__init__(self, MS, tuple(gens), - names=tuple(names), - index_set=index_set, - category=category) + MatrixLieAlgebraFromAssociative.__init__(self, MS, tuple(gens), + names=tuple(names), + index_set=index_set, + category=category) def _repr_(self): """ @@ -494,7 +469,7 @@ def monomial(self, i): return self.basis()['E_{}_{}'.format(*i)] return self.basis()[i] - class Element(ClassicalMatrixLieAlgebra.Element): + class Element(MatrixLieAlgebraFromAssociative.Element): def monomial_coefficients(self, copy=True): r""" Return the monomial coefficients of ``self``. diff --git a/src/sage/algebras/lie_algebras/lie_algebra.py b/src/sage/algebras/lie_algebras/lie_algebra.py index 641a9dd6118..a75ab2c3dec 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra.py +++ b/src/sage/algebras/lie_algebras/lie_algebra.py @@ -1500,7 +1500,47 @@ def section(self): class MatrixLieAlgebraFromAssociative(LieAlgebraFromAssociative): """ A Lie algebra constructed from a matrix algebra. + + This means a Lie algebra consisting of matrices, + with commutator as Lie bracket. """ class Element(LieAlgebraMatrixWrapper, LieAlgebraFromAssociative.Element): - pass + def matrix(self): + r""" + Return ``self`` as element of the underlying matrix algebra. + + OUTPUT: + + An instance of the element class of MatrixSpace. + + EXAMPLES:: + + sage: sl3m = lie_algebras.sl(ZZ, 3, representation='matrix') + sage: e1,e2, f1, f2, h1, h2 = sl3m.gens() + sage: h1m = h1.matrix(); h1m + [ 1 0 0] + [ 0 -1 0] + [ 0 0 0] + sage: h1m.parent() + Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring + sage: matrix(h2) + [ 0 0 0] + [ 0 1 0] + [ 0 0 -1] + sage: L = lie_algebras.so(QQ['z'], 5, representation='matrix') + sage: matrix(L.an_element()) + [ 1 1 0 0 0] + [ 1 1 0 0 2] + [ 0 0 -1 -1 0] + [ 0 0 -1 -1 -1] + [ 0 1 0 -2 0] + + sage: gl2 = lie_algebras.gl(QQ, 2) + sage: matrix(gl2.an_element()) + [1 1] + [1 1] + """ + return self.value + + _matrix_ = matrix diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 43a81bd4a8d..6bd78d2524a 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -1243,13 +1243,13 @@ def desolve_rk4_determine_bounds(ics,end_points=None): """ if end_points is None: - return((ics[0],ics[0]+10)) - if not isinstance(end_points,list): - end_points=[end_points] - if len(end_points)==1: - return (min(ics[0],end_points[0]),max(ics[0],end_points[0])) + return ics[0], ics[0] + 10 + if not isinstance(end_points, list): + end_points = [end_points] + if len(end_points) == 1: + return min(ics[0], end_points[0]), max(ics[0], end_points[0]) else: - return (min(ics[0],end_points[0]),max(ics[0],end_points[1])) + return min(ics[0], end_points[0]), max(ics[0], end_points[1]) def desolve_rk4(de, dvar, ics=None, ivar=None, end_points=None, step=0.1, output='list', **kwds): diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 5af5785d7c7..2a1f5a0f7a4 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -77,10 +77,9 @@ def _test_divides(self, **options): return # 3. divisibility of some elements - S = tester.some_elements() - for a,b in tester.some_elements(repeat=2): + for a, b in tester.some_elements(repeat=2): try: - test = a.divides(a*b) + test = a.divides(a * b) except NotImplementedError: pass else: diff --git a/src/sage/categories/examples/cw_complexes.py b/src/sage/categories/examples/cw_complexes.py index ba59be17bd2..8422fc0a418 100644 --- a/src/sage/categories/examples/cw_complexes.py +++ b/src/sage/categories/examples/cw_complexes.py @@ -1,21 +1,19 @@ """ Examples of CW complexes """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Travis Scrimshaw # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** - +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element from sage.categories.cw_complexes import CWComplexes -from sage.rings.integer import Integer -from sage.rings.all import QQ from sage.sets.family import Family + class Surface(UniqueRepresentation, Parent): r""" An example of a CW complex: a (2-dimensional) surface. @@ -161,4 +159,3 @@ def dimension(self): return self._dim Example = Surface - diff --git a/src/sage/categories/examples/graphs.py b/src/sage/categories/examples/graphs.py index 9c1e1eddb7e..21c2d2a150a 100644 --- a/src/sage/categories/examples/graphs.py +++ b/src/sage/categories/examples/graphs.py @@ -1,18 +1,17 @@ """ Examples of graphs """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Travis Scrimshaw # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** - +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper from sage.categories.graphs import Graphs -from sage.rings.all import QQ + class Cycle(UniqueRepresentation, Parent): r""" @@ -119,4 +118,3 @@ def dimension(self): return 1 Example = Cycle - diff --git a/src/sage/categories/examples/manifolds.py b/src/sage/categories/examples/manifolds.py index a71b7cf30e3..e1d7c851202 100644 --- a/src/sage/categories/examples/manifolds.py +++ b/src/sage/categories/examples/manifolds.py @@ -1,18 +1,17 @@ """ Examples of manifolds """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Travis Scrimshaw # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** - +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper from sage.categories.manifolds import Manifolds -from sage.rings.all import QQ + class Plane(UniqueRepresentation, Parent): r""" @@ -91,4 +90,3 @@ def an_element(self): Element = ElementWrapper Example = Plane - diff --git a/src/sage/categories/fields.py b/src/sage/categories/fields.py index af9f1b29122..686c5eb369a 100644 --- a/src/sage/categories/fields.py +++ b/src/sage/categories/fields.py @@ -141,8 +141,8 @@ def _contains_helper(cls): sage: P. = QQ[] sage: Q = P.quotient(x^2+2) sage: Q.category() - Category of commutative no zero divisors quotients - of algebras over Rational Field + Category of commutative no zero divisors quotients of algebras + over (number fields and quotient fields and metric spaces) sage: F = Fields() sage: F._contains_helper(Q) False diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 25071da4417..8073599c0d3 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -758,7 +758,7 @@ def one(self): sage: PvW0.one() 1 """ - return(self(self.realization_of().a_realization().one())) + return self(self.realization_of().a_realization().one()) class ParentMethods: diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index 14790bf4b54..7b43e95ad7d 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -1350,7 +1350,7 @@ def coefficient(self, m): Test that ``coefficient`` also works for those parents that do not have an ``element_class``:: - sage: H = End(ZZ) + sage: H = pAdicWeightSpace(3) sage: F = CombinatorialFreeModule(QQ, H) sage: hasattr(H, "element_class") False diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 0fe756e5259..27e436c6360 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -1390,10 +1390,11 @@ def __mul__(self, other): othervars = other.vars else: othervars = [other.var] - OverlappingGens = [] ## Generator names of variable names of the MultiPolynomialFunctor - ## that can be interpreted as variables in self - OverlappingVars = [] ## The variable names of the MultiPolynomialFunctor - ## that can be interpreted as variables in self + + OverlappingVars = [] + # The variable names of the MultiPolynomialFunctor + # that can be interpreted as variables in self + RemainingVars = [x for x in othervars] IsOverlap = False BadOverlap = False @@ -1741,7 +1742,6 @@ def _apply_functor(self, R): if self.multi_variate and is_LaurentPolynomialRing(R): return LaurentPolynomialRing(R.base_ring(), (list(R.variable_names()) + [self.var])) else: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing return LaurentPolynomialRing(R, self.var) def __eq__(self, other): @@ -2746,7 +2746,6 @@ def _apply_functor(self, R): Quotient of Rational Field by the ideal (1) """ I = self.I - from sage.all import QQ if not I.is_zero(): from sage.categories.fields import Fields if R in Fields(): diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index e8036869add..a8f648277f5 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -348,7 +348,7 @@ def _Hom_(self, Y, category): sage: Hom(CyclotomicField(3), QQ, category = Rings()).__class__ - + sage: TestSuite(Hom(QQ, QQ, category = Rings())).run() # indirect doctest diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index bc2bd4bf03b..ec757ade1e7 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -1,13 +1,12 @@ r""" Unital algebras """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Nicolas M. Thiery # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** - +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute @@ -18,9 +17,9 @@ from sage.categories.morphism import SetMorphism from sage.categories.homset import Hom from sage.categories.rings import Rings -from sage.categories.magmas import Magmas from sage.categories.magmatic_algebras import MagmaticAlgebras + class UnitalAlgebras(CategoryWithAxiom_over_base_ring): """ The category of non-associative algebras over a given base ring. diff --git a/src/sage/categories/weyl_groups.py b/src/sage/categories/weyl_groups.py index ec858f557d8..6ea5cf878ea 100644 --- a/src/sage/categories/weyl_groups.py +++ b/src/sage/categories/weyl_groups.py @@ -147,6 +147,7 @@ def pieri_factors(self, *args, **keywords): # Do not remove this line which makes sure the pieri factor # code is properly inserted inside the Cartan Types import sage.combinat.root_system.pieri_factors + assert sage.combinat.root_system.pieri_factors ct = self.cartan_type() if hasattr(ct, "PieriFactors"): return ct.PieriFactors(self, *args, **keywords) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 47e3d7d80b0..e1064980632 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -3711,9 +3711,9 @@ class LinearCodeSyndromeDecoder(Decoder): is bigger than both the covering radius and half the minimum distance:: sage: D = C.decoder("Syndrome", maximum_error_weight = 5) # long time - sage: D.decoder_type() + sage: D.decoder_type() # long time {'complete', 'hard-decision', 'might-error'} - sage: D.decoding_radius() + sage: D.decoding_radius() # long time 4 In that case, the decoder might still return an unexpected codeword, but diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 571bad916a9..9ff0b58199e 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -752,7 +752,7 @@ def ASM_compatible_bigger(self): for k in range(len(output)): output[k] = M.from_height_function(output[k]/2) - return(output) + return output def ASM_compatible_smaller(self): r""" @@ -809,7 +809,7 @@ def ASM_compatible_smaller(self): output.append(d) for k in range(len(output)): output[k] = M.from_height_function((output[k]-matrix.ones(n,n))/2) - return(output) + return output @combinatorial_map(name='to Dyck word') def to_dyck_word(self, algorithm): diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index 79f582b0f6f..fcb53e3ac3e 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -4122,7 +4122,7 @@ def greedy(self, a1, a2, algorithm='by_recursion'): for q in range(max(a1, 0)+1): if p != 0 or q != 0: ans += self._R(coeff_recurs(p, q, a1, a2, b, c))*self.x(0)**(b*p-a1)*self.x(1)**(c*q-a2) - return(ans) + return ans elif algorithm == 'by_combinatorics': if b == 0: S = ClusterSeed([['A', 1], ['A', 1]]) @@ -4153,7 +4153,7 @@ def greedy(self, a1, a2, algorithm='by_recursion'): for q in range(max(a1, 0)+1): if p != 0 or q != 0: ans += coeff_recurs(p, q, a1, a2, b, c) - return(ans) + return ans else: raise ValueError("The third input should be 'by_recursion', " "'by_combinatorics', or 'just_numbers'.") diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 0f932830332..796571fec00 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -6907,7 +6907,7 @@ def add_from_transition_function(self, function, initial_states=None, ....: final_states=[1], ....: input_alphabet=[0]) sage: def transition_function(state, letter): - ....: return(1-state, []) + ....: return 1 - state, [] sage: F.add_from_transition_function(transition_function) sage: F.transitions() [Transition from 0 to 1: 0|-, @@ -6921,7 +6921,7 @@ def add_from_transition_function(self, function, initial_states=None, ....: final_states=[1], ....: input_alphabet=[0]) sage: def transition_function(state, letter): - ....: return(1-state, []) + ....: return 1 - state, [] sage: F.add_from_transition_function(transition_function, ....: explore_existing_states=False) sage: F.transitions() @@ -10031,8 +10031,7 @@ def predecessors(self, state, valid_input=None): open.extend(candidates) unhandeled_direct_predecessors[s] = None done.append(s) - return(done) - + return done def number_of_words(self, variable=var('n'), base_ring=None): diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index 5518953f9c9..60571e9044e 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -483,10 +483,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): if not self.controller.options.long: if self.controller.options.show_skipped: log(" %s not run"%(count_noun(nskipped, "long test"))) - elif tag == "high_mem": + elif tag == "memlimit": if self.controller.options.memlimit <= 0: seen_other = True - log(" %s not run"%(count_noun(nskipped, "high mem"))) + log(" %s not run"%(count_noun(nskipped, "memlimit"))) elif tag == "not tested": if self.controller.options.show_skipped: log(" %s not run"%(count_noun(nskipped, "not tested test"))) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index 12801639a73..ffb61df6b03 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -108,6 +108,7 @@ class initialization directly. from sage.combinat.subset import Subsets from sage.symbolic.ring import SR + class DynamicalSystem_projective(SchemeMorphism_polynomial_projective_space, DynamicalSystem): r"""A dynamical system of projective schemes determined by homogeneous @@ -486,7 +487,7 @@ def _number_field_from_algebraics(self): r""" Return a dynamical system defined over the number field of its coefficients. - OTUPUT: dynamical system. + OUTPUT: dynamical system. EXAMPLES:: @@ -1321,7 +1322,7 @@ def orbit(self, P, N, **kwds): if N[0] < 0 or N[1] < 0: raise TypeError("orbit bounds must be non-negative") if N[0] > N[1]: - return([]) + return [] R = self.domain()(P) if R in self.domain(): #Check whether R is a zero-dimensional point @@ -1342,7 +1343,7 @@ def orbit(self, P, N, **kwds): if normalize: Q.normalize_coordinates() orb.append(Q) - return(orb) + return orb def resultant(self, normalize=False): r""" @@ -1415,7 +1416,7 @@ def resultant(self, normalize=False): try: res = (f.lc() ** (d - g.degree()) * g.lc() ** (d - f.degree()) * f.__pari__().polresultant(g, x)) - return(self.domain().base_ring()(res)) + return self.domain().base_ring()(res) except (TypeError, PariError): pass #Otherwise, use Macaulay @@ -1541,7 +1542,7 @@ def primes_of_bad_reduction(self, check=True): badprimes.pop(index) else: index += 1 - return(badprimes) + return badprimes else: raise TypeError("base ring must be number field or number field ring") @@ -2165,7 +2166,7 @@ def height_difference_bound(self, prec=None): maxh = max(maxh, val[1]) L = abs(R(gcdRes).log() - R((N + 1) * binomial(N + D - d, D - d)).log() - maxh) C = max(U, L) #height difference dh(P) - L <= h(f(P)) <= dh(P) +U - return(C / (d - 1)) + return C / (d - 1) def multiplier(self, P, n, check=True): r""" @@ -2330,7 +2331,7 @@ def _multipliermod(self, P, n, p, k): F = self.dehomogenize((indexlist[i],indexlist[i+1])) l = (F.jacobian()(tuple(Q.dehomogenize(indexlist[i])))*l) % (p ** k) Q = R - return(l) + return l def _nth_preimage_tree_helper(self, Q, n, m, **kwds): r""" @@ -2677,7 +2678,7 @@ def _preperiodic_points_to_cyclegraph(self, preper): E.append([Q]) from sage.graphs.digraph import DiGraph g = DiGraph(dict(zip(V, E)), loops=True) - return(g) + return g def is_PGL_minimal(self, prime_list=None): r""" @@ -3169,11 +3170,11 @@ def automorphism_group(self, **kwds): from .endPN_automorphism_group import automorphism_group_QQ_CRT, automorphism_group_QQ_fixedpoints if alg is None: if self.degree() <= 12: - return(automorphism_group_QQ_fixedpoints(F, return_functions, iso_type)) - return(automorphism_group_QQ_CRT(F, p, return_functions, iso_type)) + return automorphism_group_QQ_fixedpoints(F, return_functions, iso_type) + return automorphism_group_QQ_CRT(F, p, return_functions, iso_type) elif alg == 'CRT': - return(automorphism_group_QQ_CRT(F, p, return_functions, iso_type)) - return(automorphism_group_QQ_fixedpoints(F, return_functions, iso_type)) + return automorphism_group_QQ_CRT(F, p, return_functions, iso_type) + return automorphism_group_QQ_fixedpoints(F, return_functions, iso_type) def critical_subscheme(self): r""" @@ -3388,7 +3389,7 @@ def is_postcritically_finite(self, err=0.01, use_algebraic_closure=True): if crit_points[i].is_preperiodic(F, err) == False: pcf = False i += 1 - return(pcf) + return pcf def critical_point_portrait(self, check=True, use_algebraic_closure=True): r""" @@ -3501,7 +3502,7 @@ def critical_point_portrait(self, check=True, use_algebraic_closure=True): else: crit_points.append(Q) Q = F(Q) - return(F._preperiodic_points_to_cyclegraph(crit_points)) + return F._preperiodic_points_to_cyclegraph(crit_points) def critical_height(self, **kwds): r""" @@ -3792,7 +3793,7 @@ def preperiodic_points(self, m, n, **kwds): points = [dom(Q) for Q in X.rational_points()] good_points = [] for Q in points: - #check if point is in indeterminacy + # check if point is in indeterminacy if not all(F(list(Q)) == 0 for F in f): good_points.append(Q) points = good_points @@ -3807,7 +3808,7 @@ def preperiodic_points(self, m, n, **kwds): orbit = [P] Q = f(P) n_plus_m = 1 - while not Q in orbit: + while Q not in orbit: orbit.append(Q) Q = f(Q) n_plus_m += 1 @@ -4024,7 +4025,7 @@ def periodic_points(self, n, minimal=True, R=None, algorithm='variety', else: if n % m == 0: points = points + cycle[:-1] - return(points) + return points else: raise TypeError("ring must be finite to generate cyclegraph") elif algorithm == 'variety': @@ -4949,7 +4950,7 @@ def _is_preperiodic(self, P, err=0.1, return_period=False): if H <= B: #it must have been in the cycle if return_period: m = orbit.index(Q) - return((m, n-m)) + return (m, n - m) else: return True if return_period: @@ -4957,6 +4958,7 @@ def _is_preperiodic(self, P, err=0.1, return_period=False): else: return False + class DynamicalSystem_projective_field(DynamicalSystem_projective, SchemeMorphism_polynomial_projective_space_field): @@ -5161,7 +5163,7 @@ def lift_to_rational_periodic(self, points_modp, B=None): done = True k += 1 - return(good_points) + return good_points def all_periodic_points(self, **kwds): r""" @@ -5815,7 +5817,7 @@ def all_preperiodic_points(self, **kwds): periods = DS.possible_periods(prime_bound=primebound, bad_primes=badprimes, ncpus=num_cpus) if periods == []: - return([]) #no rational preperiodic points + return [] #no rational preperiodic points else: p = kwds.pop("lifting_prime", 23) #find the rational preperiodic points @@ -7229,7 +7231,7 @@ def automorphism_group(self, absolute=False, iso_type=False, return_functions=Fa else: F = f[0].numerator().polynomial(z) from .endPN_automorphism_group import automorphism_group_FF - return(automorphism_group_FF(F, absolute, iso_type, return_functions)) + return automorphism_group_FF(F, absolute, iso_type, return_functions) def all_periodic_points(self, **kwds): r""" diff --git a/src/sage/dynamics/complex_dynamics/all.py b/src/sage/dynamics/complex_dynamics/all.py index 02b2d44fc78..d118421375f 100644 --- a/src/sage/dynamics/complex_dynamics/all.py +++ b/src/sage/dynamics/complex_dynamics/all.py @@ -1,4 +1,4 @@ from __future__ import absolute_import from sage.misc.lazy_import import lazy_import lazy_import("sage.dynamics.complex_dynamics.mandel_julia", - ["mandelbrot_plot", "external_ray", "julia_plot"]) + ["mandelbrot_plot", "external_ray", "kneading_sequence", "julia_plot"]) diff --git a/src/sage/dynamics/complex_dynamics/mandel_julia.py b/src/sage/dynamics/complex_dynamics/mandel_julia.py index 3dedd49a273..ea061da8df4 100644 --- a/src/sage/dynamics/complex_dynamics/mandel_julia.py +++ b/src/sage/dynamics/complex_dynamics/mandel_julia.py @@ -45,7 +45,8 @@ from sage.plot.colors import Color from sage.repl.image import Image from sage.functions.log import logb -from sage.rings.all import CC, CDF +from sage.functions.other import floor +from sage.rings.all import QQ, CC, CDF from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.schemes.projective.projective_space import ProjectiveSpace from sage.misc.prandom import randint @@ -430,6 +431,75 @@ def external_ray(theta, **kwds): pixel[int(k[0]), int(k[1])] = tuple(ray_color) return M +def kneading_sequence(theta): + r""" + Determines the kneading sequence for an angle theta in RR/ZZ which + is periodic under doubling. We use the definition for the kneading + sequence given in Definition 3.2 of [LS1994]_. + + INPUT: + + - ``theta`` -- a rational number with odd denominator + + OUTPUT: + + a string representing the kneading sequence of theta in RR/ZZ + + REFERENCES: + + [LS1994]_ + + EXAMPLES:: + + sage: kneading_sequence(0) + '*' + + :: + + sage: kneading_sequence(1/3) + '1*' + + Since 1/3 and 7/3 are the same in RR/ZZ, they have the same kneading sequence:: + + sage: kneading_sequence(7/3) + '1*' + + We can also use (finite) decimal inputs, as long as the denominator in reduced form is odd:: + + sage: kneading_sequence(1.2) + '110*' + + Since rationals with even denominator are not periodic under doubling, we have not implemented kneading sequences for such rationals:: + + sage: kneading_sequence(1/4) + Traceback (most recent call last): + ... + ValueError: input must be a rational number with odd denominator + """ + + if theta not in QQ: + raise TypeError('input must be a rational number with odd denominator') + elif QQ(theta).valuation(2) < 0: + raise ValueError('input must be a rational number with odd denominator') + else: + theta = QQ(theta) + theta = theta - floor(theta) + KS = [] + not_done = True + left = theta/2 + right = (theta + 1)/2 + y = theta + while not_done: + if ((y < left) or (y > right)): + KS.append('0') + elif ((y > left) and (y < right)): + KS.append('1') + else: + not_done = False + y = 2*y - floor(2*y) + KS_str = ''.join(KS) + '*' + return KS_str + def julia_plot(f=None, **kwds): r""" Plots the Julia set of a given polynomial ``f``. Users can specify whether @@ -592,11 +662,11 @@ def julia_plot(f=None, **kwds): max_iteration = kwds.pop("max_iteration", 500) pixel_count = kwds.pop("pixel_count", 500) base_color = kwds.pop("base_color", 'steelblue') - level_sep= kwds.pop("level_sep", 1) + level_sep = kwds.pop("level_sep", 1) number_of_colors = kwds.pop("number_of_colors", 30) interacts = kwds.pop("interact", False) - f_is_default_after_all=None + f_is_default_after_all = None if period: # pick a random c with the specified period R = PolynomialRing(CC, 'c') diff --git a/src/sage/env.py b/src/sage/env.py index d8609ad6c99..d692e329045 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -187,6 +187,7 @@ def var(key, *fallbacks, **kwds): var('SINGULARPATH', join(SAGE_SHARE, 'singular')) var('PPLPY_DOCS', join(SAGE_SHARE, 'doc', 'pplpy')) var('MAXIMA_FAS') +var('SAGE_NAUTY_BINS_PREFIX', '') # misc var('SAGE_BANNER', '') diff --git a/src/sage/geometry/polyhedron/backend_cdd.py b/src/sage/geometry/polyhedron/backend_cdd.py index 6585dfbbf40..02b20ff2ec7 100644 --- a/src/sage/geometry/polyhedron/backend_cdd.py +++ b/src/sage/geometry/polyhedron/backend_cdd.py @@ -367,6 +367,17 @@ class Polyhedron_QQ_cdd(Polyhedron_cdd, Polyhedron_QQ): sage: from sage.geometry.polyhedron.backend_cdd import Polyhedron_QQ_cdd sage: Polyhedron_QQ_cdd(parent, [ [(1,0),(0,1),(0,0)], [], []], None, verbose=False) A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices + + TESTS: + + Check that :trac:`19803` is fixed:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra + sage: P_cdd = Polyhedra(QQ, 3, 'cdd') + sage: P_cdd([[],[],[]], None) + The empty polyhedron in QQ^3 + sage: Polyhedron(vertices=[], backend='cdd', base_ring=QQ) + The empty polyhedron in QQ^0 """ _cdd_type = 'rational' @@ -439,6 +450,15 @@ class Polyhedron_RDF_cdd(Polyhedron_cdd, Polyhedron_RDF): 10 sage: P.n_facets() 10 + + Check that :trac:`19803` is fixed:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra + sage: P_cdd = Polyhedra(RDF, 3, 'cdd') + sage: P_cdd([[],[],[]], None) + The empty polyhedron in RDF^3 + sage: Polyhedron(vertices=[], backend='cdd', base_ring=RDF) + The empty polyhedron in RDF^0 """ _cdd_type = 'real' diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 57ff89c2499..c795e9196ac 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd @@ -8,6 +8,8 @@ from .polyhedron_face_lattice cimport PolyhedronFaceLattice @cython.final cdef class CombinatorialPolyhedron(SageObject): + cdef public dict __cached_methods + # Do not assume any of those attributes to be initialized, use the corresponding methods instead. cdef tuple _Vrep # the names of VRep, if they exist cdef tuple _facet_names # the names of HRep without equalities, if they exist diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index 9971320c8cf..ca8ccba5b2a 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -97,6 +97,7 @@ from .conversions \ incidence_matrix_to_bit_repr_of_Vrepr, \ facets_tuple_to_bit_repr_of_facets, \ facets_tuple_to_bit_repr_of_Vrepr +from sage.misc.cachefunc import cached_method from sage.rings.integer cimport smallInteger from cysignals.signals cimport sig_check, sig_block, sig_unblock @@ -1241,6 +1242,146 @@ cdef class CombinatorialPolyhedron(SageObject): f_vector.set_immutable() return f_vector + @cached_method + def simpliciality(self): + r""" + Return the largest `k` such that the polytope is `k`-simplicial. + + Return the dimension in case of a simplex. + + A polytope is `k`-simplicial, if every `k`-face is a simplex. + + EXAMPLES:: + + sage: cyclic = polytopes.cyclic_polytope(10,4) + sage: CombinatorialPolyhedron(cyclic).simpliciality() + 3 + + sage: hypersimplex = polytopes.hypersimplex(5,2) + sage: CombinatorialPolyhedron(hypersimplex).simpliciality() + 2 + + sage: cross = polytopes.cross_polytope(4) + sage: P = cross.join(cross) + sage: CombinatorialPolyhedron(P).simpliciality() + 3 + + sage: P = polytopes.simplex(3) + sage: CombinatorialPolyhedron(P).simpliciality() + 3 + + sage: P = polytopes.simplex(1) + sage: CombinatorialPolyhedron(P).simpliciality() + 1 + + TESTS:: + + sage: P = polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: C.simpliciality is C.simpliciality + True + """ + if not self.is_bounded(): + raise NotImplementedError("must be bounded") + cdef FaceIterator face_iter = self._face_iter(False, -2) + cdef int d + cdef int dim = self.dimension() + + if self.n_facets() == self.dimension() + 1: + # A simplex. + return self.dimension() + + cdef simpliciality = dim - 1 + + # For each face in the iterator, check if its a simplex. + face_iter.lowest_dimension = 2 # every 1-face is a simplex + d = face_iter.next_dimension() + while (d < dim): + sig_check() + if face_iter.n_atom_rep() == d + 1: + # The current face is a simplex. + face_iter.ignore_subfaces() + else: + # Current face is not a simplex. + if simpliciality > d - 1: + simpliciality = d - 1 + d = face_iter.next_dimension() + if simpliciality == 1: + # Every polytope is 1-simplicial. + d = dim + return smallInteger(simpliciality) + + @cached_method + def simplicity(self): + r""" + Return the largest `k` such that the polytope is `k`-simple. + + Return the dimension in case of a simplex. + + A polytope `P` is `k`-simple, if every `(d-1-k)`-face + is contained in exactly `k+1` facets of `P` for `1 <= k <= d-1`. + + Equivalently it is `k`-simple if the polar/dual polytope is `k`-simplicial. + + EXAMPLES:: + + sage: hyper4 = polytopes.hypersimplex(4,2) + sage: CombinatorialPolyhedron(hyper4).simplicity() + 1 + + sage: hyper5 = polytopes.hypersimplex(5,2) + sage: CombinatorialPolyhedron(hyper5).simplicity() + 2 + + sage: hyper6 = polytopes.hypersimplex(6,2) + sage: CombinatorialPolyhedron(hyper6).simplicity() + 3 + + sage: P = polytopes.simplex(3) + sage: CombinatorialPolyhedron(P).simplicity() + 3 + + sage: P = polytopes.simplex(1) + sage: CombinatorialPolyhedron(P).simplicity() + 1 + + TESTS:: + + sage: P = polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: C.simplicity is C.simplicity + True + """ + if not self.is_bounded(): + raise NotImplementedError("must be bounded") + cdef FaceIterator coface_iter = self._face_iter(True, -2) + cdef int d + cdef int dim = self.dimension() + + if self.n_facets() == self.dimension() + 1: + # A simplex. + return self.dimension() + + cdef simplicity = dim - 1 + + # For each coface in the iterator, check if its a simplex. + coface_iter.lowest_dimension = 2 # every coface of dimension 1 is a simplex + d = coface_iter.next_dimension() + while (d < dim): + sig_check() + if coface_iter.n_atom_rep() == d + 1: + # The current coface is a simplex. + coface_iter.ignore_supfaces() + else: + # Current coface is not a simplex. + if simplicity > d - 1: + simplicity = d - 1 + d = coface_iter.next_dimension() + if simplicity == 1: + # Every polytope is 1-simple. + d = dim + return smallInteger(simplicity) + def face_iter(self, dimension=None, dual=None): r""" Iterator over all proper faces of specified dimension. diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index b1f2efdc846..89492161f5b 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -65,6 +65,7 @@ from six.moves import range from six import PY2 from sage.cpython.string import bytes_to_str +from sage.env import SAGE_NAUTY_BINS_PREFIX as nautyprefix import sys from sage.misc.randstate import current_randstate @@ -528,7 +529,7 @@ def tournaments_nauty(self, n, nauty_input += " " + str(n) + " " - sp = subprocess.Popen("gentourng {0}".format(nauty_input), shell=True, + sp = subprocess.Popen(nautyprefix+"gentourng {0}".format(nauty_input), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) @@ -642,7 +643,7 @@ def nauty_directg(self, graphs, options="", debug=False): # Build directg input (graphs6 format) input = ''.join(g.graph6_string()+'\n' for g in graphs) - sub = subprocess.Popen('directg {0}'.format(options), + sub = subprocess.Popen(nautyprefix+'directg {0}'.format(options), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 68c3500b63b..139c8dfed1d 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -203,7 +203,6 @@ :meth:`~GenericGraph.lex_UP` | Perform a lexicographic UP search (LexUP) on the graph. :meth:`~GenericGraph.lex_DFS` | Perform a lexicographic depth first search (LexDFS) on the graph. :meth:`~GenericGraph.lex_DOWN` | Perform a lexicographic DOWN search (LexDOWN) on the graph. - :meth:`~GenericGraph.lex_M` | Return an ordering of the vertices according the LexM graph traversal. **Distances:** @@ -23370,7 +23369,6 @@ def is_self_complementary(self): from sage.graphs.traversals import lex_UP from sage.graphs.traversals import lex_DFS from sage.graphs.traversals import lex_DOWN - from sage.graphs.traversals import lex_M def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): r""" diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index afcb6334ce6..6450e9574ef 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8607,6 +8607,7 @@ def most_common_neighbors(self, nonedgesonly=True): from sage.graphs.chrompoly import chromatic_polynomial from sage.graphs.graph_decompositions.rankwidth import rank_decomposition from sage.graphs.graph_decompositions.vertex_separation import pathwidth + from sage.graphs.graph_decompositions.clique_separators import atoms_and_clique_separators from sage.graphs.matchpoly import matching_polynomial from sage.graphs.cliquer import all_max_clique as cliques_maximum from sage.graphs.spanning_tree import random_spanning_tree @@ -8626,7 +8627,8 @@ def most_common_neighbors(self, nonedgesonly=True): from sage.graphs.domination import is_redundant from sage.graphs.domination import private_neighbors from sage.graphs.domination import minimal_dominating_sets - + from sage.graphs.traversals import (lex_M, maximum_cardinality_search, + maximum_cardinality_search_M) _additional_categories = { "is_long_hole_free" : "Graph properties", @@ -8639,6 +8641,7 @@ def most_common_neighbors(self, nonedgesonly=True): "matching_polynomial" : "Algorithmically hard stuff", "all_max_clique" : "Clique-related methods", "cliques_maximum" : "Clique-related methods", + "atoms_and_clique_separators" : "Clique-related methods", "random_spanning_tree" : "Connectivity, orientations, trees", "is_cartesian_product" : "Graph properties", "is_distance_regular" : "Graph properties", @@ -8658,7 +8661,10 @@ def most_common_neighbors(self, nonedgesonly=True): "is_dominating" : "Domination", "is_redundant" : "Domination", "private_neighbors" : "Domination", - "minimal_dominating_sets" : "Domination" + "minimal_dominating_sets" : "Domination", + "lex_M" : "Traversals", + "maximum_cardinality_search" : "Traversals", + "maximum_cardinality_search_M" : "Traversals" } __doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) diff --git a/src/sage/graphs/graph_decompositions/clique_separators.pyx b/src/sage/graphs/graph_decompositions/clique_separators.pyx new file mode 100644 index 00000000000..3df6224d839 --- /dev/null +++ b/src/sage/graphs/graph_decompositions/clique_separators.pyx @@ -0,0 +1,597 @@ +# -*- coding: utf-8 -*- +# cython: binding=True +# distutils: language = c++ +r""" +Decomposition by clique minimal separators + +This module implements methods related to the decomposition of a graph by clique +minimal separators. See [TY1984]_ and [BPS2010]_ for more details on the +algorithms. + +Methods +------- +""" +# **************************************************************************** +# Copyright (C) 2019 David Coudert +# +# 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 libcpp.pair cimport pair +from libcpp.vector cimport vector + +from sage.ext.memory_allocator cimport MemoryAllocator +from sage.graphs.base.static_sparse_graph cimport short_digraph +from sage.graphs.base.static_sparse_graph cimport init_short_digraph +from sage.graphs.base.static_sparse_graph cimport free_short_digraph +from sage.graphs.base.static_sparse_graph cimport has_edge +from libc.stdint cimport uint32_t + +from cysignals.signals cimport sig_on, sig_off, sig_check + +from sage.sets.set import Set + +from sage.graphs.traversals cimport maximum_cardinality_search_M_short_digraph + +def make_tree(atoms, cliques): + r""" + Return a tree of atoms and cliques. + + The atoms are the leaves of the tree and the cliques are the internal + vertices. The number of atoms is the number of cliques plus one. + + As a clique may appear several times in the list ``cliques``, vertices are + numbered by pairs `(i, S)`, where `0 \leq i < |atoms| + |cliques|` and `S` + is either an atom or a clique. + + The root of the tree is the only vertex with even or null degree, i.e., 0 if + ``cliques`` is empty and 2 otherwise. When ``cliques`` is not empty, other + internal vertices (each of which is a clique) have degree 3, and the + leaves (vertices of degree 1) are the atoms. + + INPUT: + + - ``atoms`` -- list of atoms + + - ``cliques`` -- list of cliques + + EXAMPLES:: + + sage: from sage.graphs.graph_decompositions.clique_separators import make_tree + sage: G = graphs.Grid2dGraph(2, 4) + sage: A, Sc = G.atoms_and_clique_separators() + sage: T = make_tree(A, Sc) + sage: all(u[1] in A for u in T if T.degree(u) == 1) + True + sage: all(u[1] in Sc for u in T if T.degree(u) != 1) + True + + TESTS:: + + sage: from sage.graphs.graph_decompositions.clique_separators import make_tree + sage: make_tree([0], [1, 2]) + Traceback (most recent call last): + ... + ValueError: the number of atoms must be the number of cliques plus one + """ + if (atoms or cliques) and len(atoms) != len(cliques) + 1: + raise ValueError("the number of atoms must be the number of cliques plus one") + + from sage.graphs.graph import Graph + T = Graph() + if cliques: + # As a clique can appear several times, we number the vertices by + # pairs (int, Set), with 0 <= int < |atoms| + |cliques| + T.add_path(list(enumerate(cliques))) + j = len(cliques) + T.add_edges((s, (i + j, a)) for s, (i, a) in zip(enumerate(cliques), enumerate(atoms))) + # We have |atoms| = |cliques| + 1. So |atoms| + |cliques| = 2 * j + 1 + T.add_edge((j - 1, cliques[-1]), ( 2 * j, atoms[-1])) + + elif atoms: + # The graph has no clique separator + T.add_vertex(atoms[0]) + + return T + +def make_labelled_rooted_tree(atoms, cliques): + r""" + Return a :class:`~LabelledRootedTree` of atoms and cliques. + + The atoms are the leaves of the tree and the cliques are the internal + vertices. The number of atoms is the number of cliques plus one. + + EXAMPLES:: + + sage: G = graphs.PathGraph(5) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + _____{3}_____ + / / + {3, 4} ____{2}____ + / / + {2, 3} __{1}__ + / / + {0, 1} {1, 2} + + TESTS:: + + sage: from sage.graphs.graph_decompositions.clique_separators import make_labelled_rooted_tree + sage: make_labelled_rooted_tree([0], [1, 2]) + Traceback (most recent call last): + ... + ValueError: the number of atoms must be the number of cliques plus one + """ + from sage.combinat.rooted_tree import LabelledRootedTree + if not atoms and not cliques: + return LabelledRootedTree([]) + + if len(atoms) != len(cliques) + 1: + raise ValueError("the number of atoms must be the number of cliques plus one") + + def to_tree(i, n): + if i < n: + return LabelledRootedTree([LabelledRootedTree([], label=atoms[i]), to_tree(i + 1, n)], + label=cliques[i]) + return LabelledRootedTree([], label=atoms[i]) + + return to_tree(0, len(cliques)) + + +cdef inline bint is_clique(short_digraph sd, vector[int] Hx): + """ + Check if the subgraph sd[Hx] is a clique. + + This is a helper function of ``atoms_and_clique_separators``. + """ + cdef size_t Hx_size = Hx.size() + cdef size_t i, j + cdef int u + for i in range(Hx_size -1): + u = Hx[i] + for j in range(i + 1, Hx_size): + if not has_edge(sd, u, Hx[j]): + return False + return True + +def atoms_and_clique_separators(G, tree=False, rooted_tree=False, separators=False): + r""" + Return the atoms of the decomposition of `G` by clique minimal separators. + + Let `G = (V, E)` be a graph. A set `S \subset V` is a clique separator if + `G[S]` is a clique and the graph `G \setminus S` has at least 2 connected + components. Let `C \subset V` be the vertices of a connected component of `G + \setminus S`. The graph `G[C + S]` is an *atom* if it has no clique + separator. + + This method implements the algorithm proposed in [BPS2010]_, that improves + upon the algorithm proposed in [TY1984]_, for computing the atoms and the + clique minimal separators of a graph. This algorithm is based on the + :meth:`~sage.graphs.traversals.maximum_cardinality_search_M` graph traversal + and has time complexity in `O(|V|\cdot|E|)`. + + If the graph is not connected, we insert empty separators between the lists + of separators of each connected components. See the examples below for more + details. + + INPUT: + + - ``G`` -- a Sage graph + + - ``tree`` -- boolean (default: ``False``); whether to return the result as + a directed tree in which internal nodes are clique separators and leaves + are the atoms of the decomposition. Since a clique separator is repeated + when its removal partition the graph into 3 or more connected components, + vertices are labels by tuples `(i, S)`, where `S` is the set of vertices + of the atom or the clique separator, and `0 \leq i \leq |T|`. + + - ``rooted_tree`` -- boolean (default: ``False``); whether to return the + result as a :class:`~sage.combinat.rooted_tree.LabelledRootedTree`. When + ``tree`` is ``True``, this parameter is ignored. + + - ``separators`` -- boolean (default: ``False``); whether to also return the + complete list of separators considered during the execution of the + algorithm. When ``tree`` or ``rooted_tree`` is ``True``, this parameter is + ignored. + + OUTPUT: + + - By default, return a tuple `(A, S_c)`, where `A` is the list of atoms of + the graph in the order of dicovery, and `S_c` is the list of clique + separators, with possible repetitions, in the order the separator has been + considered. If furthermore ``separators`` is ``True``, return a tuple `(A, + S_h, S_c)`, where `S_c` is the list of considered separators of the graph + in the order they have been considered. + + - When ``tree`` is ``True``, format the result as a directed tree + + - When ``rooted_tree`` is ``True`` and ``tree`` is ``False``, format the + ouput as a :class:`~sage.combinat.rooted_tree.LabelledRootedTree` + + EXAMPLES: + + Example of [BPS2010]_:: + + sage: G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'], + ....: 'd': ['e', 'f', 'j', 'k'], 'e': ['g'], + ....: 'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'], + ....: 'i': ['k'], 'j': ['k']}) + sage: atoms, cliques = G.atoms_and_clique_separators() + sage: sorted(sorted(a) for a in atoms) + [['a', 'b', 'c', 'k'], + ['c', 'd', 'j', 'k'], + ['d', 'e', 'f', 'g', 'j', 'k'], + ['h', 'i', 'j', 'k']] + sage: sorted(sorted(c) for c in cliques) + [['c', 'k'], ['d', 'j', 'k'], ['j', 'k']] + sage: T = G.atoms_and_clique_separators(tree=True) + sage: T.is_tree() + True + sage: T.diameter() == len(atoms) + True + sage: all(u[1] in atoms for u in T if T.degree(u) == 1) + True + sage: all(u[1] in cliques for u in T if T.degree(u) != 1) + True + + A graph without clique separator:: + + sage: G = graphs.CompleteGraph(5) + sage: G.atoms_and_clique_separators() + ([{0, 1, 2, 3, 4}], []) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + {0, 1, 2, 3, 4} + + Graphs with several biconnected components:: + + sage: G = graphs.PathGraph(4) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + ____{2}____ + / / + {2, 3} __{1}__ + / / + {1, 2} {0, 1} + + sage: G = graphs.WindmillGraph(3, 4) + sage: G.atoms_and_clique_separators() + ([{0, 1, 2}, {0, 3, 4}, {0, 5, 6}, {0, 8, 7}], [{0}, {0}, {0}]) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + ________{0}________ + / / + {0, 1, 2} _______{0}______ + / / + {0, 3, 4} ____{0}___ + / / + {0, 8, 7} {0, 5, 6} + + When the removal of a clique separator results in `k > 2` connected + components, this separator is repeated `k - 1` times, but the repetitions + are not necessarily contiguous:: + + sage: G = Graph(2) + sage: for i in range(5): + ....: G.add_cycle([0, 1, G.add_vertex()]) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + _________{0, 1}_____ + / / + {0, 1, 4} ________{0, 1}_____ + / / + {0, 1, 2} _______{0, 1}___ + / / + {0, 1, 3} ____{0, 1} + / / + {0, 1, 5} {0, 1, 6} + + sage: G = graphs.StarGraph(3) + sage: G.subdivide_edges(G.edges(), 2) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + ______{5}______ + / / + {1, 5} ______{7}______ + / / + {2, 7} ______{9}______ + / / + {9, 3} ______{6}______ + / / + {6, 7} ______{4}_____ + / / + {4, 5} _____{0}_____ + / / + {0, 6} ____{8}____ + / / + {8, 9} __{0}__ + / / + {0, 8} {0, 4} + + If the graph is not connected, we insert empty separators between the lists + of separators of each connected components. For instance, let `G` be a graph + with 3 connected components. The method returns the list `S_c = + [S_0,\cdots,S_{i},\ldots, S_{j},\ldots,S_{k-1}]` of `k` clique separators, + where `i` and `j` are the indexes of the inserted empty separators and `0 + \leq i < j < k - 1`. The method also returns the list `A = + [A_0,\ldots,S_{k}]` of the `k + 1` atoms, with `k + 1 \geq 3`. The lists of + atoms and clique separators of each of the connected components are + respectively `[A_0,\ldots,A_{i}]` and `[S_0,\ldots,S_{i-1}]`, + `[A_{i+1},\ldots,A_{j}]` and `[S_{i+1},\ldots,S_{j-1}]`, and + `[A_{j+1},\ldots,A_{k}]` and `[S_{j+1},\ldots,S_{k-1}]`. One can check that + for each connected component, we get one atom more than clique separators:: + + sage: G = graphs.PathGraph(3) * 3 + sage: A, Sc = G.atoms_and_clique_separators() + sage: A + [{1, 2}, {0, 1}, {4, 5}, {3, 4}, {8, 7}, {6, 7}] + sage: Sc + [{1}, {}, {4}, {}, {7}] + sage: i , j = [i for i, s in enumerate(Sc) if not s] + sage: i, j + (1, 3) + sage: A[:i+1], Sc[:i] + ([{1, 2}, {0, 1}], [{1}]) + sage: A[i+1:j+1], Sc[i+1:j] + ([{4, 5}, {3, 4}], [{4}]) + sage: A[j+1:], Sc[j+1:] + ([{8, 7}, {6, 7}], [{7}]) + sage: I = [-1, i, j, len(Sc)] + sage: for i, j in zip(I[:-1], I[1:]): + ....: print(A[i+1:j+1], Sc[i+1:j]) + [{1, 2}, {0, 1}] [{1}] + [{4, 5}, {3, 4}] [{4}] + [{8, 7}, {6, 7}] [{7}] + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + ______{1}______ + / / + {1, 2} ______{}______ + / / + {0, 1} _____{4}_____ + / / + {4, 5} ____{}_____ + / / + {3, 4} __{7}__ + / / + {6, 7} {8, 7} + + Loops and multiple edges are ignored:: + + sage: G.allow_loops(True) + sage: G.add_edges([(u, u) for u in G]) + sage: G.allow_multiple_edges(True) + sage: G.add_edges(G.edges()) + sage: ascii_art(G.atoms_and_clique_separators(rooted_tree=True)) + ______{1}______ + / / + {1, 2} ______{}______ + / / + {0, 1} _____{4}_____ + / / + {4, 5} ____{}_____ + / / + {3, 4} __{7}__ + / / + {6, 7} {8, 7} + + We can check that the returned list of separators is valid:: + + sage: G = graphs.RandomGNP(50, .1) + sage: while not G.is_connected(): + ....: G = graphs.RandomGNP(50, .1) + sage: _, separators, _ = G.atoms_and_clique_separators(separators=True) + sage: for S in separators: + ....: H = G.copy() + ....: H.delete_vertices(S) + ....: if H.is_connected(): + ....: raise ValueError("something goes wrong") + + TESTS:: + + sage: EmptyGraph = Graph() + sage: EmptyGraph.atoms_and_clique_separators() + ([], []) + sage: EmptyGraph.atoms_and_clique_separators(separators=True) + ([], [], []) + sage: EmptyGraph.atoms_and_clique_separators(tree=True) + Graph on 0 vertices + sage: EmptyGraph.atoms_and_clique_separators(rooted_tree=True) + None[] + sage: ascii_art(EmptyGraph.atoms_and_clique_separators(rooted_tree=True)) + None + sage: I4 = Graph(4) + sage: I4.atoms_and_clique_separators() + ([{0}, {1}, {2}, {3}], [{}, {}, {}]) + sage: I4.atoms_and_clique_separators(separators=True) + ([{0}, {1}, {2}, {3}], [{}, {}, {}], [{}, {}, {}]) + sage: I4.atoms_and_clique_separators(tree=True) + Graph on 7 vertices + sage: I4.atoms_and_clique_separators(rooted_tree=True) + {}[{0}[], {}[{1}[], {}[{3}[], {2}[]]]] + sage: ascii_art(I4.atoms_and_clique_separators(rooted_tree=True)) + ___{}___ + / / + {0} __{}___ + / / + {1} _{}_ + / / + {3} {2} + """ + cdef list A = [] # atoms + cdef list Sh = [] # separators + cdef list Sc = [] # clique separators + cdef bint first = True + + if not G.is_connected(): + from sage.graphs.graph import Graph + + for cc in G.connected_components(): + g = Graph([cc, G.edge_boundary(cc, cc, False, False)], + format='vertices_and_edges', + loops=True, multiedges=True) + res = g.atoms_and_clique_separators(tree=False, rooted_tree=False, separators=separators) + + # Update lists of atoms, separators and clique separators + A.extend(res[0]) + if separators: + if not first: + Sh.append(Set()) + Sh.extend(res[1]) + if not first: + Sc.append(Set()) + if separators: + Sc.extend(res[2]) + else: + Sc.extend(res[1]) + first = False + + # Format and return the result + if tree: + return make_tree(A, Sc) + elif rooted_tree: + return make_labelled_rooted_tree(A, Sc) + elif separators: + return A, Sh, Sc + return A, Sc + + cdef int N = G.order() + cdef list int_to_vertex = list(G) + + # Copying the whole graph to obtain the list of neighbors quicker than by + # calling out_neighbors. This data structure is well documented in the + # module sage.graphs.base.static_sparse_graph + cdef short_digraph sd + init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex) + + # variables for the manipulation of the short digraph + cdef uint32_t** p_vertices = sd.neighbors + cdef uint32_t* p_tmp + cdef uint32_t* p_end + + cdef MemoryAllocator mem = MemoryAllocator() + cdef int* alpha = mem.calloc(N, sizeof(int)) + cdef int* alpha_inv = mem.calloc(N, sizeof(int)) + cdef bint* X = mem.calloc(N, sizeof(bint)) + cdef bint* active = mem.calloc(N, sizeof(bint)) + cdef int* waiting_list = mem.calloc(N, sizeof(int)) + cdef int* seen = mem.calloc(N, sizeof(int)) + cdef list F = [] + cdef int u, v, waiting_begin, waiting_end + + sig_on() + maximum_cardinality_search_M_short_digraph(sd, 0, alpha, alpha_inv, F, X) + sig_off() + + # Instead of building the graph H of the triangulation, extracting the + # neighbors of vertex alpha[i] and then removing that vertex from H, we + # directly build the neighborhoods. Note that vertices are removed from the + # graph of the triangulation in the order of alpha. Hence, at step i, this + # graph has no vertex u such that alpha_inv[u] < i, and edge (u, v) is + # removed from the graph when min(alpha_inv[u], alpha_inv[v]) is removed. + # The neighborhood of x at step i is thus {v in N_H(x) | alpha_inv[v] > i}. + cdef vector[vector[int]] H + H.resize(N) + for u in range(N): + p_tmp = p_vertices[u] + p_end = p_vertices[u + 1] + while p_tmp < p_end: + v = p_tmp[0] + p_tmp += 1 + if u < v: + # We consider edge (u, v) only once, when u > v, and the + # short_digraph data structure ensures that neighbors are sorted + break + if alpha_inv[u] < alpha_inv[v]: + if X[u]: + H[u].push_back(v) + elif X[v]: + H[v].push_back(u) + for u, v in F: + if alpha_inv[u] < alpha_inv[v]: + if X[u]: + H[u].push_back(v) + elif X[v]: + H[v].push_back(u) + + # Instead of using a copy Gp of G and removing from it the vertices of the + # connected component of an atom after its discovery, we use an array of + # booleans to avoid visiting inactive vertices + for u in range(N): + active[u] = True + seen[u] = -1 + + cdef frozenset Sint + cdef vector[int] Sint_min + cdef vector[int] Cint + cdef vector[int] Hx + cdef size_t ui, vi + cdef bint stop + + for i in range(N): + sig_check() + x = alpha[i] + if X[x] and not H[x].empty(): + Hx = H[x] + + if separators: + Sh.append(Set(int_to_vertex[u] for u in Hx)) + + if is_clique(sd, Hx): + # The subgraph G[H[x]] = G[S] is a clique. + # We extract the connected component of Gp - S containing x + Sint = frozenset(Hx) + Sint_min.clear() + Cint.clear() + Cint.push_back(x) + seen[x] = x + waiting_list[0] = x + waiting_begin = 0 + waiting_end = 0 + while waiting_begin <= waiting_end: + + u = waiting_list[waiting_begin] + waiting_begin += 1 + p_tmp = p_vertices[u] + end = p_vertices[u + 1] + + while p_tmp < end: + v = p_tmp[0] + p_tmp += 1 + + if active[v] and seen[v] != x: + seen[v] = x + if v in Sint: + # We keep only the vertices of the clique + # separator incident to the connected component + # containing x + Sint_min.push_back(v) + else: + Cint.push_back(v) + waiting_end += 1 + waiting_list[waiting_end] = v + + # Store the atom Smin + C and the minimal clique separator Smin + Smin = Set(int_to_vertex[u] for u in Sint_min) + A.append(Set(Smin.set().union(int_to_vertex[u] for u in Cint))) + Sc.append(Smin) + + # "Remove" the vertices of Cint from the graph Gp + for u in Cint: + active[u] = False + + free_short_digraph(sd) + H.clear() + + # We add the last atom + if Sc: + A.append(Set(int_to_vertex[x] for x in range(N) if active[x])) + elif G: + # The graph has no clique separator + A.append(Set(int_to_vertex)) + + # Format and return the result + if tree: + return make_tree(A, Sc) + elif rooted_tree: + return make_labelled_rooted_tree(A, Sc) + if separators: + return A, Sh, Sc + return A, Sc diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index 14721c7ac6a..a5794c1a6c0 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -18,6 +18,7 @@ from __future__ import print_function, absolute_import, division from six.moves import range from six import PY2 +from sage.env import SAGE_NAUTY_BINS_PREFIX as nautyprefix import subprocess @@ -899,7 +900,7 @@ def nauty_geng(self, options="", debug=False): sage: gen = graphs.nauty_geng("4", debug=True) sage: print(next(gen)) - >A geng -d0D3 n=4 e=0-6 + >A ...geng -d0D3 n=4 e=0-6 sage: gen = graphs.nauty_geng("4 -q", debug=True) sage: next(gen) '' @@ -913,16 +914,16 @@ def nauty_geng(self, options="", debug=False): ... ValueError: wrong format of parameter option sage: list(graphs.nauty_geng("-c3", debug=True)) - ['>E Usage: geng [-cCmtfbd#D#] [-uygsnh] [-lvq] \n'] + ['>E Usage: ...geng [-cCmtfbd#D#] [-uygsnh] [-lvq] ... sage: list(graphs.nauty_geng("-c 3", debug=True)) - ['>A geng -cd1D2 n=3 e=2-3\n', Graph on 3 vertices, Graph on 3 vertices] + ['>A ...geng -cd1D2 n=3 e=2-3\n', Graph on 3 vertices, Graph on 3 vertices] """ if PY2: enc_kwargs = {} else: enc_kwargs = {'encoding': 'latin-1'} - sp = subprocess.Popen("geng {0}".format(options), shell=True, + sp = subprocess.Popen(nautyprefix+"geng {0}".format(options), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, **enc_kwargs) diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index ef36b7294e8..aa3d5e39885 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -31,6 +31,7 @@ --------------------- """ from __future__ import print_function +from sage.env import SAGE_NAUTY_BINS_PREFIX as nautyprefix class HypergraphGenerators(): r""" @@ -161,7 +162,7 @@ def nauty(self, number_of_sets, number_of_vertices, nauty_input += " " + str(number_of_vertices) + " " + str(number_of_sets) + " " - sp = subprocess.Popen("genbg {0}".format(nauty_input), shell=True, + sp = subprocess.Popen(nautyprefix+"genbg {0}".format(nauty_input), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 3d890a3602e..7a3d0761e1e 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -1488,9 +1488,10 @@ def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): g = strongly_regular_graph(vv, k, l - 2*mu + k) return twograph_descendant(g, next(g.vertex_iterator()), name=True) - return(la, v + 1) + return la, v + 1 return + @cached_function def is_taylor_twograph_srg(int v,int k,int l,int mu): r""" diff --git a/src/sage/graphs/traversals.pxd b/src/sage/graphs/traversals.pxd new file mode 100644 index 00000000000..a81c72dcd9d --- /dev/null +++ b/src/sage/graphs/traversals.pxd @@ -0,0 +1,8 @@ +from sage.graphs.base.static_sparse_graph cimport short_digraph + +cdef maximum_cardinality_search_M_short_digraph(short_digraph sd, + int initial_vertex, + int* alpha, + int* alpha_inv, + list F, + bint* X) diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 4820005fd94..5116f71f75b 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -18,6 +18,8 @@ Graph traversals. :meth:`~lex_M` | Return an ordering of the vertices according the LexM graph traversal. :meth:`~lex_M_slow` | Return an ordering of the vertices according the LexM graph traversal. :meth:`~lex_M_fast` | Return an ordering of the vertices according the LexM graph traversal. + :meth:`~maximum_cardinality_search` | Return an ordering of the vertices according a maximum cardinality search. + :meth:`~maximum_cardinality_search_M` | Return the ordering and the edges of the triangulation produced by MCS-M. Methods ------- @@ -37,11 +39,15 @@ import collections from libc.string cimport memset from sage.ext.memory_allocator cimport MemoryAllocator -from sage.graphs.base.static_sparse_graph cimport short_digraph from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge from libc.stdint cimport uint32_t +from cysignals.signals cimport sig_on, sig_off, sig_check + +from libcpp.queue cimport priority_queue +from libcpp.pair cimport pair +from libcpp.vector cimport vector def lex_BFS(G, reverse=False, tree=False, initial_vertex=None): r""" @@ -846,16 +852,15 @@ def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorith ``'lex_M_fast'`` cannot return labels:: - sage: G = graphs.CompleteGraph(6) - sage: G.lex_M(labels=True, algorithm='lex_M_fast') + sage: Graph().lex_M(labels=True, algorithm='lex_M_fast') Traceback (most recent call last): ... ValueError: 'lex_M_fast' cannot return labels assigned to vertices The method works only for undirected graphs:: - sage: G = digraphs.Circuit(15) - sage: G.lex_M() + sage: from sage.graphs.traversals import lex_M + sage: lex_M(DiGraph()) Traceback (most recent call last): ... ValueError: input graph must be undirected @@ -877,8 +882,7 @@ def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorith ``initial_vertex`` should be a valid graph vertex:: - sage: G = graphs.CompleteGraph(6) - sage: G.lex_M(initial_vertex='foo') + sage: Graph().lex_M(initial_vertex='foo') Traceback (most recent call last): ... ValueError: 'foo' is not a graph vertex @@ -1326,3 +1330,500 @@ def is_valid_lex_M_order(G, alpha, F): if not K.is_clique(): return False return True + + +def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None): + r""" + Return an ordering of the vertices according a maximum cardinality search. + + Maximum cardinality search (MCS) is a graph traversal introduced in + [TY1984]_. It starts by assigning an arbitrary vertex (or the specified + ``initial_vertex``) of `G` the last position in the ordering `\alpha`. Every + vertex keeps a weight equal to the number of its already processed neighbors + (i.e., already added to `\alpha`), and a vertex of largest such number is + chosen at each step `i` to be placed in position `n - i` in `\alpha`. This + ordering can be computed in time `O(n + m)`. + + When the graph is chordal, the ordering returned by MCS is a *perfect + elimination ordering*, like :meth:`~sage.graphs.traversals.lex_BFS`. So + this ordering can be used to recognize chordal graphs. See [He2006]_ for + more details. + + .. NOTE:: + + The current implementation is for connected graphs only. + + INPUT: + + - ``G`` -- a Sage Graph + + - ``reverse`` -- boolean (default: ``False``); whether to return the + vertices in discovery order, or the reverse + + - ``tree`` -- boolean (default: ``False``); whether to also return the + discovery directed tree (each vertex being linked to the one that saw + it for the first time) + + - ``initial_vertex`` -- (default: ``None``); the first vertex to consider + + OUTPUT: + + By default, return the ordering `\alpha` as a list. When ``tree`` is + ``True``, the method returns a tuple `(\alpha, T)`, where `T` is a directed + tree with the same set of vertices as `G`and a directed edge from `u` to `v` + if `u` was the first vertex to saw `v`. + + EXAMPLES: + + When specified, the ``initial_vertex`` is placed at the end of the ordering, + unless parameter ``reverse`` is ``True``, in which case it is placed at the + beginning:: + + sage: G = graphs.PathGraph(4) + sage: G.maximum_cardinality_search(initial_vertex=0) + [3, 2, 1, 0] + sage: G.maximum_cardinality_search(initial_vertex=1) + [0, 3, 2, 1] + sage: G.maximum_cardinality_search(initial_vertex=2) + [0, 1, 3, 2] + sage: G.maximum_cardinality_search(initial_vertex=3) + [0, 1, 2, 3] + sage: G.maximum_cardinality_search(initial_vertex=3, reverse=True) + [3, 2, 1, 0] + + Returning the discovery tree:: + + sage: G = graphs.PathGraph(4) + sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=0) + sage: T.order(), T.size() + (4, 3) + sage: T.edges(labels=False, sort=True) + [(1, 0), (2, 1), (3, 2)] + sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=3) + sage: T.edges(labels=False, sort=True) + [(0, 1), (1, 2), (2, 3)] + + TESTS: + + sage: Graph().maximum_cardinality_search() + [] + sage: Graph(1).maximum_cardinality_search() + [0] + sage: Graph(2).maximum_cardinality_search() + Traceback (most recent call last): + ... + ValueError: the input graph is not connected + sage: graphs.PathGraph(2).maximum_cardinality_search(initial_vertex=17) + Traceback (most recent call last): + ... + ValueError: vertex (17) is not a vertex of the graph + """ + if tree: + from sage.graphs.digraph import DiGraph + + cdef int N = G.order() + if not N: + return ([], DiGraph()) if tree else [] + if N == 1: + return (list(G), DiGraph(G)) if tree else list(G) + + cdef list int_to_vertex = list(G) + + if initial_vertex is None: + initial_vertex = 0 + elif initial_vertex in G: + initial_vertex = int_to_vertex.index(initial_vertex) + else: + raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex)) + + cdef short_digraph sd + init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex) + cdef uint32_t** p_vertices = sd.neighbors + cdef uint32_t* p_tmp + cdef uint32_t* p_end + + cdef MemoryAllocator mem = MemoryAllocator() + cdef int* weight = mem.calloc(N, sizeof(int)) + cdef bint* seen = mem.calloc(N, sizeof(bint)) + cdef int* pred = mem.allocarray(N, sizeof(int)) + + cdef int i, u, v + for i in range(N): + weight[i] = 0 + seen[i] = False + pred[i] = i + + # We emulate a heap with decrease key operation using a priority queue. + # A vertex can be inserted multiple times (up to its degree), but only the + # first extraction (with maximum weight) matters. The size of the queue will + # never exceed O(m). + cdef priority_queue[pair[int, int]] pq + pq.push((0, initial_vertex)) + + # The ordering alpha is feed in reversed order and revert afterword + cdef list alpha = [] + + while not pq.empty(): + _, u = pq.top() + pq.pop() + if seen[u]: + # We use a lazy decrease key mode, so u can be several times in pq + continue + + alpha.append(int_to_vertex[u]) + seen[u] = True + + p_tmp = p_vertices[u] + p_end = p_vertices[u + 1] + while p_tmp < p_end: + v = p_tmp[0] + if not seen[v]: + weight[v] += 1 + pq.push((weight[v], v)) + if pred[v] == v: + pred[v] = u + p_tmp += 1 + + free_short_digraph(sd) + + if len(alpha) < N: + raise ValueError("the input graph is not connected") + + if not reverse: + alpha.reverse() + + if tree: + D = DiGraph([int_to_vertex, [(int_to_vertex[i], int_to_vertex[pred[i]]) + for i in range(N) if pred[i] != i]], + format="vertices_and_edges") + return alpha, D + + return alpha + + +cdef inline int swap(int* alpha, int* alpha_inv, int u, int new_pos_u): + """ + Swap positions of u and v in alpha, where v is be the vertex occupying cell + new_pos_u in alpha. + """ + cdef int v = alpha[new_pos_u] + alpha[new_pos_u], alpha[alpha_inv[u]] = u, v + alpha_inv[u], alpha_inv[v] = alpha_inv[v], alpha_inv[u] + return v + +cdef maximum_cardinality_search_M_short_digraph(short_digraph sd, int initial_vertex, + int* alpha, int* alpha_inv, list F, bint* X): + r""" + Compute the ordering and the edges of the triangulation produced by MCS-M. + + Maximum cardinality search M (MCS-M) is an extension of MCS + (:meth:`~sage.graphs.traversals.maximum_cardinality_search`) in the same way + that Lex-M (:meth:`~sage.graphs.traversals.lex_M`) is an extension of + Lex-BFS (:meth:`~sage.graphs.traversalslex_BFS`). That is, in MCS-M when `u` + receives number `i` at step `n - i + 1`, it increments the weight of all + unnumbered vertices `v` for which there exists a path between `u` and `v` + consisting only of unnumbered vertices with weight strictly less than + `w^-(u)` and `w^-(v)`, where `w^-` is the number of times a vertex has been + reached during previous iterations. See [BBHP2004]_ for the details of this + `O(nm)` time algorithm. + + If `G` is not connected, the orderings of each of its connected components + are added consecutively. + + This method is the core of + :meth:`~sage.graphs.traversals.maximum_cardinality_search_M`. + + INPUT: + + - ``sd`` -- a ``short_digraph`` as documented in + :mod:`~sage.graphs.base.static_sparse_graph` + + - ``initial_vertex`` -- int; initial vertex for the search + + - ``alpha`` -- int array of size `N`; the computed ordering of MCS-M + + - ``alpha_inv`` -- int array of size `N`; the position of vertex ``u`` in + ``alpha``, that is the inverse function of alpha. So we have + ``alpha[alpha_inv[u]] == u`` for all `0 \leq u < N - 1`. + + - ``F`` -- list; to be filled with the edges of the triangulation + + - ``X`` -- boolean array of size `N`; ``X[u]`` is set to ``True`` if the + neighborhood of `u` is a separator of the graph + + TESTS:: + + sage: Graph().maximum_cardinality_search_M() + ([], [], []) + sage: Graph(1).maximum_cardinality_search_M() + ([0], [], []) + sage: graphs.PathGraph(2).maximum_cardinality_search_M(initial_vertex=17) + Traceback (most recent call last): + ... + ValueError: vertex (17) is not a vertex of the graph + + .. TODO:: + + Use a fast heap data structure with decrease-key operation. + """ + # Initialization of data structures + cdef int N = sd.n + cdef MemoryAllocator mem = MemoryAllocator() + # number of times a vertex is reached, initially 0 + cdef int* weight = mem.calloc(N, sizeof(int)) + # has a vertex been reached, initally False + cdef bint* reached = mem.calloc(N, sizeof(bint)) + + cdef int i, u, v, xi + for i in range(N): + weight[i] = 0 + alpha[i] = i + alpha_inv[i] = i + X[i] = False + + # If an initial vertex is specified, we put it at position 0 in alpha. + # This way, it will be the first vertex to be considered. + if initial_vertex: + swap(alpha, alpha_inv, initial_vertex, 0) + + # variables for the manipulation of the short digraph + cdef uint32_t** p_vertices = sd.neighbors + cdef uint32_t* p_tmp + cdef uint32_t* p_end + + cdef vector[vector[int]] reach + cdef int s = -1 + cdef int current_pos = N + + while current_pos: + + # Choose an unnumbered vertex of maximum weight. + # This could be done faster if we had a heap data structure with + # decrease key operation. + u = alpha[0] + for i in range(current_pos): + v = alpha[i] + if weight[u] < weight[v]: + u = v + + # Swap u and the vertex v occupying position current_pos in alpha + current_pos -= 1 + v = swap(alpha, alpha_inv, u, current_pos) + reached[u] = True + + # If the weight decreases, the neighborhood of u is a separator + if weight[u] <= s: + X[u] = True + s = weight[u] + + # Search for new edges of the triangulation. + # We add an edge to the triangulation between u and any unnumbered + # vertex v such that there is a path (v, x1, x2,... , xk, u) through + # unnumbered vertices such that count-[xi] < count-[v], 1 <= i <= k. If + # such an edge is found, we increase the count of v for next round. + + # Mark all unnumbered vertices unreached. These vertices occupy + # positions 0,..,current_pos-1 in alpha + reach.clear() + reach.resize(N) + for i in range(current_pos): + v = alpha[i] + reached[v] = False + + # Initialize reach with unnumbered neighbors of u + p_tmp = p_vertices[u] + p_end = p_vertices[u + 1] + while p_tmp < p_end: + v = p_tmp[0] + p_tmp += 1 + if not reached[v]: + reach[weight[v]].push_back(v) + reached[v] = True + weight[v] += 1 + + # Search + for i in range(N): + while not reach[i].empty(): + xi = reach[i].back() + reach[i].pop_back() + p_tmp = p_vertices[xi] + p_end = p_vertices[xi + 1] + while p_tmp < p_end: + v = p_tmp[0] + p_tmp += 1 + if reached[v]: + continue + reached[v] = True + if i < weight[v]: + reach[weight[v]].push_back(v) + weight[v] += 1 + F.append((u, v)) + else: + reach[i].push_back(v) + + reach.clear() + +def maximum_cardinality_search_M(G, initial_vertex=None): + r""" + Return the ordering and the edges of the triangulation produced by MCS-M. + + Maximum cardinality search M (MCS-M) is an extension of MCS + (:meth:`~sage.graphs.traversals.maximum_cardinality_search`) in the same way + that Lex-M (:meth:`~sage.graphs.traversals.lex_M`) is an extension of + Lex-BFS (:meth:`~sage.graphs.traversals.lex_BFS`). That is, in MCS-M when + `u` receives number `i` at step `n - i + 1`, it increments the weight of all + unnumbered vertices `v` for which there exists a path between `u` and `v` + consisting only of unnumbered vertices with weight strictly less than + `w^-(u)` and `w^-(v)`, where `w^-` is the number of times a vertex has been + reached during previous iterations. See [BBHP2004]_ for the details of this + `O(nm)` time algorithm. + + If `G` is not connected, the orderings of each of its connected components + are added consecutively. Furthermore, if `G` has `k` connected components + `C_i` for `0 \leq i < k`, `X` contains at least one vertex of `C_i` for each + `i \geq 1`. Hence, `|X| \geq k - 1`. In particular, some isolated vertices + (i.e., of degree 0) can appear in `X` as for such a vertex `x`, we have that + `G \setminus N(x) = G` is not connected. + + INPUT: + + - ``G`` -- a Sage graph + + - ``initial_vertex`` -- (default: ``None``); the first vertex to consider + + OUTPUT: a tuple `(\alpha, F, X)`, where + + - `\alpha` is the resulting ordering of the vertices. If an initial vertex + is specified, it gets the last position in the ordering `\alpha`. + + - `F` is the list of edges of a minimal triangulation of `G` according + `\alpha` + + - `X` is is a list of vertices such that for each `x \in X`, the + neighborhood of `x` in `G` is a separator (i.e., `G \setminus N(x)` is not + connected). Note that we may have `N(x) = \emptyset` if `G` is not + connected and `x` has degree 0. + + EXAMPLES: + + Chordal graphs have a perfect elimination ordering, and so the set `F` of + edges of the triangulation is empty:: + + sage: G = graphs.RandomChordalGraph(20) + sage: alpha, F, X = G.maximum_cardinality_search_M(); F + [] + + The cycle of order 4 is not chordal and so the triangulation has one edge:: + + sage: G = graphs.CycleGraph(4) + sage: alpha, F, X = G.maximum_cardinality_search_M(); len(F) + 1 + + The number of edges needed to triangulate of a cycle graph or order `n` is + `n - 3`, independently of the initial vertex:: + + sage: n = randint(3, 20) + sage: C = graphs.CycleGraph(n) + sage: _, F, X = C.maximum_cardinality_search_M() + sage: len(F) == n - 3 + True + sage: _, F, X = C.maximum_cardinality_search_M(initial_vertex=C.random_vertex()) + sage: len(F) == n - 3 + True + + When an initial vertex is specified, it gets the last position in the + ordering:: + + sage: G = graphs.PathGraph(4) + sage: G.maximum_cardinality_search_M(initial_vertex=0) + ([3, 2, 1, 0], [], [2, 3]) + sage: G.maximum_cardinality_search_M(initial_vertex=1) + ([3, 2, 0, 1], [], [2, 3]) + sage: G.maximum_cardinality_search_M(initial_vertex=2) + ([0, 1, 3, 2], [], [0, 1]) + sage: G.maximum_cardinality_search_M(initial_vertex=3) + ([0, 1, 2, 3], [], [0, 1]) + + + When `G` is not connected, the orderings of each of its connected components + are added consecutively, the vertices of the component containing the + initial vertex occupying the last positions:: + + sage: G = graphs.CycleGraph(4) * 2 + sage: G.maximum_cardinality_search_M()[0] + [5, 4, 6, 7, 2, 3, 1, 0] + sage: G.maximum_cardinality_search_M(initial_vertex=7)[0] + [2, 1, 3, 0, 5, 6, 4, 7] + + Furthermore, if `G` has `k` connected components, `X` contains at least one + vertex per connected component, except for the first one, and so at least `k + - 1` vertices:: + + sage: for k in range(1, 5): + ....: _, _, X = Graph(k).maximum_cardinality_search_M() + ....: if len(X) < k - 1: + ....: raise ValueError("something goes wrong") + sage: G = graphs.RandomGNP(10, .2) + sage: cc = G.connected_components() + sage: _, _, X = G.maximum_cardinality_search_M() + sage: len(X) >= len(cc) - 1 + True + + In the example of [BPS2010]_, the triangulation has 3 edges:: + + sage: G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'], + ....: 'd': ['e', 'f', 'j', 'k'], 'e': ['g'], + ....: 'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'], + ....: 'i': ['k'], 'j': ['k']}) + sage: _, F, _ = G.maximum_cardinality_search_M(initial_vertex='a') + sage: len(F) + 3 + + TESTS:: + + sage: Graph().maximum_cardinality_search_M() + ([], [], []) + sage: Graph(1).maximum_cardinality_search_M() + ([0], [], []) + sage: graphs.PathGraph(2).maximum_cardinality_search_M(initial_vertex=17) + Traceback (most recent call last): + ... + ValueError: vertex (17) is not a vertex of the graph + """ + cdef list int_to_vertex = list(G) + + if initial_vertex is None: + initial_vertex = 0 + elif initial_vertex in G: + initial_vertex = int_to_vertex.index(initial_vertex) + else: + raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex)) + + cdef int N = G.order() + if not N: + return ([], [], []) + if N == 1: + return (list(G), [], []) + + # Copying the whole graph to obtain the list of neighbors quicker than by + # calling out_neighbors. This data structure is well documented in the + # module sage.graphs.base.static_sparse_graph + cdef short_digraph sd + init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex) + + cdef MemoryAllocator mem = MemoryAllocator() + cdef int* alpha = mem.calloc(N, sizeof(int)) + cdef int* alpha_inv = mem.calloc(N, sizeof(int)) + cdef bint* X = mem.calloc(N, sizeof(bint)) + cdef list F = [] + + sig_on() + maximum_cardinality_search_M_short_digraph(sd, initial_vertex, alpha, alpha_inv, F, X) + sig_off() + + free_short_digraph(sd) + + cdef int u, v + return ([int_to_vertex[alpha[u]] for u in range(N)], + [(int_to_vertex[u], int_to_vertex[v]) for u, v in F], + [int_to_vertex[u] for u in range(N) if X[u]]) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 9bc5ff4e7d3..a056ad4c455 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -228,7 +228,7 @@ def __init__(self, parent, x): else: i=i+1 AbstractWordTietzeWord = libgap.eval('AbstractWordTietzeWord') - x = AbstractWordTietzeWord(l, parent._gap_gens()) + x = AbstractWordTietzeWord(l, parent.gap().GeneratorsOfGroup()) ElementLibGAP.__init__(self, parent, x) def __hash__(self): @@ -840,7 +840,7 @@ def _element_constructor_(self, *args, **kwds): sage: G([1, 2, -2, 1, 1, -2]) # indirect doctest a^3*b^-1 - sage: G( G._gap_gens()[0] ) + sage: G( a.gap() ) a sage: type(_) diff --git a/src/sage/groups/libgap_group.py b/src/sage/groups/libgap_group.py index cf3d39056e4..25783d73d6e 100644 --- a/src/sage/groups/libgap_group.py +++ b/src/sage/groups/libgap_group.py @@ -34,8 +34,9 @@ from sage.groups.group import Group from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP +from sage.groups.libgap_mixin import GroupMixinLibGAP -class GroupLibGAP(Group, ParentLibGAP): +class GroupLibGAP(GroupMixinLibGAP, Group, ParentLibGAP): Element = ElementLibGAP @@ -62,6 +63,3 @@ def __init__(self, *args, **kwds): ParentLibGAP.__init__(self, *args, **kwds) Group.__init__(self) - - - diff --git a/src/sage/groups/libgap_mixin.py b/src/sage/groups/libgap_mixin.py index f125a6d2182..16417aea5e0 100644 --- a/src/sage/groups/libgap_mixin.py +++ b/src/sage/groups/libgap_mixin.py @@ -11,24 +11,62 @@ just raise ``NotImplementedError``. """ -from sage.libs.all import libgap +from sage.libs.gap.libgap import libgap +from sage.libs.gap.element import GapElement +from sage.structure.element import parent from sage.misc.cachefunc import cached_method from sage.groups.class_function import ClassFunction_libgap - +from sage.groups.libgap_wrapper import ElementLibGAP class GroupMixinLibGAP(object): + def __contains__(self, elt): + r""" + TESTS:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.SL(2,3)) + sage: libgap([[1,0],[0,1]]) in G + False + sage: o = Mod(1, 3) + sage: z = Mod(0, 3) + sage: libgap([[o,z],[z,o]]) in G + True + + sage: G.an_element() in GroupLibGAP(libgap.GL(2,3)) + True + sage: G.an_element() in GroupLibGAP(libgap.GL(2,5)) + False + """ + if parent(elt) is self: + return True + elif isinstance(elt, GapElement): + return elt in self.gap() + elif isinstance(elt, ElementLibGAP): + return elt.gap() in self.gap() + else: + try: + elt2 = self(elt) + except Exception: + return False + return elt == elt2 - @cached_method def is_abelian(self): r""" - Test whether the group is Abelian. + Return whether the group is Abelian. OUTPUT: - Boolean. ``True`` if this group is an Abelian group. + Boolean. ``True`` if this group is an Abelian group and ``False`` + otherwise. EXAMPLES:: + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.CyclicGroup(12)).is_abelian() + True + sage: GroupLibGAP(libgap.SymmetricGroup(12)).is_abelian() + False + sage: SL(1, 17).is_abelian() True sage: SL(2, 17).is_abelian() @@ -36,7 +74,110 @@ def is_abelian(self): """ return self.gap().IsAbelian().sage() - @cached_method + def is_nilpotent(self): + r""" + Return whether this group is nilpotent. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.AlternatingGroup(3)).is_nilpotent() + True + sage: GroupLibGAP(libgap.SymmetricGroup(3)).is_nilpotent() + False + """ + return self.gap().IsNilpotentGroup().sage() + + def is_solvable(self): + r""" + Return whether this group is solvable. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.SymmetricGroup(4)).is_solvable() + True + sage: GroupLibGAP(libgap.SymmetricGroup(5)).is_solvable() + False + """ + return self.gap().IsSolvableGroup().sage() + + def is_supersolvable(self): + r""" + Return whether this group is supersolvable. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.SymmetricGroup(3)).is_supersolvable() + True + sage: GroupLibGAP(libgap.SymmetricGroup(4)).is_supersolvable() + False + """ + return self.gap().IsSupersolvableGroup().sage() + + def is_polycyclic(self): + r""" + Return whether this group is polycyclic. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.AlternatingGroup(4)).is_polycyclic() + True + sage: GroupLibGAP(libgap.AlternatingGroup(5)).is_solvable() + False + """ + return self.gap().IsPolycyclicGroup().sage() + + def is_perfect(self): + r""" + Return whether this group is perfect. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.SymmetricGroup(5)).is_perfect() + False + sage: GroupLibGAP(libgap.AlternatingGroup(5)).is_perfect() + True + + sage: SL(3,3).is_perfect() + True + """ + return self.gap().IsPerfectGroup().sage() + + def is_p_group(self): + r""" + Return whether this group is a p-group. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.CyclicGroup(9)).is_p_group() + True + sage: GroupLibGAP(libgap.CyclicGroup(10)).is_p_group() + False + """ + return self.gap().IsPGroup().sage() + + def is_simple(self): + r""" + Return whether this group is simple. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: GroupLibGAP(libgap.SL(2,3)).is_simple() + False + sage: GroupLibGAP(libgap.SL(3,3)).is_simple() + True + + sage: SL(3,3).is_simple() + True + """ + return self.gap().IsSimpleGroup().sage() + def is_finite(self): """ Test whether the matrix group is finite. @@ -98,8 +239,6 @@ def cardinality(self): +Infinity """ return self.gap().Size().sage() - from sage.rings.infinity import Infinity - return Infinity order = cardinality @@ -335,7 +474,7 @@ def irreducible_characters(self): def character(self, values): r""" - Returns a group character from ``values``, where ``values`` is + Return a group character from ``values``, where ``values`` is a list of the values of the character evaluated on the conjugacy classes. @@ -365,7 +504,7 @@ def character(self, values): def trivial_character(self): r""" - Returns the trivial character of this group. + Return the trivial character of this group. OUTPUT: a group character @@ -388,7 +527,7 @@ def trivial_character(self): def character_table(self): r""" - Returns the matrix of values of the irreducible characters of this + Return the matrix of values of the irreducible characters of this group `G` at its conjugacy classes. The columns represent the conjugacy classes of @@ -476,6 +615,11 @@ def __iter__(self): sage: next(iter(G)) [1 0] [0 1] + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.AlternatingGroup(5)) + sage: sum(1 for g in G) + 60 """ if self.list.cache is not None: for g in self.list(): @@ -483,7 +627,7 @@ def __iter__(self): return iterator = self.gap().Iterator() while not iterator.IsDoneIterator().sage(): - yield self(iterator.NextIterator(), check=False) + yield self.element_class(self, iterator.NextIterator()) def __len__(self): """ @@ -504,9 +648,10 @@ def __len__(self): ... NotImplementedError: group must be finite """ - if not self.is_finite(): - raise NotImplementedError('group must be finite') - return int(self.cardinality()) + size = self.gap().Size() + if size.IsInfinity(): + raise NotImplementedError("group must be finite") + return int(size) @cached_method def list(self): @@ -530,9 +675,10 @@ def list(self): 24 sage: v[:5] ( - [0 1] [0 1] [0 1] [0 2] [0 2] - [2 0], [2 1], [2 2], [1 0], [1 1] + [1 0] [2 0] [0 1] [0 2] [1 2] + [0 1], [0 2], [2 0], [1 0], [2 2] ) + sage: all(g in G for g in G.list()) True @@ -544,12 +690,12 @@ def list(self): sage: MG = MatrixGroup([M1, M2, M3]) sage: MG.list() ( - [-1 0] [-1 0] [ 1 0] [1 0] - [ 0 -1], [ 0 1], [ 0 -1], [0 1] + [1 0] [ 1 0] [-1 0] [-1 0] + [0 1], [ 0 -1], [ 0 1], [ 0 -1] ) sage: MG.list()[1] - [-1 0] - [ 0 1] + [ 1 0] + [ 0 -1] sage: MG.list()[1].parent() Matrix group over Integer Ring with 3 generators ( [-1 0] [ 1 0] [-1 0] @@ -579,8 +725,7 @@ def list(self): """ if not self.is_finite(): raise NotImplementedError('group must be finite') - elements = self.gap().Elements() - return tuple(self(x, check=False) for x in elements) + return tuple(self.element_class(self, g) for g in self.gap().AsList()) def is_isomorphic(self, H): """ @@ -610,12 +755,4 @@ def is_isomorphic(self, H): sage: F==G, G==H, F==H (False, False, False) """ - iso = self.gap().IsomorphismGroups(H.gap()) - if iso.is_bool(): # fail means not isomorphic - try: - iso.sage() - assert False - except ValueError: - pass - return False - return True + return self.gap().IsomorphismGroups(H.gap()) != libgap.fail diff --git a/src/sage/groups/libgap_wrapper.pxd b/src/sage/groups/libgap_wrapper.pxd index a7c689e201f..0c43b098140 100644 --- a/src/sage/groups/libgap_wrapper.pxd +++ b/src/sage/groups/libgap_wrapper.pxd @@ -1,4 +1,4 @@ -from sage.structure.element cimport MultiplicativeGroupElement, MonoidElement +from sage.structure.element cimport MultiplicativeGroupElement from sage.libs.gap.element cimport GapElement diff --git a/src/sage/groups/libgap_wrapper.pyx b/src/sage/groups/libgap_wrapper.pyx index 33ce1d2a813..d4ad92b6be4 100644 --- a/src/sage/groups/libgap_wrapper.pyx +++ b/src/sage/groups/libgap_wrapper.pyx @@ -59,6 +59,7 @@ AUTHORS: # https://www.gnu.org/licenses/ ############################################################################## +from sage.libs.gap.libgap import libgap from sage.libs.gap.element cimport GapElement from sage.rings.integer import Integer from sage.rings.integer_ring import IntegerRing @@ -261,7 +262,7 @@ class ParentLibGAP(SageObject): def gap(self): """ - Returns the gap representation of self + Return the gap representation of self. OUTPUT: @@ -299,26 +300,6 @@ class ParentLibGAP(SageObject): _libgap_ = _gap_ = gap - @cached_method - def _gap_gens(self): - """ - Return the generators as a LibGAP object - - OUTPUT: - - A :class:`~sage.libs.gap.element.GapElement` - - EXAMPLES:: - - sage: G = FreeGroup(2) - sage: G._gap_gens() - [ x0, x1 ] - sage: type(_) - - """ - return self._libgap.GeneratorsOfGroup() - - @cached_method def ngens(self): """ Return the number of generators of self. @@ -338,11 +319,11 @@ class ParentLibGAP(SageObject): sage: type(G.ngens()) """ - return self._gap_gens().Length().sage() + return Integer(len(self.gens())) def _repr_(self): """ - Return a string representation + Return a string representation. OUTPUT: @@ -357,6 +338,33 @@ class ParentLibGAP(SageObject): """ return self._libgap._repr_() + @cached_method + def gens(self): + """ + Return the generators of the group. + + EXAMPLES:: + + sage: G = FreeGroup(2) + sage: G.gens() + (x0, x1) + sage: H = FreeGroup('a, b, c') + sage: H.gens() + (a, b, c) + + :meth:`generators` is an alias for :meth:`gens` :: + + sage: G = FreeGroup('a, b') + sage: G.generators() + (a, b) + sage: H = FreeGroup(3, 'x') + sage: H.generators() + (x0, x1, x2) + """ + return tuple(self.element_class(self, g) for g in self._libgap.GeneratorsOfGroup()) + + generators = gens + def gen(self, i): """ Return the `i`-th generator of self. @@ -385,40 +393,12 @@ class ParentLibGAP(SageObject): """ if not (0 <= i < self.ngens()): raise ValueError('i must be in range(ngens)') - gap = self._gap_gens()[i] - return self.element_class(self, gap) - - @cached_method - def gens(self): - """ - Returns the generators of the group. - - EXAMPLES:: - - sage: G = FreeGroup(2) - sage: G.gens() - (x0, x1) - sage: H = FreeGroup('a, b, c') - sage: H.gens() - (a, b, c) - - :meth:`generators` is an alias for :meth:`gens` :: - - sage: G = FreeGroup('a, b') - sage: G.generators() - (a, b) - sage: H = FreeGroup(3, 'x') - sage: H.generators() - (x0, x1, x2) - """ - return tuple( self.gen(i) for i in range(self.ngens()) ) - - generators = gens + return self.gens()[i] @cached_method def one(self): """ - Returns the identity element of self + Return the identity element of self. EXAMPLES:: @@ -434,7 +414,7 @@ class ParentLibGAP(SageObject): def _an_element_(self): """ - Returns an element of self. + Return an element of self. EXAMPLES:: @@ -500,7 +480,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): cpdef GapElement gap(self): """ - Returns a LibGAP representation of the element + Return a LibGAP representation of the element. OUTPUT: @@ -517,10 +497,36 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): a*b*a^-1*b^-1 sage: type(xg) + + TESTS:: + + sage: libgap(FreeGroup('a, b').an_element()) + a*b + sage: type(libgap(FreeGroup('a, b').an_element())) + """ return self._libgap - _gap_ = gap + _libgap_ = _gap_ = gap + + def _test_libgap_conversion(self, **options): + r""" + TESTS:: + + sage: FreeGroup(2).an_element()._test_libgap_conversion() + """ + tester = self._tester(**options) + tester.assertTrue(libgap(self) is self.gap()) + + def _test_libgap_reconstruction(self, **options): + r""" + TESTS:: + + sage: FreeGroup(2).an_element()._test_libgap_reconstruction() + """ + tester = self._tester(**options) + P = self.parent() + tester.assertEqual(self, P.element_class(P, libgap(self))) def is_one(self): """ @@ -612,8 +618,9 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): sage: y*x == y._mul_(x) True """ - P = left.parent() - return P.element_class(P, left.gap() * right.gap()) + P = ( left)._parent + return P.element_class(P, ( left)._libgap * \ + ( right)._libgap) cpdef _richcmp_(left, right, int op): """ @@ -653,8 +660,9 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): sage: x/y == y.__truediv__(x) False """ - P = left.parent() - return P.element_class(P, left.gap() / right.gap()) + P = ( left)._parent + return P.element_class(P, ( left)._libgap / \ + ( right)._libgap) def __pow__(self, n, dummy): """ @@ -674,8 +682,8 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): """ if n not in IntegerRing(): raise TypeError("exponent must be an integer") - P = self.parent() - return P.element_class(P, self.gap() ** n) + P = ( self)._parent + return P.element_class(P, ( self)._libgap ** n) def __invert__(self): """ @@ -695,8 +703,87 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): sage: x.inverse() b*a*b^-1*a^-1 """ - P = self.parent() - return P.element_class(P, self.gap().Inverse()) + P = ( self)._parent + return P.element_class(P, ( self)._libgap.Inverse()) inverse = __invert__ + def order(self): + r""" + Return the multiplicative order. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.GL(2, 3)) + sage: a,b = G.gens() + sage: print(a.order()) + 2 + sage: print(a.multiplicative_order()) + 2 + + sage: z = Mod(0, 3) + sage: o = Mod(1, 3) + sage: G(libgap([[o,o],[z,o]])).order() + 3 + """ + return self._libgap.Order().sage() + + multiplicative_order = order + + def is_conjugate(self, other): + r""" + Return whether the elements ``self`` and ``other`` are conjugate. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.GL(2, 3)) + sage: a,b = G.gens() + sage: a.is_conjugate(b) + False + sage: a.is_conjugate((a*b^2) * a * ~(a*b^2)) + True + """ + return libgap.IsConjugate(self.parent(), self, other).sage() + + def normalizer(self): + r""" + Return the normalizer of the cyclic group generated by this element. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.GL(3,3)) + sage: a,b = G.gens() + sage: H = a.normalizer() + sage: H + + sage: H.cardinality() + 96 + sage: all(g*a == a*g for g in H) + True + """ + from sage.groups.libgap_group import GroupLibGAP + P = self.parent() + return GroupLibGAP(libgap.Normalizer(P, self), ambient=P) + + def nth_roots(self, n): + r""" + Return the set of ``n``-th roots of this group element. + + EXAMPLES:: + + sage: from sage.groups.libgap_group import GroupLibGAP + sage: G = GroupLibGAP(libgap.GL(3,3)) + sage: a,b = G.gens() + sage: g = a*b**2*a*~b + sage: r = g.nth_roots(4) + sage: r + [[ [ Z(3), Z(3), Z(3)^0 ], [ Z(3)^0, Z(3)^0, 0*Z(3) ], [ 0*Z(3), Z(3), 0*Z(3) ] ], + [ [ Z(3)^0, Z(3)^0, Z(3) ], [ Z(3), Z(3), 0*Z(3) ], [ 0*Z(3), Z(3)^0, 0*Z(3) ] ]] + sage: r[0]**4 == r[1]**4 == g + True + """ + P = self.parent() + return [P.element_class(P, g) for g in libgap.NthRootsInGroup(P.gap(), self._libgap, n)] diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index 3413c2a6358..90249e2adcf 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -574,8 +574,8 @@ def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None) 24 sage: v[:5] ( - [0 1] [0 1] [0 1] [0 2] [0 2] - [2 0], [2 1], [2 2], [1 0], [1 1] + [1 0] [2 0] [0 1] [0 2] [1 2] + [0 1], [0 2], [2 0], [1 0], [2 2] ) sage: all(g in G for g in G.list()) True @@ -588,12 +588,12 @@ def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None) sage: MG = MatrixGroup([M1, M2, M3]) sage: MG.list() ( - [-1 0] [-1 0] [ 1 0] [1 0] - [ 0 -1], [ 0 1], [ 0 -1], [0 1] + [1 0] [ 1 0] [-1 0] [-1 0] + [0 1], [ 0 -1], [ 0 1], [ 0 -1] ) sage: MG.list()[1] - [-1 0] - [ 0 1] + [ 1 0] + [ 0 -1] sage: MG.list()[1].parent() Matrix group over Integer Ring with 3 generators ( [-1 0] [ 1 0] [-1 0] diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index dbe06b18494..76c093475cf 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -26,6 +26,8 @@ from __future__ import division, absolute_import, print_function import six +from sage.libs.gap.element import GapElement + from sage.misc.cachefunc import cached_method from sage.structure.richcmp import richcmp from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement @@ -387,15 +389,51 @@ def __init__(self, parent, lst): sage: G = RightAngledArtinGroup(Gamma) sage: elt = G.prod(G.gens()) sage: TestSuite(elt).run() + + sage: g = G([[0,-3], [2,2], [3,-1], [2,4]]) + sage: h = G.element_class(G, g.gap()) + sage: assert g.gap() == h.gap() + sage: assert g._data == h._data + + sage: g = G.one() + sage: h = G.element_class(G, g.gap()) + sage: assert g.gap() == h.gap() + sage: assert g._data == h._data """ - self._data = lst - elt = [] - for i, p in lst: - if p > 0: - elt.extend([i + 1] * p) - elif p < 0: - elt.extend([-i - 1] * -p) - FinitelyPresentedGroupElement.__init__(self, parent, elt) + if isinstance(lst, GapElement): + # e.g. direct call from GroupLibGAP + FinitelyPresentedGroupElement.__init__(self, parent, lst) + data = [] + j = None + mult = 0 + for i in self.Tietze(): + if j is None: + j = i + mult = 1 + elif j == i: + mult += 1 + else: + if j < 0: + data.append([-j-1, -mult]) + else: + data.append([j-1, mult]) + j = i + mult = 1 + if j is not None: + if j < 0: + data.append([-j-1, -mult]) + else: + data.append([j-1, mult]) + self._data = tuple(data) + else: + self._data = lst + elt = [] + for i, p in lst: + if p > 0: + elt.extend([i + 1] * p) + elif p < 0: + elt.extend([-i - 1] * -p) + FinitelyPresentedGroupElement.__init__(self, parent, elt) def __reduce__(self): """ diff --git a/src/sage/interfaces/mathematica.py b/src/sage/interfaces/mathematica.py index 58d8f4a2eab..644b0b8ee69 100644 --- a/src/sage/interfaces/mathematica.py +++ b/src/sage/interfaces/mathematica.py @@ -308,7 +308,7 @@ sage: def math_bessel_K(nu,x): ....: return mathematica(nu).BesselK(x).N(20) sage: math_bessel_K(2,I) # optional - mathematica - -2.5928861754911969782 + 0.1804899720669620266 I + -2.59288617549119697817 + 0.18048997206696202663*I :: @@ -339,6 +339,21 @@ - Felix Lawrence (2009-08-21): Added support for importing Mathematica lists and floats with exponents. + +TESTS: + +Check that numerical approximations via Mathematica's `N[]` function work +correctly (:trac:`18888`, :trac:`28907`):: + + sage: mathematica('Pi/2').N(10) # optional -- mathematica + 1.5707963268 + sage: mathematica('Pi').N(10) # optional -- mathematica + 3.1415926536 + sage: mathematica('Pi').N(50) # optional -- mathematica + 3.14159265358979323846264338327950288419716939937511 + sage: str(mathematica('Pi*x^2-1/2').N()) # optional -- mathematica + 2 + -0.5 + 3.14159 x """ #***************************************************************************** @@ -676,7 +691,7 @@ def __float__(self, precision=16): return float(P.eval('N[%s,%s]'%(self.name(),precision))) def _reduce(self): - return self.parent().eval('InputForm[%s]'%self.name()) + return self.parent().eval('InputForm[%s]' % self.name()).strip() def __reduce__(self): return reduce_load, (self._reduce(), ) @@ -1003,37 +1018,6 @@ def __bool__(self): __nonzero__ = __bool__ - def N(self, precision=None): - r""" - Numerical approximation by calling Mathematica's `N[]` - - Calling Mathematica's `N[]` function, with optional precision in decimal digits. - Unlike Sage's `n()`, `N()` can be applied to symbolic Mathematica objects. - - A workaround for :trac:`18888` backtick issue, stripped away by `get()`, - is included. - - .. note:: - - The base class way up the hierarchy defines an `N` (modeled - after Mathematica's) which overwrites the Mathematica one, - and doesn't work at all. We restore it here. - - EXAMPLES:: - - sage: mathematica('Pi/2').N(10) # optional -- mathematica - 1.570796327 - sage: mathematica('Pi').N(50) # optional -- mathematica - 3.1415926535897932384626433832795028841971693993751 - sage: mathematica('Pi*x^2-1/2').N() # optional -- mathematica - 2 - -0.5 + 3.14159 x - """ - P = self.parent() - if precision is None: - return P.eval('N[%s]'%self.name()) - return P.eval('N[%s,%s]'%(self.name(),precision)) - def n(self, *args, **kwargs): r""" Numerical approximation by converting to Sage object first diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 6b38a22a58a..2939d46644b 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -372,8 +372,14 @@ def _setup_r_to_sage_converter(): # expect interface) cv = Converter('r to sage converter') + # support rpy version 2 and 3 + try: + rpy2py = cv.rpy2py + except AttributeError: + rpy2py = cv.ri2py + # fallback - cv.ri2py.register(object, lambda obj: obj) + rpy2py.register(object, lambda obj: obj) def float_to_int_if_possible(f): # First, round the float to at most 15 significant places. @@ -383,7 +389,7 @@ def float_to_int_if_possible(f): # Preserve the behaviour of the old r parser, e.g. return 1 instead of 1.0 float_or_int = int(f) if isinstance(f, int) or f.is_integer() else f return float_or_int - cv.ri2py.register(float, float_to_int_if_possible) + rpy2py.register(float, float_to_int_if_possible) def list_to_singleton_if_possible(l): if len(l) == 1: @@ -395,11 +401,11 @@ def _vector(vec): attrs = vec.list_attrs() # Recursive calls have to be made explicitly # https://bitbucket.org/rpy2/rpy2/issues/363/custom-converters-are-not-applied - data = list_to_singleton_if_possible([ cv.ri2py(val) for val in vec ]) + data = list_to_singleton_if_possible([ rpy2py(val) for val in vec ]) rclass = list(vec.do_slot('class')) if 'class' in attrs else vec.rclass if 'names' in attrs: - # separate names and values, call ri2py recursively to convert elements + # separate names and values, call rpy2py recursively to convert elements names = list_to_singleton_if_possible(list(vec.do_slot('names'))) return { 'DATA': data, @@ -409,7 +415,7 @@ def _vector(vec): else: # if no names are present, convert to a normal list or a single value return data - cv.ri2py.register(SexpVector, _vector) + rpy2py.register(SexpVector, _vector) def _matrix(mat): if 'dim' in mat.list_attrs(): @@ -421,28 +427,28 @@ def _matrix(mat): (nrow, ncol) = dimensions # Since R does it the other way round, we assign transposed and # then transpose the matrix :) - m = matrix(ncol, nrow, [cv.ri2py(i) for i in mat]) + m = matrix(ncol, nrow, [rpy2py(i) for i in mat]) return m.transpose() except TypeError: pass else: return _vector(mat) - cv.ri2py.register(FloatSexpVector, _matrix) + rpy2py.register(FloatSexpVector, _matrix) def _list_vector(vec): # we have a R list (vector of arbitrary elements) attrs = vec.list_attrs() names = vec.do_slot('names') - values = [ cv.ri2py(val) for val in vec ] + values = [ rpy2py(val) for val in vec ] rclass = list(vec.do_slot('class')) if 'class' in attrs else vec.rclass data = zip(names, values) return { 'DATA': dict(data), - '_Names': cv.ri2py(names), + '_Names': rpy2py(names), # We don't give the rclass here because the old expect interface # didn't do that either and we want to maintain compatibility. }; - cv.ri2py.register(ListSexpVector, _list_vector) + rpy2py.register(ListSexpVector, _list_vector) return cv diff --git a/src/sage/libs/singular/polynomial.pyx b/src/sage/libs/singular/polynomial.pyx index 3f625387ff8..7687de8f2d8 100644 --- a/src/sage/libs/singular/polynomial.pyx +++ b/src/sage/libs/singular/polynomial.pyx @@ -29,7 +29,7 @@ from sage.cpython.string cimport bytes_to_str, str_to_bytes from sage.libs.singular.decl cimport number, ideal from sage.libs.singular.decl cimport currRing, rChangeCurrRing from sage.libs.singular.decl cimport p_Copy, p_Add_q, p_Neg, pp_Mult_nn, p_GetCoeff, p_IsConstant, p_Cmp, pNext -from sage.libs.singular.decl cimport p_GetMaxExp, pp_Mult_qq, pPower, p_String, p_GetExp, p_Deg, p_Totaldegree, p_WTotaldegree, p_WDegree +from sage.libs.singular.decl cimport p_GetMaxExp, pp_Mult_qq, pPower, p_String, p_GetExp, p_LDeg from sage.libs.singular.decl cimport n_Delete, idInit, fast_map_common_subexp, id_Delete from sage.libs.singular.decl cimport omAlloc0, omStrDup, omFree from sage.libs.singular.decl cimport p_GetComp, p_SetComp @@ -551,21 +551,16 @@ cdef object singular_polynomial_str_with_changed_varnames(poly *p, ring *r, obje cdef long singular_polynomial_deg(poly *p, poly *x, ring *r): cdef long _deg, deg + cdef int dummy deg = -1 - _deg = -1 + _deg = -1 if p == NULL: return -1 if r != currRing: rChangeCurrRing(r) if x == NULL: - while p: - _deg = p_WDegree(p,r) - - if _deg > deg: - deg = _deg - p = pNext(p) - return deg + return p_LDeg(p, &dummy, r) cdef int i = 0 for i in range(1,r.N+1): diff --git a/src/sage/manifolds/differentiable/automorphismfield.py b/src/sage/manifolds/differentiable/automorphismfield.py index 157ccd421f0..0ee55d75c8c 100644 --- a/src/sage/manifolds/differentiable/automorphismfield.py +++ b/src/sage/manifolds/differentiable/automorphismfield.py @@ -600,14 +600,22 @@ def __invert__(self): if self._is_identity: return self if self._inverse is None: + from sage.tensor.modules.format_utilities import is_atomic if self._name is None: inv_name = None else: - inv_name = self._name + '^(-1)' + if is_atomic(self._name, ['*']): + inv_name = self._name + '^(-1)' + else: + inv_name = '(' + self._name + ')^(-1)' if self._latex_name is None: inv_latex_name = None else: - inv_latex_name = self._latex_name + r'^{-1}' + if is_atomic(self._latex_name, ['\\circ', '\\otimes']): + inv_latex_name = self._latex_name + r'^{-1}' + else: + inv_latex_name = r'\left(' + self._latex_name + \ + r'\right)^{-1}' self._inverse = self._vmodule.automorphism(name=inv_name, latex_name=inv_latex_name) for dom, rst in self._restrictions.items(): @@ -1154,14 +1162,22 @@ def __invert__(self): if self._is_identity: return self if self._inverse is None: + from sage.tensor.modules.format_utilities import is_atomic if self._name is None: inv_name = None else: - inv_name = self._name + '^(-1)' + if is_atomic(self._name, ['*']): + inv_name = self._name + '^(-1)' + else: + inv_name = '(' + self._name + ')^(-1)' if self._latex_name is None: inv_latex_name = None else: - inv_latex_name = self._latex_name + r'^{-1}' + if is_atomic(self._latex_name, ['\\circ', '\\otimes']): + inv_latex_name = self._latex_name + r'^{-1}' + else: + inv_latex_name = r'\left(' + self._latex_name + \ + r'\right)^{-1}' fmodule = self._fmodule si = fmodule._sindex ; nsi = fmodule._rank + si self._inverse = fmodule.automorphism(name=inv_name, diff --git a/src/sage/manifolds/differentiable/mixed_form.py b/src/sage/manifolds/differentiable/mixed_form.py index 3cf6ff50f81..4fabf3111ed 100644 --- a/src/sage/manifolds/differentiable/mixed_form.py +++ b/src/sage/manifolds/differentiable/mixed_form.py @@ -874,7 +874,7 @@ def _lmul_(self, other): INPUT: - - ``num`` -- an element of the symbolic ring + - ``other`` -- an element of the symbolic ring OUTPUT: @@ -896,6 +896,11 @@ def _lmul_(self, other): y/\(x/\F) = [0] + [x^2*y^2 dx] + [0] """ + # Simple checks: + if other.is_trivial_zero(): + return self.parent().zero() + elif (other - 1).is_trivial_zero(): + return self resu = self._new_instance() resu[:] = [other * form for form in self._comp] # Compose name: diff --git a/src/sage/manifolds/differentiable/mixed_form_algebra.py b/src/sage/manifolds/differentiable/mixed_form_algebra.py index 5aaab8e08c8..f8e5a524781 100644 --- a/src/sage/manifolds/differentiable/mixed_form_algebra.py +++ b/src/sage/manifolds/differentiable/mixed_form_algebra.py @@ -197,33 +197,28 @@ def _element_constructor_(self, comp=None, name=None, latex_name=None): return self.zero() elif comp in ZZ and comp == 1: return self.one() - elif isinstance(comp, tuple): - comp_list = list(comp) - if len(comp_list) != self._max_deg + 1: - raise IndexError( "input list must have" - " length {}".format(self._max_deg + 1)) - res[:] = comp_list - elif isinstance(comp, list): + elif isinstance(comp, (tuple, list)): if len(comp) != self._max_deg + 1: - raise IndexError( "input list must have" - " length {}".format(self._max_deg + 1)) - res[:] = comp + raise IndexError("input list must have " + "length {}".format(self._max_deg + 1)) + if isinstance(comp, tuple): + comp = list(comp) + res[:] = comp[:] elif isinstance(comp, self.Element): res[:] = comp[:] else: - ### - # Now, comp seems to be a differential form: - try: - deg = comp.degree() - except (AttributeError, NotImplementedError): - # No degree method? Perhaps the degree is zero? - deg = 0 - + for d in self.irange(): + dmodule = self._domain.diff_form_module(d, dest_map=self._dest_map) + if dmodule.has_coerce_map_from(comp.parent()): + deg = d + break + else: + raise TypeError("cannot convert {} into an element of " + "the {}".format(comp, self)) res[:] = [0] * (self._max_deg + 1) # fill up with zeroes... - res[deg] = comp # ...and set comp at deg of res - ### - # In case, no other name is given, use name of comp for better - # coercion: + res[deg] = comp # ...and set comp at deg of res; + # the coercion is performed here + # In case, no other name is given, use name of comp: if name is None: if hasattr(comp, '_name'): res._name = comp._name @@ -269,6 +264,8 @@ def _coerce_map_from_(self, S): True sage: A._coerce_map_from_(M.diff_form_module(3)) True + sage: A._coerce_map_from_(M.tensor_field_module((0,1))) + True sage: U = M.open_subset('U') sage: AU = U.mixed_form_algebra() sage: AU._coerce_map_from_(A) @@ -277,21 +274,26 @@ def _coerce_map_from_(self, S): False """ - if isinstance(S, self.__class__): + if isinstance(S, type(self)): # coercion by domain restriction - return (self._domain.is_subset(S._domain) and - self._ambient_domain.is_subset(S._ambient_domain)) - # Test scalar_field_algebra separately to ensure coercion from SR: - if self._domain.scalar_field_algebra().has_coerce_map_from(S): - return True - # This is tricky, we need to check the degree first: - try: - deg = S.degree() - if self._domain.diff_form_module(deg, self._dest_map).has_coerce_map_from(S): + if (self._domain.is_subset(S._domain) and + self._ambient_domain.is_subset(S._ambient_domain)): return True - except (NotImplementedError, AttributeError, TypeError): - pass - return False + # Still, there could be a coerce map + if self.irange() != S.irange(): + return False + # Check coercions on each degree: + for deg in self.irange(): + dmodule1 = self._domain.diff_form_module(deg, self._dest_map) + dmodule2 = S._domain.diff_form_module(deg, S._dest_map) + if not dmodule1.has_coerce_map_from(dmodule2): + return False + # Each degree is coercible so there must be a coerce map: + return True + # Let us check for each degree consecutively: + return any(self._domain.diff_form_module(deg, + self._dest_map).has_coerce_map_from(S) + for deg in self.irange()) @cached_method def zero(self): @@ -369,19 +371,18 @@ def _repr_(self): 3-dimensional differentiable manifold M """ - description = ("Graded algebra " + self._name + - " of mixed differential forms ") + desc = ("Graded algebra " + self._name + " of mixed differential forms ") if self._dest_map is self._domain.identity_map(): - description += "on the {}".format(self._domain) + desc += "on the {}".format(self._domain) else: - description += "along the {} mapped into the {} ".format( - self._domain, self._ambient_domain) + desc += "along the {} mapped into the {} ".format(self._domain, + self._ambient_domain) if self._dest_map._name is None: dm_name = "unnamed map" else: dm_name = self._dest_map._name - description += "via " + dm_name - return description + desc += "via " + dm_name + return desc def _latex_(self): r""" diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 09bf880522b..c33a3eace7f 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -2254,12 +2254,23 @@ def set_calculus_method(self, method): F: M --> R (x, y) |--> x^2 + cos(y)*sin(x) + The calculus method chosen via ``set_calculus_method()`` applies to any + chart defined subsequently on the manifold:: + + sage: M.set_calculus_method('sympy') + sage: Y. = M.chart() # a new chart + sage: Y.calculus_method() + Available calculus methods (* = current): + - SR (default) + - sympy (*) + .. SEEALSO:: :meth:`~sage.manifolds.chart.Chart.calculus_method` for a control of the calculus method chart by chart """ + self._calculus_method = method for chart in self._atlas: chart.calculus_method().set(method) diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index e9da77cc2c4..a163a7006c7 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -982,11 +982,10 @@ cdef class Matrix(sage.structure.element.Matrix): if single_col: col_list = [col] - if len(row_list) == 0 or len(col_list) == 0: + if not row_list or not col_list: return self.new_matrix(nrows=len(row_list), ncols=len(col_list)) - return self.matrix_from_rows_and_columns(row_list,col_list) - + return self.matrix_from_rows_and_columns(row_list, col_list) row_index = key if type(row_index) is list or type(row_index) is tuple: diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 2928107b28d..211084b90c6 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -9502,7 +9502,7 @@ cdef class Matrix(Matrix1): R[nnz, i] = 1 nnz = nnz + 1 R = R[0:nnz] - if Bstar == []: + if not Bstar: Q = matrix(F, 0, self.nrows()).transpose() else: Q = matrix(F, Bstar).transpose() diff --git a/src/sage/matrix/matrix_gap.pxd b/src/sage/matrix/matrix_gap.pxd index d61f6918f7e..0667c158df8 100644 --- a/src/sage/matrix/matrix_gap.pxd +++ b/src/sage/matrix/matrix_gap.pxd @@ -1,6 +1,5 @@ from .matrix_dense cimport Matrix_dense from sage.libs.gap.element cimport GapElement -from sage.groups.libgap_wrapper cimport ElementLibGAP cdef class Matrix_gap(Matrix_dense): cdef GapElement _libgap diff --git a/src/sage/matrix/matrix_generic_sparse.pyx b/src/sage/matrix/matrix_generic_sparse.pyx index caac6abe66d..bbf0015ca8a 100644 --- a/src/sage/matrix/matrix_generic_sparse.pyx +++ b/src/sage/matrix/matrix_generic_sparse.pyx @@ -410,8 +410,8 @@ def Matrix_sparse_from_rows(X): cdef Py_ssize_t i, j if not isinstance(X, (list, tuple)): - raise TypeError("X (=%s) must be a list or tuple"%X) - if len(X) == 0: + raise TypeError("X (=%s) must be a list or tuple" % X) + if not X: raise ArithmeticError("X must be nonempty") from . import matrix_space diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 3d5a70d24a7..27234d67639 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -5427,7 +5427,7 @@ cdef class Matrix_integer_dense(Matrix_dense): [ 1 0 3] [-1 0 5] """ - if len(cols) == 0: + if not cols: return self cdef Py_ssize_t i, c, r, nc = max(self._ncols + len(cols), max(cols)+1) cdef Matrix_integer_dense A = self.new_matrix(self._nrows, nc) diff --git a/src/sage/matrix/matrix_integer_dense_hnf.py b/src/sage/matrix/matrix_integer_dense_hnf.py index 18415cb4a95..64ba747a511 100644 --- a/src/sage/matrix/matrix_integer_dense_hnf.py +++ b/src/sage/matrix/matrix_integer_dense_hnf.py @@ -1084,7 +1084,7 @@ def hnf(A, include_zero_rows=True, proof=True): """ if A.nrows() <= 1: np = A.nonzero_positions() - if len(np) == 0: + if not np: pivots = [] if not include_zero_rows: A = A.new_matrix(0) # 0 rows diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index bc4adf081dc..6d6c2bdcf39 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -825,7 +825,7 @@ def diagonal_matrix(arg0=None, arg1=None, arg2=None, sparse=True): nrows = nentries # provide a default ring for an empty list - if len(entries) == 0 and ring is None: + if not len(entries) and ring is None: ring = rings.ZZ # Convert entries to a list v over a common ring @@ -1614,7 +1614,7 @@ def _determine_block_matrix_rows(sub_matrices): raise ValueError("incompatible submatrix heights") elif not M: found_zeroes = True - if len(R) == 0: + if not R: height = 0 # If we have a height, then we know the dimensions of any @@ -1882,7 +1882,7 @@ def block_matrix(*args, **kwds): args = list(args) sparse = kwds.get('sparse', None) - if len(args) == 0: + if not args: if sparse is not None: return matrix_space.MatrixSpace(rings.ZZ, 0, 0, sparse=sparse)([]) else: @@ -1925,7 +1925,7 @@ def block_matrix(*args, **kwds): # Now the rest of the arguments are a list of rows, a flat list of # matrices, or a single value. - if len(args) == 0: + if not args: args = [[]] if len(args) > 1: print(args) @@ -1953,7 +1953,7 @@ def block_matrix(*args, **kwds): # Will we try to place the matrices in a rectangular grid? try_grid = True - if len(sub_matrices) == 0: + if not sub_matrices: if (nrows is not None and nrows != 0) or \ (ncols is not None and ncols != 0): raise ValueError("invalid nrows/ncols passed to block_matrix") @@ -3247,7 +3247,7 @@ def random_diagonalizable_matrix(parent,eigenvalues=None,dimensions=None): raise ValueError("the size of the matrix must equal the sum of the dimensions.") if min(dimensions) < 1: raise ValueError("eigenspaces must have a dimension of at least 1.") - if len(eigenvalues)!=len(dimensions): + if len(eigenvalues) != len(dimensions): raise ValueError("each eigenvalue must have a corresponding dimension and each dimension a corresponding eigenvalue.") #sort the dimensions in order of increasing size, and sort the eigenvalues list in an identical fashion, to maintain corresponding values. dimensions_sort = sorted(zip(dimensions, eigenvalues)) diff --git a/src/sage/modular/all.py b/src/sage/modular/all.py index dd2ead1d705..5dfa44415a7 100644 --- a/src/sage/modular/all.py +++ b/src/sage/modular/all.py @@ -41,9 +41,6 @@ from .etaproducts import (EtaGroup, EtaProduct, EtaGroupElement, AllCusps, CuspFamily) -lazy_import("sage.modular.etaproducts", ['num_cusps_of_width', 'qexp_eta', - 'eta_poly_relations'], - deprecation=26196) from .overconvergent.all import * diff --git a/src/sage/modular/hypergeometric_misc.pxd b/src/sage/modular/hypergeometric_misc.pxd new file mode 100644 index 00000000000..b601a29db24 --- /dev/null +++ b/src/sage/modular/hypergeometric_misc.pxd @@ -0,0 +1,4 @@ +from cpython cimport array + +cpdef hgm_coeffs(long long p, int f, int prec, gamma, array.array m, int D, + gtable, int gtable_prec, bint use_longs) diff --git a/src/sage/modular/hypergeometric_misc.pyx b/src/sage/modular/hypergeometric_misc.pyx new file mode 100644 index 00000000000..df44371d263 --- /dev/null +++ b/src/sage/modular/hypergeometric_misc.pyx @@ -0,0 +1,111 @@ +""" +Some utility routines for the hypergeometric motives package that benefit +significantly from Cythonization. +""" +cpdef hgm_coeffs(long long p, int f, int prec, gamma, array.array m, int D, + gtable, int gtable_prec, bint use_longs): + r""" + Compute coefficients for the hypergeometric trace formula. + + This function is not intended for direct user access. + + TESTS:: + + sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp + sage: import array + sage: from sage.modular.hypergeometric_misc import hgm_coeffs + sage: H = Hyp(cyclotomic=([3],[4])) + sage: H.euler_factor(2, 7, cache_p=True) + 7*T^2 - 3*T + 1 + sage: gamma = H.gamma_array() + sage: prec, gtable = H.gauss_table(7, 1, 2) + sage: m = array.array('i', [0]*6) + sage: D = 1 + sage: hgm_coeffs(7, 1, 2, gamma, m, D, gtable, prec, False) + [7, 2*7, 6*7, 7, 6, 4*7] + """ + from sage.rings.padics.factory import Zp + + cdef int gl, j, k, l, v, gv + cdef long long i, q1, w, w1, w2, q2, r, r1 + cdef bint flip + + q1 = p ** f - 1 + gl = len(gamma) + cdef array.array gamma_array1 = array.array('i', gamma.keys()) + cdef array.array gamma_array2 = array.array('i', gamma.values()) + cdef array.array r_array = array.array('i', [0]) * gl + cdef array.array digit_count = array.array('i', [0]) * q1 + cdef array.array gtab2 + + try: + R = gtable[0].parent() + except AttributeError: + R = Zp(p, prec, "fixed-mod") + # In certain cases, the reciprocals of the Gauss sums are reported + # for efficiency. + flip = (f == 1 and prec == 1 and gtable_prec == 1) + ans = [] + if use_longs: + q2 = p ** prec + try: + gtab2 = gtable + except TypeError: + gtab2 = array.array('l', [0]) * q1 + for r in range(q1): + gtab2[r] = gtable[r].lift() % q2 + if f == 1: + for r in range(q1): + digit_count[r] = r + else: + for r in range(q1): + r1 = r + digit_count[r] = 0 + for i in range(f): + digit_count[r] += r1 % p + r1 //= p + Rz = R.zero() + for r in range(q1): + # First determine whether this term is forced to be zero + # for divisibility reasons. If so, skip the p-adic arithmetic. + i = 0 + for k in range(gl): + v = gamma_array1[k] + gv = gamma_array2[k] + r1 = v * r % q1 + r_array[k] = r1 + i += digit_count[r1] * gv + i //= (p - 1) + l = i + f * (D + m[0] - m[r]) + if l >= prec: + ans.append(Rz) + continue + # Keep numerator and denominator separate for efficiency. + if use_longs: + w = 1 + w1 = 1 + else: + u = R.one() + u1 = R.one() + for k in range(gl): + gv = gamma_array2[k] + r1 = r_array[k] + if flip: + gv = -gv + if use_longs: + w2 = gtab2[r1] # cast to long long to avoid overflow + if gv > 0: + for j in range(gv): w = w * w2 % q2 + else: + for j in range(-gv): w1 = w1 * w2 % q2 + else: + if gv > 0: + for j in range(gv): u *= gtable[r1] + else: + for j in range(-gv): u1 *= gtable[r1] + if use_longs: + u = R(w) + u1 = R(w1) + if i % 2: u = -u + ans.append((u / u1) << l) + return ans diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index 4234279b41e..645c3a3542e 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -72,11 +72,11 @@ from sage.misc.cachefunc import cached_method from sage.misc.functional import cyclotomic_polynomial from sage.misc.misc_c import prod +from sage.modular.hypergeometric_misc import hgm_coeffs from sage.rings.fraction_field import FractionField from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.integer_ring import ZZ -from sage.rings.padics.factory import Qp -from sage.rings.padics.misc import gauss_sum as padic_gauss_sum +from sage.rings.padics.padic_generic_element import gauss_table from sage.rings.polynomial.polynomial_ring import polygen from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.power_series_ring import PowerSeriesRing @@ -85,7 +85,6 @@ from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField - def characteristic_polynomial_from_traces(traces, d, q, i, sign): r""" Given a sequence of traces `t_1, \dots, t_k`, return the @@ -396,6 +395,8 @@ def gamma_list_to_cyclotomic(galist): class HypergeometricData(object): + _gauss_table = {} + def __init__(self, cyclotomic=None, alpha_beta=None, gamma_list=None): r""" Creation of hypergeometric motives. @@ -467,6 +468,7 @@ def __init__(self, cyclotomic=None, alpha_beta=None, gamma_list=None): self._beta = tuple(beta) self._deg = deg self._gamma_array = cyclotomic_to_gamma(cyclo_up, cyclo_down) + self._trace_coeffs = {} up = QQ.prod(capital_M(d) for d in cyclo_up) down = QQ.prod(capital_M(d) for d in cyclo_down) self._M_value = up / down @@ -1063,7 +1065,7 @@ def primitive_data(self): .. SEEALSO:: - :meth:`is_primitive`, :meth:`primitive_index`, + :meth:`is_primitive`, :meth:`primitive_index` EXAMPLES:: @@ -1077,9 +1079,71 @@ def primitive_data(self): d = gcd(g) return HypergeometricData(gamma_list=[x / d for x in g]) +### L-functions + + def gauss_table(self, p, f, prec): + """ + Return (and cache) a table of Gauss sums used in the trace formula. + + .. SEEALSO:: + + :meth:`gauss_table_full` + + EXAMPLES:: + + sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp + sage: H = Hyp(cyclotomic=([3],[4])) + sage: H.gauss_table(2, 2, 4) + (4, [1 + 2 + 2^2 + 2^3, 1 + 2 + 2^2 + 2^3, 1 + 2 + 2^2 + 2^3]) + """ + try: + prec1, gtab = self._gauss_table[p, f] + if prec1 < prec: raise KeyError + except KeyError: + use_longs = (p ** prec < 2 ** 31) + gtab = gauss_table(p, f, prec, use_longs) + self._gauss_table[p, f] = (prec, gtab) + prec1 = prec + return prec1, gtab + + def gauss_table_full(self): + """ + Return a dict of all stored tables of Gauss sums. + + The result is passed by reference, and is an attribute of the class; + consequently, modifying the result has global side effects. Use with + caution. + + .. SEEALSO:: + + :meth:`gauss_table` + + EXAMPLES:: + + sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp + sage: H = Hyp(cyclotomic=([3],[4])) + sage: H.euler_factor(2, 7, cache_p=True) + 7*T^2 - 3*T + 1 + sage: H.gauss_table_full()[(7, 1)] + (2, array('l', [-1, -29, -25, -48, -47, -22])) + + Clearing cached values:: + + sage: H = Hyp(cyclotomic=([3],[4])) + sage: H.euler_factor(2, 7, cache_p=True) + 7*T^2 - 3*T + 1 + sage: d = H.gauss_table_full() + sage: d.clear() # Delete all entries of this dict + sage: H1 = Hyp(cyclotomic=([5],[12])) + sage: d1 = H1.gauss_table_full() + sage: len(d1.keys()) # No cached values + 0 + """ + return self._gauss_table + # --- L-functions --- @cached_method - def padic_H_value(self, p, f, t, prec=None): + def padic_H_value(self, p, f, t, prec=None, cache_p=False): """ Return the `p`-adic trace of Frobenius, computed using the Gross-Koblitz formula. @@ -1087,6 +1151,10 @@ def padic_H_value(self, p, f, t, prec=None): If left unspecified, `prec` is set to the minimum `p`-adic precision needed to recover the Euler factor. + If `cache_p` is True, then the function caches an intermediate + result which depends only on `p` and `f`. This leads to a significant + speedup when iterating over `t`. + INPUT: - `p` -- a prime number @@ -1097,6 +1165,8 @@ def padic_H_value(self, p, f, t, prec=None): - ``prec`` -- precision (optional) + - ``cache_p`` - a boolean + OUTPUT: an integer @@ -1123,6 +1193,8 @@ def padic_H_value(self, p, f, t, prec=None): sage: H.padic_H_value(13,1,1/t) 0 + TESTS: + Check issue from :trac:`28404`:: sage: H1 = Hyp(cyclotomic=([1,1,1],[6,2])) @@ -1132,6 +1204,12 @@ def padic_H_value(self, p, f, t, prec=None): sage: [H2.padic_H_value(5,1,i) for i in range(2,5)] [-4, 1, -4] + Check for potential overflow:: + + sage: H = Hyp(cyclotomic=[[10,6],[5,4]]) + sage: H.padic_H_value(101, 2, 2) + -1560629 + REFERENCES: - [MagmaHGM]_ @@ -1141,48 +1219,39 @@ def padic_H_value(self, p, f, t, prec=None): t = QQ(t) if 0 in alpha: return self._swap.padic_H_value(p, f, ~t, prec) - gamma = self.gamma_array() - q = p**f + q = p ** f + if q > 2 ** 31: + return ValueError("p^f cannot exceed 2^31") - # m = {r: beta.count(QQ((r, q - 1))) for r in range(q - 1)} - m = array.array('i', [0] * (q - 1)) + m = array.array('i', [0]) * int(q - 1) for b in beta: u = b * (q - 1) - if u.is_integer(): - m[u] += 1 + if u.is_integer(): m[u] += 1 M = self.M_value() D = -min(self.zigzag(x, flip_beta=True) for x in alpha + beta) # also: D = (self.weight() + 1 - m[0]) // 2 if prec is None: - prec = (self.weight() * f) // 2 + ceil(log(self.degree(), p)) + 1 - # For some reason, working in Qp instead of Zp is much faster; - # it appears to avoid some costly conversions. - p_ring = Qp(p, prec=prec) - teich = p_ring.teichmuller(M / t) - - gauss_table = [None] * (q - 1) - for r in range(q - 1): - if gauss_table[r] is None: - gauss_table[r] = padic_gauss_sum(r, p, f, prec, factored=True, - algorithm='sage', parent=p_ring) - r1 = (r * p) % (q - 1) - while r1 != r: - gauss_table[r1] = gauss_table[r] - r1 = (r1 * p) % (q - 1) - - sigma = p_ring.zero() - u1 = p_ring.one() - for r in range(q - 1): - i = int(0) - u = u1 - u1 *= teich - for v, gv in gamma.items(): - r1 = (v * r) % (q - 1) - i += gauss_table[r1][0] * gv - u *= gauss_table[r1][1] ** gv - sigma += (-p)**(i // (p - 1)) * u << (f * (D + m[0] - m[r])) - resu = ZZ(-1) ** m[0] / (1 - q) * sigma + prec = ceil((self.weight() * f) / 2 + log(2*self.degree()+1, p)) + use_longs = (p ** prec < 2 ** 31) + + gamma = self._gamma_array + if cache_p: + try: + trcoeffs = self._trace_coeffs[p, f] + except KeyError: + gtab_prec, gtab = self.gauss_table(p, f, prec) + trcoeffs = hgm_coeffs(p, f, prec, gamma, m, D, gtab, gtab_prec, use_longs) + self._trace_coeffs[p, f] = trcoeffs + else: + gtab = gauss_table(p, f, prec, use_longs) + trcoeffs = hgm_coeffs(p, f, prec, gamma, m, D, gtab, prec, use_longs) + sigma = trcoeffs[q-2] + p_ring = sigma.parent() + teich = p_ring.teichmuller(M/t) + for i in range(q-3, -1, -1): + sigma = sigma * teich + trcoeffs[i] + resu = ZZ(-1) ** m[0] * sigma / (1 - q) return IntegerModRing(p**prec)(resu).lift_centered() @cached_method @@ -1334,7 +1403,7 @@ def sign(self, t, p): return sign @cached_method - def euler_factor(self, t, p): + def euler_factor(self, t, p, cache_p=False): """ Return the Euler factor of the motive `H_t` at prime `p`. @@ -1395,6 +1464,14 @@ def euler_factor(self, t, p): 279841*T^4 - 25392*T^3 + 1242*T^2 - 48*T + 1, 707281*T^4 - 7569*T^3 + 696*T^2 - 9*T + 1] + This is an example of higher degree:: + + sage: H = Hyp(cyclotomic=([11], [7, 12])) + sage: H.euler_factor(2, 13) + 371293*T^10 - 85683*T^9 + 26364*T^8 + 1352*T^7 - 65*T^6 + 394*T^5 - 5*T^4 + 8*T^3 + 12*T^2 - 3*T + 1 + sage: H.euler_factor(2, 19) # long time + 2476099*T^10 - 651605*T^9 + 233206*T^8 - 77254*T^7 + 20349*T^6 - 4611*T^5 + 1071*T^4 - 214*T^3 + 34*T^2 - 5*T + 1 + TESTS:: sage: H1 = Hyp(alpha_beta=([1,1,1],[1/2,1/2,1/2])) @@ -1407,6 +1484,15 @@ def euler_factor(self, t, p): sage: H.euler_factor(5,7) 16807*T^5 - 686*T^4 - 105*T^3 - 15*T^2 - 2*T + 1 + Check for precision downsampling:: + + sage: H = Hyp(cyclotomic=[[3],[4]]) + sage: H.euler_factor(2, 11, cache_p=True) + 11*T^2 - 3*T + 1 + sage: H = Hyp(cyclotomic=[[12],[1,2,6]]) + sage: H.euler_factor(2, 11, cache_p=True) + -T^4 + T^3 - T + 1 + REFERENCES: - [Roberts2015]_ @@ -1427,7 +1513,8 @@ def euler_factor(self, t, p): # now p is good d = self.degree() bound = d // 2 - traces = [self.padic_H_value(p, i + 1, t) for i in range(bound)] + traces = [self.padic_H_value(p, i + 1, t, cache_p=cache_p) + for i in range(bound)] w = self.weight() sign = self.sign(t, p) diff --git a/src/sage/modular/pollack_stevens/padic_lseries.py b/src/sage/modular/pollack_stevens/padic_lseries.py index 02a21602c03..b477ff659d8 100644 --- a/src/sage/modular/pollack_stevens/padic_lseries.py +++ b/src/sage/modular/pollack_stevens/padic_lseries.py @@ -3,9 +3,10 @@ `p`-adic `L`-series attached to overconvergent eigensymbols An overconvergent eigensymbol gives rise to a `p`-adic `L`-series, -which is essentially defined as the evaluation of the eigensymbol at the -path `0 \rightarrow \infty`. The resulting distribution on `\ZZ_p` can be restricted -to `\ZZ_p^\times`, thus giving the measure attached to the sought `p`-adic `L`-series. +which is essentially defined as the evaluation of the eigensymbol at +the path `0 \rightarrow \infty`. The resulting distribution on `\ZZ_p` +can be restricted to `\ZZ_p^\times`, thus giving the measure attached +to the sought `p`-adic `L`-series. All this is carefully explained in [PS2011]_. @@ -26,7 +27,6 @@ from sage.arith.all import binomial, kronecker from sage.rings.padics.precision_error import PrecisionError from sage.structure.sage_object import SageObject -from sage.misc.superseded import deprecation class pAdicLseries(SageObject): @@ -36,10 +36,12 @@ class pAdicLseries(SageObject): INPUT: - ``symb`` -- an overconvergent eigensymbol - - ``gamma`` -- topological generator of `1 + p\ZZ_p` (default: `1+p` or 5 if `p=2`) + - ``gamma`` -- topological generator of `1 + p\ZZ_p` + (default: `1+p` or 5 if `p=2`) - ``quadratic_twist`` -- conductor of quadratic twist `\chi` (default: 1) - - ``precision`` -- if ``None`` (default) is specified, the correct precision bound is - computed and the answer is returned modulo that accuracy + - ``precision`` -- if ``None`` (default) is specified, + the correct precision bound is computed and the answer + is returned modulo that accuracy EXAMPLES:: @@ -132,8 +134,7 @@ def __getitem__(self, n): O(7^5) sage: L[1] # long time 5 + 5*7 + 2*7^2 + 2*7^3 + O(7^4) - """ - + """ if n in self._coefficients: return self._coefficients[n] else: @@ -208,7 +209,8 @@ def symbol(self): sage: Phi = phi.p_stabilize_and_lift(2,5) # long time sage: L = pAdicLseries(Phi) # long time sage: L.symbol() # long time - Modular symbol of level 42 with values in Space of 2-adic distributions with k=0 action and precision cap 15 + Modular symbol of level 42 with values in Space of 2-adic + distributions with k=0 action and precision cap 15 sage: L.symbol() is Phi # long time True """ @@ -252,7 +254,8 @@ def _repr_(self): sage: E = EllipticCurve('14a2') sage: L = E.padic_lseries(3, implementation="pollackstevens", precision=4) # long time sage: L._repr_() # long time - '3-adic L-series of Modular symbol of level 42 with values in Space of 3-adic distributions with k=0 action and precision cap 8' + '3-adic L-series of Modular symbol of level 42 with values in + Space of 3-adic distributions with k=0 action and precision cap 8' """ return "%s-adic L-series of %s" % (self.prime(), self.symbol()) @@ -375,7 +378,7 @@ def _basic_integral(self, a, j): symb_twisted.moment(r) for r in range(j + 1)) / ap -def log_gamma_binomial(p, gamma, n, M, old=None): +def log_gamma_binomial(p, gamma, n, M): r""" Return the list of coefficients in the power series expansion (up to precision `M`) of `\binom{\log_p(z)/\log_p(\gamma)}{n}` @@ -399,22 +402,9 @@ def log_gamma_binomial(p, gamma, n, M, old=None): [0, -3/205, 651/84050, -223/42025] sage: log_gamma_binomial(5,1+5,3,4) [0, 2/205, -223/42025, 95228/25845375] - - TESTS:: - - sage: z = polygen(QQ, 'z') - sage: log_gamma_binomial(5,1+5,z,2,4) - doctest:...: DeprecationWarning: the parameter z is ignored and deprecated - See https://trac.sagemath.org/26096 for details. - [0, -3/205, 651/84050, -223/42025] """ - if old is not None: - deprecation(26096, 'the parameter z is ignored and deprecated') - # old deprecated order for the parameters - _, n, M = n, M, old - S = PowerSeriesRing(QQ, 'z') - L = S([0] + [ZZ(-1) ** j / j for j in range(1, M)]) # log_p(1+z) + L = S([0] + [ZZ(-1)**j / j for j in range(1, M)]) # log_p(1+z) loggam = L.O(M) / L(gamma - 1) # log_{gamma}(1+z)= log_p(1+z)/log_p(gamma) return binomial(loggam, n).list() diff --git a/src/sage/modules/free_quadratic_module_integer_symmetric.py b/src/sage/modules/free_quadratic_module_integer_symmetric.py index 469d7be6747..94a55520b3f 100644 --- a/src/sage/modules/free_quadratic_module_integer_symmetric.py +++ b/src/sage/modules/free_quadratic_module_integer_symmetric.py @@ -1004,6 +1004,130 @@ def overlattice(self, gens): inner_product_matrix=self.inner_product_matrix(), already_echelonized=False) + def maximal_overlattice(self, p=None): + r""" + Return a maximal even integral overlattice of this lattice. + + INPUT: + + - ``p`` -- (default:``None``) if given return an overlattice + `M` of this lattice `L` that is maximal at `p` and the + completions `M_q = L_q` are equal for all primes `q \neq p`. + + If `p` is `2` or ``None``, then the lattice must be even. + + EXAMPLES:: + + sage: L = IntegralLattice("A4").twist(25*89) + sage: L.maximal_overlattice().determinant() + 5 + sage: L.maximal_overlattice(89).determinant().factor() + 5^9 + sage: L.maximal_overlattice(5).determinant().factor() + 5 * 89^4 + + TESTS:: + + sage: L = IntegralLattice(matrix.diagonal([2,4,4,8])) + sage: L.maximal_overlattice().is_even() + True + + """ + # this code is somewhat slow but it works + # it might speed up things to use the algorithms given in + # https://arxiv.org/abs/1208.2481 + # and trac:11940 + if not self.is_even() and (p is None or p==2): + raise ValueError("This lattice must be even to admit an even overlattice") + from sage.rings.all import GF + L = self + if p is None: + P = ZZ(self.determinant()).prime_factors() + else: + P = ZZ(p).prime_factors() + # even case + if 2 in P: + # create an isotropic subspace by rescaling + # the generators + D = L.discriminant_group(2) + isotropic = [] + for b in D.gens(): + e = b.additive_order() + v = e.valuation(2) + q = b.q().lift() + delta = (q*e) % 2 + b = 2**(((e.valuation(2)+1)//2) + delta) * b.lift() + isotropic.append(b) + L = L.overlattice(isotropic) + D = L.discriminant_group() + # now L is of exponent at most 4. + if D.cardinality() > 512: + D = D.normal_form(partial=True) + isotropic = [] + # extract an isotropic space spanned by + # a subset of the generators of D + i = 0 + while i < len(D.gens()): + t = D.gens()[i] + if t.q() == 0 and all(t.b(g) == 0 for g in isotropic): + isotropic.append(t) + i += 1 + isotropic = [g.lift() for g in isotropic] + L = L.overlattice(isotropic) + D = L.discriminant_group(2) + # clean up whatever is left by brute force + while D.cardinality().valuation(2) > 1: + for t in D: + if t != 0 and t.q() == 0: + break + if t.q() != 0 : + # no isotropic vector left + break + L = L.overlattice([t.lift()]) + D = L.discriminant_group(2) + # odd case + for p in P: + if p == 2: + continue + # go squarefree + D = L.discriminant_group(p).normal_form() + isotropic = [p**((-b.q().lift().valuation(p)+1)//2) * b.lift() for b in D.gens()] + L = L.overlattice(isotropic) + # now the p-discriminant_group is a vector space + while True: + d = L.discriminant_group(p).gram_matrix_bilinear().det() + v = -d.valuation(p) + u = d.numerator() + if v <= 1 or (v == 2 and ZZ(-1).kronecker(p) != u.kronecker(p)): + # the lattice is already maximal at p + break + # diagonalize the gram matrix + D = L.discriminant_group(p).normal_form(partial=True) + gen = D.gens() + G = D.gram_matrix_quadratic().diagonal() + k = GF(p) + a = k(G[0].numerator()) + b = k(G[1].numerator()) + if (-b/a).is_square(): + # solve: a*x^2 + b *y^2 = 0 + x = (-b/a).sqrt() + y = 1 + t = ZZ(x)*gen[0] + ZZ(y)*gen[1] + else: + # or solve a*x^2 + b*y^2 + c = 0 + # we know the rank is at least 3 + c = k(G[2].numerator()) + # brute force to find a suitable y + # very fast + for y in GF(p): + x = (-c - b*y**2)/a + if x.is_square(): + x = x.sqrt() + break + t = ZZ(x)*gen[0] + ZZ(y)*gen[1] + ZZ(1)*gen[2] + L = L.overlattice([t.lift()]) + return L + def orthogonal_group(self, gens=None, is_finite=None): """ Return the orthogonal group of this lattice as a matrix group. @@ -1294,3 +1418,73 @@ def twist(self, s, discard_basis=False): inner_product_matrix = s * self.inner_product_matrix() ambient = FreeQuadraticModule(self.base_ring(), n, inner_product_matrix) return FreeQuadraticModule_integer_symmetric(ambient=ambient, basis=self.basis(), inner_product_matrix=inner_product_matrix) + +def local_modification(M, G, p, check=True): + r""" + Return a local modification of `M` that matches `G` at `p`. + + INPUT: + + - ``M`` -- a `\ZZ_p`-maximal lattice + + - ``G`` -- the gram matrix of a lattice + isomorphic to `M` over `\QQ_p` + + - ``p`` -- a prime number + + OUTPUT: + + an integral lattice `M'` in the ambient space of `M` such that `M` and `M'` are locally equal at all + completions except at `p` where `M'` is locally equivalent to the lattice with gram matrix `G` + + EXAMPLES:: + + sage: from sage.modules.free_quadratic_module_integer_symmetric import local_modification + sage: L = IntegralLattice("A3").twist(15) + sage: M = L.maximal_overlattice() + sage: for p in prime_divisors(L.determinant()): + ....: M = local_modification(M, L.gram_matrix(), p) + sage: M.genus() == L.genus() + True + sage: L = IntegralLattice("D4").twist(3*4) + sage: M = L.maximal_overlattice() + sage: local_modification(M, L.gram_matrix(), 2) + Lattice of degree 4 and rank 4 over Integer Ring + Basis matrix: + [1/3 0 1/3 2/3] + [ 0 1/3 1/3 2/3] + [ 0 0 1 0] + [ 0 0 0 1] + Inner product matrix: + [ 24 -12 0 0] + [-12 24 -12 -12] + [ 0 -12 24 0] + [ 0 -12 0 24] + """ + from sage.quadratic_forms.genera.normal_form import p_adic_normal_form + from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring,p_adic_symbol + + # notation + d = G.inverse().denominator() + n = M.rank() + scale = d.valuation(p) + d = p**scale + + L = IntegralLattice(G) + L_max = L.maximal_overlattice(p=p) + + # invert the gerstein operations + _, U = p_adic_normal_form(L_max.gram_matrix(), p, precision=scale+3) + B = (~L_max.basis_matrix()).change_ring(ZZ)*~U.change_ring(ZZ) + + _, UM = p_adic_normal_form(M.gram_matrix(), p, precision=scale+3) + B = B * UM.change_ring(ZZ) * M.basis_matrix() + + # the local modification + S = M.sublattice(((M.span(B) & M) + d * M).gens()) + # confirm result + if check: + s1 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(S.gram_matrix(), p, scale)) + s2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(G, p, scale)) + assert s1 == s2, "oops" + return S diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index 77842ec794a..2c1689bd256 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -281,9 +281,9 @@ def _subprocess(self, f, dir, args, kwds={}): """ import os, sys try: - from imp import reload - except ImportError: from importlib import reload + except ImportError: + from imp import reload from sage.misc.persist import save # Make it so all stdout is sent to a file so it can diff --git a/src/sage/plot/contour_plot.py b/src/sage/plot/contour_plot.py index efc1f2b2ee9..a7533fa3d94 100644 --- a/src/sage/plot/contour_plot.py +++ b/src/sage/plot/contour_plot.py @@ -1,8 +1,7 @@ """ Contour Plots """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 Alex Clemesha , # William Stein , # 2008 Mike Hansen , @@ -16,19 +15,20 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** +import operator from sage.plot.primitive import GraphicPrimitive from sage.misc.decorators import options, suboptions from sage.plot.colors import rgbcolor, get_cmap from sage.arith.srange import xsrange -import operator class ContourPlot(GraphicPrimitive): """ - Primitive class for the contour plot graphics type. See - ``contour_plot?`` for help actually doing contour plots. + Primitive class for the contour plot graphics type. + + See ``contour_plot?`` for help actually doing contour plots. INPUT: @@ -63,7 +63,7 @@ class ContourPlot(GraphicPrimitive): """ def __init__(self, xy_data_array, xrange, yrange, options): """ - Initializes base class ContourPlot. + Initialize base class ``ContourPlot``. EXAMPLES:: @@ -84,7 +84,7 @@ def __init__(self, xy_data_array, xrange, yrange, options): def get_minmax_data(self): """ - Returns a dictionary with the bounding box data. + Return a dictionary with the bounding box data. EXAMPLES:: @@ -140,7 +140,8 @@ def _repr_(self): sage: c = C[0]; c ContourPlot defined by a 100 x 100 data grid """ - return "ContourPlot defined by a %s x %s data grid"%(self.xy_array_row, self.xy_array_col) + msg = "ContourPlot defined by a %s x %s data grid" + return msg % (self.xy_array_row, self.xy_array_col) def _render_on_subplot(self, subplot): """ @@ -163,10 +164,11 @@ def _render_on_subplot(self, subplot): cmap = get_cmap('gray') else: if isinstance(contours, (int, Integer)): - cmap = get_cmap([(i,i,i) for i in xsrange(0,1,1/contours)]) + cmap = get_cmap([(i, i, i) + for i in xsrange(0, 1, 1 / contours)]) else: - l = Integer(len(contours)) - cmap = get_cmap([(i,i,i) for i in xsrange(0,1,1/l)]) + step = 1 / Integer(len(contours)) + cmap = get_cmap([(i, i, i) for i in xsrange(0, 1, step)]) x0, x1 = float(self.xrange[0]), float(self.xrange[1]) y0, y1 = float(self.yrange[0]), float(self.yrange[1]) @@ -192,7 +194,8 @@ def _render_on_subplot(self, subplot): from sage.plot.misc import get_matplotlib_linestyle linestyles = options.get('linestyles', None) if isinstance(linestyles, (list, tuple)): - linestyles = [get_matplotlib_linestyle(i, 'long') for i in linestyles] + linestyles = [get_matplotlib_linestyle(i, 'long') + for i in linestyles] else: linestyles = get_matplotlib_linestyle(linestyles, 'long') if contours is None: @@ -827,11 +830,13 @@ def f(x,y): return cos(x) + sin(y) region = options.pop('region') ev = [f] if region is None else [f, region] - F, ranges = setup_for_eval_on_grid(ev, [xrange, yrange], options['plot_points']) + F, ranges = setup_for_eval_on_grid(ev, [xrange, yrange], + options['plot_points']) g = F[0] xrange, yrange = [r[:2] for r in ranges] - xy_data_array = [[g(x, y) for x in xsrange(*ranges[0], include_endpoint=True)] + xy_data_array = [[g(x, y) for x in xsrange(*ranges[0], + include_endpoint=True)] for y in xsrange(*ranges[1], include_endpoint=True)] if region is not None: @@ -857,10 +862,11 @@ def f(x,y): return cos(x) + sin(y) scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] - if scale == 'semilogy' or scale == 'semilogx': + if scale in ('semilogy', 'semilogx'): options['aspect_ratio'] = 'automatic' - g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) + g._set_extra_kwds(Graphics._extract_kwds_for_show(options, + ignore=['xmin', 'xmax'])) g.add_primitive(ContourPlot(xy_data_array, xrange, yrange, options)) return g @@ -949,7 +955,7 @@ def implicit_plot(f, xrange, yrange, **options): g = implicit_plot(x**2 + y**2 - 2, (x,-3,3), (y,-3,3)) sphinx_plot(g) - We can do the same thing, but using a callable function so we don't + We can do the same thing, but using a callable function so we do not need to explicitly define the variables in the ranges. We also fill the inside:: @@ -1179,17 +1185,19 @@ def f(x,y): from sage.symbolic.expression import is_SymbolicEquation if is_SymbolicEquation(f): if f.operator() != operator.eq: - raise ValueError("input to implicit plot must be function or equation") + raise ValueError("input to implicit plot must be function " + "or equation") f = f.lhs() - f.rhs() linewidths = options.pop('linewidth', None) linestyles = options.pop('linestyle', None) if 'color' in options and 'rgbcolor' in options: raise ValueError('only one of color or rgbcolor should be specified') - elif 'color' in options: - options['cmap']=[options.pop('color', None)] + + if 'color' in options: + options['cmap'] = [options.pop('color', None)] elif 'rgbcolor' in options: - options['cmap']=[rgbcolor(options.pop('rgbcolor', None))] + options['cmap'] = [rgbcolor(options.pop('rgbcolor', None))] if options['fill'] is True: options.pop('fill') @@ -1202,11 +1210,10 @@ def f(x,y): borderwidth=linewidths, borderstyle=linestyles, incol=incol, bordercol=bordercol, **options) - else: - return region_plot(f < 0, xrange, yrange, borderwidth=linewidths, - borderstyle=linestyles, - incol=incol, bordercol=bordercol, - **options) + return region_plot(f < 0, xrange, yrange, borderwidth=linewidths, + borderstyle=linestyles, + incol=incol, bordercol=bordercol, + **options) elif options['fill'] is False: options.pop('fillcolor', None) return contour_plot(f, xrange, yrange, linewidths=linewidths, @@ -1349,7 +1356,7 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, g = region_plot([x**2 + y**2 < 1, x < y], (x,-2,2), (y,-2,2)) sphinx_plot(g) - Since it doesn't look very good, let's increase ``plot_points``:: + Since it does not look very good, let us increase ``plot_points``:: sage: region_plot([x^2 + y^2 < 1, x< y], (x,-2,2), (y,-2,2), plot_points=400) Graphics object consisting of 1 graphics primitive @@ -1486,12 +1493,15 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, if not isinstance(f, (list, tuple)): f = [f] - feqs = [equify(g) for g in f if is_Expression(g) and g.operator() is operator.eq and not equify(g).is_zero()] - f = [equify(g) for g in f if not (is_Expression(g) and g.operator() is operator.eq)] + feqs = [equify(g) for g in f + if is_Expression(g) and g.operator() is operator.eq + and not equify(g).is_zero()] + f = [equify(g) for g in f + if not (is_Expression(g) and g.operator() is operator.eq)] neqs = len(feqs) if neqs > 1: - warn("There are at least 2 equations; " + - "If the region is degenerated to points, " + + warn("There are at least 2 equations; " + "If the region is degenerated to points, " "plotting might show nothing.") feqs = [sum([fn**2 for fn in feqs])] neqs = 1 @@ -1506,8 +1516,11 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, plot_points) xrange, yrange = [r[:2] for r in ranges] - xy_data_arrays = numpy.asarray([[[func(x, y) for x in xsrange(*ranges[0], include_endpoint=True)] - for y in xsrange(*ranges[1], include_endpoint=True)] + xy_data_arrays = numpy.asarray([[[func(x, y) + for x in xsrange(*ranges[0], + include_endpoint=True)] + for y in xsrange(*ranges[1], + include_endpoint=True)] for func in f_all[neqs::]], dtype=float) xy_data_array = numpy.abs(xy_data_arrays.prod(axis=0)) # Now we need to set entries to negative iff all @@ -1534,10 +1547,11 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] - if scale == 'semilogy' or scale == 'semilogx': + if scale in ('semilogy', 'semilogx'): options['aspect_ratio'] = 'automatic' - g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) + g._set_extra_kwds(Graphics._extract_kwds_for_show(options, + ignore=['xmin', 'xmax'])) if neqs == 0: g.add_primitive(ContourPlot(xy_data_array, xrange, yrange, @@ -1545,11 +1559,14 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, cmap=cmap, fill=True, **options))) else: - mask = numpy.asarray([[elt > 0 for elt in rows] for rows in xy_data_array], + mask = numpy.asarray([[elt > 0 for elt in rows] + for rows in xy_data_array], dtype=bool) xy_data_array = numpy.asarray([[f_all[0](x, y) - for x in xsrange(*ranges[0], include_endpoint=True)] - for y in xsrange(*ranges[1], include_endpoint=True)], + for x in xsrange(*ranges[0], + include_endpoint=True)] + for y in xsrange(*ranges[1], + include_endpoint=True)], dtype=float) xy_data_array[mask] = None if bordercol or borderstyle or borderwidth: @@ -1567,8 +1584,8 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, def equify(f): """ - Returns the equation rewritten as a symbolic function to give - negative values when True, positive when False. + Return the equation rewritten as a symbolic function to give + negative values when ``True``, positive when ``False``. EXAMPLES:: @@ -1583,13 +1600,12 @@ def equify(f): -x*y + 1 sage: equify(y > 0) -y - sage: f=equify(lambda x, y: x > y) + sage: f = equify(lambda x, y: x > y) sage: f(1, 2) 1 sage: f(2, 1) -1 """ - import operator from sage.calculus.all import symbolic_expression from sage.symbolic.expression import is_Expression if not is_Expression(f): @@ -1598,5 +1614,4 @@ def equify(f): op = f.operator() if op is operator.gt or op is operator.ge: return symbolic_expression(f.rhs() - f.lhs()) - else: - return symbolic_expression(f.lhs() - f.rhs()) + return symbolic_expression(f.lhs() - f.rhs()) diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index bce8d99640e..8a75fe81bca 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -7,6 +7,7 @@ - David Kohel & Gabriele Nebe (2007): First created - Simon Brandhorst (2018): various bugfixes and printing - Simon Brandhorst (2018): enumeration of genera +- Simon Brandhorst (2020): genus representative """ # **************************************************************************** # Copyright (C) 2007 David Kohel @@ -26,6 +27,8 @@ from sage.rings.integer_ring import IntegerRing, ZZ from sage.rings.rational_field import RationalField, QQ from sage.rings.integer import Integer +from sage.interfaces.gp import gp +from sage.libs.pari import pari from sage.rings.finite_rings.finite_field_constructor import FiniteField from copy import copy, deepcopy from sage.misc.misc import verbose @@ -111,7 +114,8 @@ def genera(sig_pair, determinant, max_scale=None, even=False): genera.append(G) # render the output deterministic for testing genera.sort(key=lambda x: [s.symbol_tuple_list() for s in x.local_symbols()]) - return(genera) + return genera + def _local_genera(p, rank, det_val, max_scale, even): r""" @@ -210,7 +214,7 @@ def _local_genera(p, rank, det_val, max_scale, even): # each equivalence class if not g1 in symbols: symbols.append(g1) - return(symbols) + return symbols def _blocks(b, even_only=False): @@ -2242,6 +2246,7 @@ def dimension(self): return p + n dim = dimension + rank = dimension def discriminant_form(self): r""" @@ -2276,6 +2281,139 @@ def discriminant_form(self): q = matrix.block_diagonal(qL) return TorsionQuadraticForm(q) + def rational_representative(self): + r""" + Return a representative of the rational + bilinear form defined by this genus. + + OUTPUT: + + A diagonal_matrix. + + EXAMPLES:: + + sage: from sage.quadratic_forms.genera.genus import genera + sage: G = genera((8,0),1)[0] + sage: G + Genus of + None + Signature: (8, 0) + Genus symbol at 2: 1^8 + sage: G.rational_representative() + [1 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0] + [0 0 0 1 0 0 0 0] + [0 0 0 0 1 0 0 0] + [0 0 0 0 0 2 0 0] + [0 0 0 0 0 0 1 0] + [0 0 0 0 0 0 0 2] + """ + from sage.quadratic_forms.all import QuadraticForm, quadratic_form_from_invariants + sminus = self.signature_pair_of_matrix()[1] + det = self.determinant() + m = self.rank() + P = [] + for sym in self._local_symbols: + p = sym._prime + # it is important to use the definition of Cassels here! + if QuadraticForm(QQ,2*sym.gram_matrix()).hasse_invariant(p) == -1: + P.append(p) + q = quadratic_form_from_invariants(F=QQ, rk=m, det=det, + P=P, sminus=sminus) + return q.Hessian_matrix()/2 + + def _compute_representative(self, LLL=True): + r""" + Compute a representative of this genus and cache it. + + INPUT: + + - ``LLL`` -- boolean (default: ``True``); whether or not to LLL reduce the result + + TESTS:: + + sage: from sage.quadratic_forms.genera.genus import genera + sage: for det in range(1,5): + ....: G = genera((4,0), det, even=False) + ....: assert all(g==Genus(g.representative()) for g in G) + sage: for det in range(1,5): + ....: G = genera((1,2), det, even=False) + ....: assert all(g==Genus(g.representative()) for g in G) + sage: for det in range(1,9): # long time (8s, 2020) + ....: G = genera((2,2), det, even=False) # long time + ....: assert all(g==Genus(g.representative()) for g in G) # long time + """ + from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice, local_modification + even = self.is_even() + q = self.rational_representative() + n = q.nrows() + # the associated quadratic form xGx.T/2 should be integral + L = IntegralLattice(4*q).maximal_overlattice() + p = 2 + sym2 = self.local_symbols()[0] + if not self.is_even(): + # the quadratic form of xGx.T/2 must be integral + # for things to work + # solve this by multiplying the basis by 2 + L = local_modification(L, 4*sym2.gram_matrix(), p) + L = L.overlattice(L.basis_matrix()/2) + else: + L = local_modification(L, sym2.gram_matrix(), p) + for sym in self._local_symbols[1:]: + p = sym.prime() + L = local_modification(L, sym.gram_matrix(), p) + L = L.gram_matrix().change_ring(ZZ) + if LLL: + sig = self.signature_pair_of_matrix() + if sig[0]*sig[1] != 0: + from sage.env import SAGE_EXTCODE + m = pari(L) + gp.read(SAGE_EXTCODE + "/pari/simon/qfsolve.gp") + m = gp.eval('qflllgram_indefgoon(%s)'%m) + # convert the output string to sage + L = pari(m).sage()[0] + elif sig[1] != 0: + U = -(-L).LLL_gram() + L = U.T * L * U + else: + U = L.LLL_gram() + L = U.T * L * U + # confirm the computation + assert Genus(L) == self + L.set_immutable() + self._representative = L + + def representative(self): + r""" + Return a representative in this genus. + + EXAMPLES:: + + sage: from sage.quadratic_forms.genera.genus import genera + sage: g = genera([1,3],24)[0] + sage: g + Genus of + None + Signature: (1, 3) + Genus symbol at 2: [1^-1 2^3]_0 + Genus symbol at 3: 1^3 3^1 + + A representative of ``g`` is not known yet. + Let us trigger its computation: + + sage: g.representative() + [ 0 0 0 2] + [ 0 -1 0 0] + [ 0 0 -6 0] + [ 2 0 0 0] + sage: g == Genus(g.representative()) + True + """ + if self._representative is None: + self._compute_representative() + return self._representative + def local_symbols(self): r""" Return a copy of the list of local symbols of this symbol. diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 64738e9eea0..58050ee2e1c 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -117,6 +117,21 @@ def quadratic_form_from_invariants(F, rk, det, P, sminus): [ * -3 ] sage: all(q.hasse_invariant(p)==-1 for p in P) True + + TESTS: + + This shows that :trac:`28955` is fixed:: + + sage: quadratic_form_from_invariants(QQ,3,2,[2],2) + Quadratic form in 3 variables over Rational Field with coefficients: + [ -1 0 0 ] + [ * 1 0 ] + [ * * -2 ] + + sage: quadratic_form_from_invariants(QQ,4,2,[2],4) + Traceback (most recent call last): + ... + ValueError: invariants do not define a rational quadratic form """ from sage.arith.misc import hilbert_symbol # normalize input @@ -135,7 +150,10 @@ def quadratic_form_from_invariants(F, rk, det, P, sminus): for p in P: if QQ(-d).is_padic_square(p): raise ValueError("invariants do not define a rational quadratic form") - if sminus % 4 in (2, 3) and len(P) % 2 == 0: + f = 0 + if sminus % 4 in (2, 3): + f = 1 + if (f + len(P)) % 2 == 1: raise ValueError("invariants do not define a rational quadratic form") D = [] while rk >= 2: @@ -162,9 +180,9 @@ def quadratic_form_from_invariants(F, rk, det, P, sminus): S += [-1] a = QQ.hilbert_symbol_negative_at_S(S,-d) a = ZZ(a) - P = [p for p in P if hilbert_symbol(a, -d, p) == 1] - P += [p for p in (2*a*d).prime_divisors() - if hilbert_symbol(a, -d, p)==-1 and p not in P] + P = ([p for p in P if hilbert_symbol(a, -d, p) == 1] + +[p for p in (2*a*d).prime_divisors() + if hilbert_symbol(a, -d, p)==-1 and p not in P]) sminus = max(0, sminus-1) rk = rk - 1 d = a*d diff --git a/src/sage/quadratic_forms/ternary.pyx b/src/sage/quadratic_forms/ternary.pyx index ec7a00e0919..63b59b82525 100644 --- a/src/sage/quadratic_forms/ternary.pyx +++ b/src/sage/quadratic_forms/ternary.pyx @@ -248,7 +248,8 @@ def _reduced_ternary_form_eisenstein_with_matrix(a1, a2, a3, a23, a13, a12): M*=matrix(ZZ,3,[0,-1,0,-1,0,0,0,0,-1]) [a13,a23]=[a23,a13] - return((a1,a2,a3,a23,a13,a12),M) + return (a1, a2, a3, a23, a13, a12), M + def _reduced_ternary_form_eisenstein_without_matrix(a1, a2, a3, a23, a13, a12): """ @@ -416,7 +417,7 @@ def _reduced_ternary_form_eisenstein_without_matrix(a1, a2, a3, a23, a13, a12): if a1 == a2 and abs(a23) > abs(a13): [a13,a23]=[a23,a13] - return((a1,a2,a3,a23,a13,a12)) + return a1, a2, a3, a23, a13, a12 def primitivize(long long v0, long long v1, long long v2, p): diff --git a/src/sage/repl/display/fancy_repr.py b/src/sage/repl/display/fancy_repr.py index 818498fbaec..b33a3064ed9 100644 --- a/src/sage/repl/display/fancy_repr.py +++ b/src/sage/repl/display/fancy_repr.py @@ -43,7 +43,7 @@ def __repr__(self): sage: ObjectReprABC() ObjectReprABC pretty printer """ - return('{0} pretty printer'.format(self.__class__.__name__)) + return '{0} pretty printer'.format(self.__class__.__name__) def __call__(self, obj, p, cycle): r""" diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index 76f0d3ed655..c03ab39e179 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -428,9 +428,9 @@ cdef class IntegerMod_abstract(FiniteRingElement): sage: abs(Mod(2,3)) Traceback (most recent call last): ... - ArithmeticError: absolute valued not defined on integers modulo n. + ArithmeticError: absolute value not defined on integers modulo n. """ - raise ArithmeticError("absolute valued not defined on integers modulo n.") + raise ArithmeticError("absolute value not defined on integers modulo n.") def __reduce__(IntegerMod_abstract self): """ diff --git a/src/sage/rings/function_field/place.py b/src/sage/rings/function_field/place.py index 476273171f7..a856a25ec8c 100644 --- a/src/sage/rings/function_field/place.py +++ b/src/sage/rings/function_field/place.py @@ -865,8 +865,8 @@ def _residue_field(self, name=None): To: Number Field in s with defining polynomial x^2 - 2*x + 2)] sage: for p in L.places_above(I.place()): ....: k, fr_k, to_k = p.residue_field() - ....: assert all([fr_k(k(e)) == e for e in range(10)]) - ....: assert all([to_k(fr_k(e)) == e for e in [k.random_element() for i in [1..10]]]) + ....: assert all(fr_k(k(e)) == e for e in range(10)) + ....: assert all(to_k(fr_k(e)) == e for e in [k.random_element() for i in [1..10]]) :: diff --git a/src/sage/rings/homset.py b/src/sage/rings/homset.py index 6a20fb5217c..86ac99e1852 100644 --- a/src/sage/rings/homset.py +++ b/src/sage/rings/homset.py @@ -65,6 +65,9 @@ class RingHomset_generic(HomsetWithBase): sage: QQ.Hom(ZZ) Set of Homomorphisms from Rational Field to Integer Ring """ + + Element = morphism.RingHomomorphism + def __init__(self, R, S, category = None): """ Initialize ``self``. @@ -105,66 +108,23 @@ def has_coerce_map_from(self, x): """ return (x.domain() == self.domain() and x.codomain() == self.codomain()) - def _coerce_impl(self, x): + def _element_constructor_(self, x, check=True, base_map=None): """ - Check to see if we can coerce ``x`` into a homomorphism with the - correct rings. + Construct an element of ``self`` from ``x``. EXAMPLES:: sage: H = Hom(ZZ, QQ) - sage: phi = H([1]) - sage: H2 = Hom(QQ, QQ) - sage: phi2 = H2(phi); phi2 # indirect doctest - Ring endomorphism of Rational Field - Defn: 1 |--> 1 - sage: H(phi2) # indirect doctest + sage: phi = H([1]); phi Ring morphism: From: Integer Ring To: Rational Field Defn: 1 |--> 1 - """ - from sage.categories.map import Map - if not (isinstance(x, Map) and x.category_for().is_subcategory(Rings())): - raise TypeError - if x.parent() is self: - return x - # Case 1: the parent fits - if x.parent() == self: - if isinstance(x, morphism.RingHomomorphism_im_gens): - return morphism.RingHomomorphism_im_gens(self, x.im_gens()) - elif isinstance(x, morphism.RingHomomorphism_cover): - return morphism.RingHomomorphism_cover(self) - elif isinstance(x, morphism.RingHomomorphism_from_base): - return morphism.RingHomomorphism_from_base(self, x.underlying_map()) - # Case 2: unique extension via fraction field - try: - if isinstance(x, morphism.RingHomomorphism_im_gens) and x.domain().fraction_field().has_coerce_map_from(self.domain()): - return morphism.RingHomomorphism_im_gens(self, x.im_gens()) - except Exception: - pass - # Case 3: the homomorphism can be extended by coercion - try: - return x.extend_codomain(self.codomain()).extend_domain(self.domain()) - except Exception: - pass - # Last resort, case 4: the homomorphism is induced from the base ring - if self.domain()==self.domain().base() or self.codomain()==self.codomain().base(): - raise TypeError - try: - x = self.domain().base().Hom(self.codomain().base())(x) - return morphism.RingHomomorphism_from_base(self, x) - except Exception: - raise TypeError - - def __call__(self, im_gens, check=True, base_map=None): - """ - Create a homomorphism. - - EXAMPLES:: - - sage: H = Hom(ZZ, QQ) - sage: H([1]) + sage: H2 = Hom(QQ, QQ) + sage: phi2 = H2(phi); phi2 + Ring endomorphism of Rational Field + Defn: 1 |--> 1 + sage: H(phi2) Ring morphism: From: Integer Ring To: Rational Field @@ -200,12 +160,37 @@ def __call__(self, im_gens, check=True, base_map=None): True """ from sage.categories.map import Map - if isinstance(im_gens, Map): - if base_map is not None: - raise ValueError("Cannot specify base_map when providing a map") - return self._coerce_impl(im_gens) - else: - return morphism.RingHomomorphism_im_gens(self, im_gens, base_map=base_map, check=check) + # Case 0: the homomorphism is given by images of generators + if not (isinstance(x, Map) and x.category_for().is_subcategory(Rings())): + return morphism.RingHomomorphism_im_gens(self, x, base_map=base_map, check=check) + if base_map is not None: + raise ValueError("cannot specify base_map when providing a map") + # Case 1: the parent fits + if x.parent() == self: + if isinstance(x, morphism.RingHomomorphism_im_gens): + return morphism.RingHomomorphism_im_gens(self, x.im_gens()) + elif isinstance(x, morphism.RingHomomorphism_cover): + return morphism.RingHomomorphism_cover(self) + elif isinstance(x, morphism.RingHomomorphism_from_base): + return morphism.RingHomomorphism_from_base(self, x.underlying_map()) + # Case 2: unique extension via fraction field + try: + if (isinstance(x, morphism.RingHomomorphism_im_gens) + and x.domain().fraction_field().has_coerce_map_from(self.domain())): + return morphism.RingHomomorphism_im_gens(self, x.im_gens()) + except (TypeError, ValueError): + pass + # Case 3: the homomorphism can be extended by coercion + try: + return x.extend_codomain(self.codomain()).extend_domain(self.domain()) + except (TypeError, ValueError): + pass + # Case 4: the homomorphism is induced from the base ring + if (self.domain() != self.domain().base() + or self.codomain() != self.codomain().base()): + x = self.domain().base().Hom(self.codomain().base())(x) + return morphism.RingHomomorphism_from_base(self, x) + raise ValueError('cannot convert {} to an element of {}'.format(x, self)) def natural_map(self): """ @@ -289,49 +274,27 @@ class RingHomset_quo_ring(RingHomset_generic): sage: phi == loads(dumps(phi)) True """ - def __call__(self, im_gens, base_map=None, check=True): + + Element = morphism.RingHomomorphism_from_quotient + + def _element_constructor_(self, x, base_map=None, check=True): """ - Create a homomorphism. + Construct an element of ``self`` from ``x``. EXAMPLES:: sage: R. = PolynomialRing(QQ, 2) sage: S. = R.quotient(x^2 + y^2) sage: H = S.Hom(R) - sage: phi = H([b,a]); phi + sage: phi = H([b, a]); phi Ring morphism: From: Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) To: Multivariate Polynomial Ring in x, y over Rational Field Defn: a |--> b b |--> a - - """ - if isinstance(im_gens, morphism.RingHomomorphism_from_quotient): - return morphism.RingHomomorphism_from_quotient(self, im_gens._phi()) - try: - pi = self.domain().cover() - phi = pi.domain().hom(im_gens, base_map=base_map, check=check) - return morphism.RingHomomorphism_from_quotient(self, phi) - except (NotImplementedError, ValueError): - try: - return self._coerce_impl(im_gens) - except TypeError: - raise TypeError("images do not define a valid homomorphism") - - def _coerce_impl(self, x): - """ - Check to see if we can coerce ``x`` into a homomorphism with the - correct rings. - - EXAMPLES:: - - sage: R. = PolynomialRing(QQ, 2) - sage: S. = R.quotient(x^2 + y^2) - sage: H = S.Hom(R) - sage: phi = H([b,a]) sage: R2. = PolynomialRing(ZZ, 2) sage: H2 = Hom(R2, S) - sage: H2(phi) # indirect doctest + sage: H2(phi) Composite map: From: Multivariate Polynomial Ring in x, y over Integer Ring To: Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) @@ -350,10 +313,9 @@ def _coerce_impl(self, x): To: Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + y^2) """ - if not isinstance(x, morphism.RingHomomorphism_from_quotient): - raise TypeError - if x.parent() is self: - return x - if x.parent() == self: - return morphism.RingHomomorphism_from_quotient(self, x._phi()) - raise TypeError + if isinstance(x, morphism.RingHomomorphism_from_quotient): + phi = x._phi() + else: + pi = self.domain().cover() + phi = pi.domain().hom(x, base_map=base_map, check=check) + return self.element_class(self, phi) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 186959eb8dc..0e47af25b00 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -34,6 +34,7 @@ from __future__ import print_function, absolute_import from sage.categories.rings import Rings +from sage.rings.infinity import infinity from sage.categories.integral_domains import IntegralDomains from sage.categories.fields import Fields from sage.categories.complete_discrete_valuation import CompleteDiscreteValuationFields @@ -369,7 +370,7 @@ def _repr_(self): s = 'Sparse ' + s return s - def _element_constructor_(self, x, n=0): + def _element_constructor_(self, x, n=0, prec=infinity): r""" Construct a Laurent series from `x`. @@ -379,12 +380,18 @@ def _element_constructor_(self, x, n=0): - ``n`` -- (default: 0) multiply the result by `t^n` + - ``prec`` -- (default: ``infinity``) the precision of the series + as an integer. + + EXAMPLES:: sage: R. = LaurentSeriesRing(Qp(5, 10)) sage: S. = LaurentSeriesRing(RationalField()) sage: R(t + t^2 + O(t^3)) (1 + O(5^10))*u + (1 + O(5^10))*u^2 + O(u^3) + sage: R(t + t^2 + O(t^3), prec=2) + (1 + O(5^10))*u + O(u^2) Note that coercing an element into its own parent just produces that element again (since Laurent series are immutable):: @@ -412,6 +419,12 @@ def _element_constructor_(self, x, n=0): TESTS: + Check that :trac:`28993` is fixed:: + + sage: from sage.modular.etaproducts import qexp_eta + sage: qexp_eta(S, prec=30) + 1 - t - t^2 + t^5 + t^7 - t^12 - t^15 + t^22 + t^26 + O(t^30) + When converting from `R((z))` to `R((z))((w))`, the variable `z` is sent to `z` rather than to `w` (see :trac:`7085`):: @@ -452,7 +465,7 @@ def _element_constructor_(self, x, n=0): P = parent(x) if isinstance(x, self.element_class) and n == 0 and P is self: - return x # ok, since Laurent series are immutable (no need to make a copy) + return x.add_bigoh(prec) # ok, since Laurent series are immutable (no need to make a copy) elif P is self.base_ring(): # Convert x into a power series; if P is itself a Laurent # series ring A((t)), this prevents the implementation of @@ -469,20 +482,20 @@ def _element_constructor_(self, x, n=0): if t == "t_RFRAC": # Rational function x = self(self.polynomial_ring()(x.numerator())) / \ self(self.polynomial_ring()(x.denominator())) - return (x << n) + return (x << n).add_bigoh(prec) elif t == "t_SER": # Laurent series n += x._valp() bigoh = n + x.length() x = self(self.polynomial_ring()(x.Vec())) return (x << n).add_bigoh(bigoh) else: # General case, pretend to be a polynomial - return self(self.polynomial_ring()(x)) << n + return (self(self.polynomial_ring()(x)) << n).add_bigoh(prec) elif (is_FractionFieldElement(x) and (x.base_ring() is self.base_ring() or x.base_ring() == self.base_ring()) and (is_Polynomial(x.numerator()) or is_MPolynomial(x.numerator())) ): x = self(x.numerator()) / self(x.denominator()) - return (x << n) - return self.element_class(self, x, n) + return (x << n).add_bigoh(prec) + return self.element_class(self, x, n).add_bigoh(prec) def construction(self): r""" diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 6c3f176a133..bb6f15703c0 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -740,6 +740,14 @@ cdef class LaurentSeries(AlgebraElement): def add_bigoh(self, prec): """ + Return the truncated series at chosen precision ``prec``. + + See also :meth:`O`. + + INPUT: + + - ``prec`` -- the precision of the series as an integer + EXAMPLES:: sage: R. = LaurentSeriesRing(QQ) @@ -747,11 +755,22 @@ cdef class LaurentSeries(AlgebraElement): t^2 + t^3 + O(t^10) sage: f.add_bigoh(5) t^2 + t^3 + O(t^5) + + TESTS: + + Check that :trac:`28239` is fixed:: + + sage: (t^(-2)).add_bigoh(-1) + t^-2 + O(t^-1) + sage: (t^(-2)).add_bigoh(-2) + O(t^-2) + sage: (t^(-2)).add_bigoh(-3) + O(t^-3) """ if prec == infinity or prec >= self.prec(): return self P = self._parent - if not self: + if not self or prec < self.__n: return type(self)(P, P._power_series_ring(0, prec=0), prec) u = self.__u.add_bigoh(prec - self.__n) return type(self)(P, u, self.__n) @@ -766,6 +785,8 @@ cdef class LaurentSeries(AlgebraElement): the precision of ``self`` and ``prec``. The term `O(q^\text{prec})` is the zero series with precision ``prec``. + See also :meth:`add_bigoh`. + EXAMPLES:: sage: R. = LaurentSeriesRing(QQ) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index eed78a5015e..66cec959e9d 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -1086,7 +1086,7 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): sage: phi = S.hom([xx+1,xx-1]) Traceback (most recent call last): ... - TypeError: images do not define a valid homomorphism + ValueError: relations do not all (canonically) map to 0 under map determined by images of generators You can give a map of the base ring:: @@ -1119,7 +1119,7 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): sage: phi = S.hom([xx+1,xx-1], check=False) Traceback (most recent call last): ... - TypeError: images do not define a valid homomorphism + ValueError: relations do not all (canonically) map to 0 under map determined by images of generators """ RingHomomorphism.__init__(self, parent) if not isinstance(im_gens, sage.structure.sequence.Sequence_generic): @@ -1836,7 +1836,7 @@ cdef class RingHomomorphism_from_quotient(RingHomomorphism): sage: S.hom([b^2, c^2, a^2]) Traceback (most recent call last): ... - TypeError: images do not define a valid homomorphism + ValueError: relations do not all (canonically) map to 0 under map determined by images of generators """ def __init__(self, parent, phi): """ diff --git a/src/sage/rings/number_field/homset.py b/src/sage/rings/number_field/homset.py new file mode 100644 index 00000000000..6a42e7b88e4 --- /dev/null +++ b/src/sage/rings/number_field/homset.py @@ -0,0 +1,580 @@ +""" +Sets of homomorphisms between number fields +""" + +#***************************************************************************** +# Copyright (C) 2007 William Stein +# Copyright (C) 2020 Peter Bruin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.superseded import deprecation + +from sage.rings.homset import RingHomset_generic +from sage.rings.number_field.morphism import (NumberFieldHomomorphism_im_gens, + RelativeNumberFieldHomomorphism_from_abs, + CyclotomicFieldHomomorphism_im_gens) +from sage.rings.integer import Integer +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.structure.sequence import Sequence + + +class NumberFieldHomset(RingHomset_generic): + """ + Set of homomorphisms with domain a given number field. + + TESTS:: + + sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) + sage: TestSuite(H).run() + Failure in _test_category: + ... + The following tests failed: _test_elements + """ + def __init__(self, R, S, category=None): + """ + TESTS: + + Check that :trac:`23647` is fixed:: + + sage: K. = NumberField([x^2 - 2, x^2 - 3]) + sage: e, u, v, w = End(K) + sage: e.abs_hom().parent().category() + Category of homsets of number fields + sage: (v*v).abs_hom().parent().category() + Category of homsets of number fields + """ + if category is None: + from sage.categories.all import Fields, NumberFields + if S in NumberFields: + category = NumberFields() + elif S in Fields: + category = Fields() + RingHomset_generic.__init__(self, R, S, category) + + def __call__(self, im_gens, check=True): + """ + Create the homomorphism sending the generators to ``im_gens``. + + EXAMPLES:: + + sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) + sage: phi = H([H.domain().gen()]); phi # indirect doctest + Ring morphism: + From: Number Field in a with defining polynomial x^2 + 1 with a = 1*I + To: Number Field in b with defining polynomial x^2 + 1 with b = 1*I + Defn: a |--> b + """ + if isinstance(im_gens, NumberFieldHomomorphism_im_gens): + return self._coerce_impl(im_gens) + try: + return NumberFieldHomomorphism_im_gens(self, im_gens, check=check) + except (NotImplementedError, ValueError): + try: + return self._coerce_impl(im_gens) + except TypeError: + raise TypeError("images do not define a valid homomorphism") + + def _coerce_impl(self, x): + r""" + Canonical coercion of ``x`` into this homset. The only things that + coerce canonically into self are elements of self and of homsets equal + to self. + + EXAMPLES:: + + sage: H1 = End(QuadraticField(-1, 'a')) + sage: H1.coerce(loads(dumps(H1[1]))) # indirect doctest + Ring endomorphism of Number Field in a with defining polynomial x^2 + 1 with a = 1*I + Defn: a |--> -a + + TESTS: + + We can move morphisms between categories:: + + sage: f = H1.an_element() + sage: g = End(H1.domain(), category=Rings())(f) + sage: f == End(H1.domain(), category=NumberFields())(g) + True + """ + if not isinstance(x, NumberFieldHomomorphism_im_gens): + raise TypeError + if x.parent() is self: + return x + from sage.categories.all import NumberFields, Rings + if (x.parent() == self or + (x.domain() == self.domain() and x.codomain() == self.codomain() and + # This would be the better check, however it returns False currently: + # self.homset_category().is_full_subcategory(x.category_for()) + # So we check instead that this is a morphism anywhere between + # Rings and NumberFields where the hom spaces do not change. + NumberFields().is_subcategory(self.homset_category()) and + self.homset_category().is_subcategory(Rings()) and + NumberFields().is_subcategory(x.category_for()) and + x.category_for().is_subcategory(Rings()))): + return NumberFieldHomomorphism_im_gens(self, x.im_gens(), check=False) + raise TypeError + + def _an_element_(self): + r""" + Return an element of this set of embeddings. + + EXAMPLES:: + + sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) + sage: H.an_element() # indirect doctest + Ring morphism: + From: Number Field in a with defining polynomial x^2 + 1 with a = 1*I + To: Number Field in b with defining polynomial x^2 + 1 with b = 1*I + Defn: a |--> b + + sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-2, 'b')) + sage: H.an_element() + Traceback (most recent call last): + ... + EmptySetError: There is no morphism from Number Field in a with defining polynomial x^2 + 1 with a = 1*I to Number Field in b with defining polynomial x^2 + 2 with b = 1.414213562373095?*I + """ + L = self.list() + if len(L) != 0: + return L[0] + else: + from sage.categories.sets_cat import EmptySetError + raise EmptySetError("There is no morphism from {} to {}".format( + self.domain(), self.codomain())) + + def _repr_(self): + r""" + String representation of this homset. + + EXAMPLES:: + + sage: repr(Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b'))) # indirect doctest + 'Set of field embeddings from Number Field in a with defining polynomial x^2 + 1 with a = 1*I to Number Field in b with defining polynomial x^2 + 1 with b = 1*I' + sage: repr(Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'a'))) # indirect doctest + 'Automorphism group of Number Field in a with defining polynomial x^2 + 1 with a = 1*I' + """ + D = self.domain() + C = self.codomain() + if C == D: + return "Automorphism group of {}".format(D) + else: + return "Set of field embeddings from {} to {}".format(D, C) + + def order(self): + """ + Return the order of this set of field homomorphism. + + EXAMPLES:: + + sage: k. = NumberField(x^2 + 1) + sage: End(k) + Automorphism group of Number Field in a with defining polynomial x^2 + 1 + sage: End(k).order() + 2 + sage: k. = NumberField(x^3 + 2) + sage: End(k).order() + 1 + + sage: K. = NumberField( [x^3 + 2, x^2 + x + 1] ) + sage: End(K).order() + 6 + """ + return Integer(len(self.list())) + + cardinality = order + + @cached_method + def list(self): + """ + Return a list of all the elements of self. + + EXAMPLES:: + + sage: K. = NumberField(x^3 - 3*x + 1) + sage: End(K).list() + [ + Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 + Defn: a |--> a, + Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 + Defn: a |--> a^2 - 2, + Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 + Defn: a |--> -a^2 - a + 2 + ] + sage: Hom(K, CyclotomicField(9))[0] # indirect doctest + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 3*x + 1 + To: Cyclotomic Field of order 9 and degree 6 + Defn: a |--> -zeta9^4 + zeta9^2 - zeta9 + + An example where the codomain is a relative extension:: + + sage: K. = NumberField(x^3 - 2) + sage: L. = K.extension(x^2 + 3) + sage: Hom(K, L).list() + [ + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 2 + To: Number Field in b with defining polynomial x^2 + 3 over its base field + Defn: a |--> a, + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 2 + To: Number Field in b with defining polynomial x^2 + 3 over its base field + Defn: a |--> -1/2*a*b - 1/2*a, + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 2 + To: Number Field in b with defining polynomial x^2 + 3 over its base field + Defn: a |--> 1/2*a*b - 1/2*a + ] + """ + D = self.domain() + C = self.codomain() + if D.degree().divides(C.absolute_degree()): + roots = D.polynomial().roots(ring=C, multiplicities=False) + v = [D.hom([r], codomain=C, check=False) for r in roots] + else: + v = [] + return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) + + def __getitem__(self, n): + r""" + Return the ``n``th element of ``self.list()``. + + EXAMPLES:: + + sage: End(CyclotomicField(37))[3] # indirect doctest + Ring endomorphism of Cyclotomic Field of order 37 and degree 36 + Defn: zeta37 |--> zeta37^4 + """ + return self.list()[n] + + +class RelativeNumberFieldHomset(NumberFieldHomset): + """ + Set of homomorphisms with domain a given relative number field. + + EXAMPLES: + + We construct a homomorphism from a relative field by giving + the image of a generator:: + + sage: L. = CyclotomicField(3).extension(x^3 - 2) + sage: phi = L.hom([cuberoot2 * zeta3]); phi + Relative number field endomorphism of Number Field in cuberoot2 with defining polynomial x^3 - 2 over its base field + Defn: cuberoot2 |--> zeta3*cuberoot2 + zeta3 |--> zeta3 + sage: phi(cuberoot2 + zeta3) + zeta3*cuberoot2 + zeta3 + + In fact, this phi is a generator for the Kummer Galois group of this + cyclic extension:: + + sage: phi(phi(cuberoot2 + zeta3)) + (-zeta3 - 1)*cuberoot2 + zeta3 + sage: phi(phi(phi(cuberoot2 + zeta3))) + cuberoot2 + zeta3 + """ + def __call__(self, im_gen, base_map=None, base_hom=None, check=True): + r""" + Create a homomorphism in this homset from the given data, which can be: + + - A homomorphism from this number field. + - A homomorphism from the absolute number field corresponding to this + relative number field. + - An element (specifying the image of the generator) of a ring into + which the base ring coerces. + - A pair consisting of an element of a ring R and a homomorphism from + the base ring to R. + + EXAMPLES:: + + sage: K. = NumberField(x^2 + 1) + sage: L. = K.extension(x^4 - 2) + sage: E = End(L) + sage: E(E[0]) # indirect doctest + Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field + Defn: b |--> b + a |--> a + sage: E(L.absolute_field('c').hom(b+a, L)) # indirect doctest + Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field + Defn: b |--> b + a |--> -a + sage: E(-b*a) # indirect doctest + Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field + Defn: b |--> -a*b + a |--> a + sage: E(-a*b, K.hom([-a])) # indirect doctest + Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field + Defn: b |--> -a*b + a |--> -a + + You can specify a map on the base field:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^2 + 1) + sage: L. = K.extension(x^2-17) + sage: cc = K.hom([-i]) + sage: phi = L.hom([-b],base_map=cc); phi + Relative number field endomorphism of Number Field in b with defining polynomial x^2 - 17 over its base field + Defn: b |--> -b + i |--> -i + + Using check=False, it is possible to construct homomorphisms into fields such as CC + where calculations are only approximate. + + sage: K. = QuadraticField(-7) + sage: f = K.hom([CC(sqrt(-7))], check=False) + sage: x = polygen(K) + sage: L. = K.extension(x^2 - a - 5) + sage: L.Hom(CC)(f(a + 5).sqrt(), f, check=False) + Relative number field morphism: + From: Number Field in b with defining polynomial x^2 - a - 5 over its base field + To: Complex Field with 53 bits of precision + Defn: b |--> 2.30833860703888 + 0.573085617291335*I + a |--> -8.88178419700125e-16 + 2.64575131106459*I + """ + if base_hom is not None: + deprecation(26105, "Use base_map rather than base_hom") + base_map = base_hom + if isinstance(im_gen, NumberFieldHomomorphism_im_gens): + # Then it must be a homomorphism from the corresponding + # absolute number field + abs_hom = im_gen + K = abs_hom.domain() + if K != self.domain().absolute_field(K.variable_name()): + raise TypeError("domain of morphism must be absolute field of domain.") + from_K, to_K = K.structure() + if abs_hom.domain() != K: + raise ValueError("domain of absolute homomorphism must be absolute field of domain.") + if abs_hom.codomain() != self.codomain(): + raise ValueError("codomain of absolute homomorphism must be codomain of this homset.") + return RelativeNumberFieldHomomorphism_from_abs(self, abs_hom) + if isinstance(im_gen, RelativeNumberFieldHomomorphism_from_abs): + return self._coerce_impl(im_gen) + if base_map is None: + base_map = self.default_base_hom() + if isinstance(im_gen, (list, tuple)) and len(im_gen) == 1: + im_gen = im_gen[0] + if check: + im_gen = self.codomain()(im_gen) + return self._from_im(im_gen, base_map=base_map, check=check) + + def _coerce_impl(self, x): + r""" + Canonically coerce ``x`` into this homset. This will only work if ``x`` + is already in the homset. + + EXAMPLES:: + + sage: L. = NumberField([x^3 - x + 1, x^2 + 23]) + sage: E = End(L) + sage: E.coerce(loads(dumps(E[0]))) # indirect doctest + Relative number field endomorphism of Number Field in a with defining polynomial x^3 - x + 1 over its base field + Defn: a |--> a + b |--> b + """ + if not isinstance(x, RelativeNumberFieldHomomorphism_from_abs): + raise TypeError + if x.parent() is self: + return x + if x.parent() == self: + return RelativeNumberFieldHomomorphism_from_abs(self, x.abs_hom()) + raise TypeError + + def _from_im(self, im_gen, base_map, check=True): + """ + Return the homomorphism that acts on the base as given and + sends the generator of the domain to im_gen. + + EXAMPLES:: + + sage: K. = NumberField(x^2 + 23) + sage: L. = K.extension(x^3 - x + 1) + sage: End(L)._from_im( -3/23*a*b^2 + (-9/46*a - 1/2)*b + 2/23*a, K.hom([-a], K)) + Relative number field endomorphism of Number Field in b with defining polynomial x^3 - x + 1 over its base field + Defn: b |--> -3/23*a*b^2 + (-9/46*a - 1/2)*b + 2/23*a + a |--> -a + """ + K = self.domain().absolute_field('a') + from_K, to_K = K.structure() + a = from_K(K.gen()) + # We just have to figure out where a goes to + # under the morphism defined by im_gen and base_map. + L = self.codomain() + R = L['x'] + f = R([base_map(x) for x in a.list()]) + b = f(im_gen) + abs_hom = K.hom([b], check=check) + return RelativeNumberFieldHomomorphism_from_abs(self, abs_hom) + + @cached_method + def default_base_hom(self): + r""" + Pick an embedding of the base field of self into the codomain of this + homset. This is done in an essentially arbitrary way. + + EXAMPLES:: + + sage: L. = NumberField([x^3 - x + 1, x^2 + 23]) + sage: M. = NumberField(x^4 + 80*x^2 + 36) + sage: Hom(L, M).default_base_hom() + Ring morphism: + From: Number Field in b with defining polynomial x^2 + 23 + To: Number Field in c with defining polynomial x^4 + 80*x^2 + 36 + Defn: b |--> 1/12*c^3 + 43/6*c + """ + v = self.domain().base_field().embeddings(self.codomain()) + if len(v) == 0: + raise ValueError("no way to map base field to codomain.") + return v[0] + + @cached_method + def list(self): + """ + Return a list of all the elements of self (for which the domain + is a relative number field). + + EXAMPLES:: + + sage: K. = NumberField([x^2 + x + 1, x^3 + 2]) + sage: End(K).list() + [ + Relative number field endomorphism of Number Field in a with defining polynomial x^2 + x + 1 over its base field + Defn: a |--> a + b |--> b, + ... + Relative number field endomorphism of Number Field in a with defining polynomial x^2 + x + 1 over its base field + Defn: a |--> a + b |--> -b*a - b + ] + + An example with an absolute codomain:: + + sage: K. = NumberField([x^2 - 3, x^2 + 2]) + sage: Hom(K, CyclotomicField(24, 'z')).list() + [ + Relative number field morphism: + From: Number Field in a with defining polynomial x^2 - 3 over its base field + To: Cyclotomic Field of order 24 and degree 8 + Defn: a |--> z^6 - 2*z^2 + b |--> -z^5 - z^3 + z, + ... + Relative number field morphism: + From: Number Field in a with defining polynomial x^2 - 3 over its base field + To: Cyclotomic Field of order 24 and degree 8 + Defn: a |--> -z^6 + 2*z^2 + b |--> z^5 + z^3 - z + ] + """ + D = self.domain() + C = self.codomain() + D_abs = D.absolute_field('a') + v = [self(f, check=False) for f in D_abs.Hom(C).list()] + return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) + + +class CyclotomicFieldHomset(NumberFieldHomset): + """ + Set of homomorphisms with domain a given cyclotomic field. + + EXAMPLES:: + + sage: End(CyclotomicField(16)) + Automorphism group of Cyclotomic Field of order 16 and degree 8 + """ + def __call__(self, im_gens, check=True): + """ + Create an element of this homset. + + EXAMPLES:: + + sage: K. = CyclotomicField(16) + sage: E = End(K) + sage: E(E[0]) # indirect doctest + Ring endomorphism of Cyclotomic Field of order 16 and degree 8 + Defn: z |--> z + sage: E(z^5) # indirect doctest + Ring endomorphism of Cyclotomic Field of order 16 and degree 8 + Defn: z |--> z^5 + sage: E(z^6) # indirect doctest + Traceback (most recent call last): + ... + TypeError: images do not define a valid homomorphism + """ + if isinstance(im_gens, CyclotomicFieldHomomorphism_im_gens): + return self._coerce_impl(im_gens) + try: + return CyclotomicFieldHomomorphism_im_gens(self, im_gens, check=check) + except (NotImplementedError, ValueError): + try: + return self._coerce_impl(im_gens) + except TypeError: + raise TypeError("images do not define a valid homomorphism") + + def _coerce_impl(self, x): + r""" + Coerce ``x`` into self. This will only work if ``x`` is already in self. + + EXAMPLES:: + + sage: E = End(CyclotomicField(16)) + sage: E.coerce(E[0]) # indirect doctest + Ring endomorphism of Cyclotomic Field of order 16 and degree 8 + Defn: zeta16 |--> zeta16 + sage: E.coerce(17) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Integer Ring to Automorphism group of Cyclotomic Field of order 16 and degree 8 + """ + if not isinstance(x, CyclotomicFieldHomomorphism_im_gens): + raise TypeError + if x.parent() is self: + return x + if x.parent() == self: + return CyclotomicFieldHomomorphism_im_gens(self, x.im_gens()) + raise TypeError + + @cached_method + def list(self): + """ + Return a list of all the elements of self (for which the domain + is a cyclotomic field). + + EXAMPLES:: + + sage: K. = CyclotomicField(12) + sage: G = End(K); G + Automorphism group of Cyclotomic Field of order 12 and degree 4 + sage: [g(z) for g in G] + [z, z^3 - z, -z, -z^3 + z] + sage: L. = NumberField([x^2 + x + 1, x^4 + 1]) + sage: L + Number Field in a with defining polynomial x^2 + x + 1 over its base field + sage: Hom(CyclotomicField(12), L)[3] + Ring morphism: + From: Cyclotomic Field of order 12 and degree 4 + To: Number Field in a with defining polynomial x^2 + x + 1 over its base field + Defn: zeta12 |--> -b^2*a + sage: list(Hom(CyclotomicField(5), K)) + [] + sage: Hom(CyclotomicField(11), L).list() + [] + """ + D = self.domain() + C = self.codomain() + z = D.gen() + n = z.multiplicative_order() + if not n.divides(C.zeta_order()): + v =[] + else: + if D == C: + w = z + else: + w = C.zeta(n) + v = [self([w**k], check=False) for k in Zmod(n) if k.is_unit()] + return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) diff --git a/src/sage/rings/number_field/morphism.py b/src/sage/rings/number_field/morphism.py index b740969ba91..885431c4436 100644 --- a/src/sage/rings/number_field/morphism.py +++ b/src/sage/rings/number_field/morphism.py @@ -5,245 +5,24 @@ fields (i.e. field embeddings). """ +#***************************************************************************** +# Copyright (C) 2007 William Stein +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + from sage.misc.cachefunc import cached_method -from sage.misc.superseded import deprecation +from sage.misc.lazy_import import lazy_import -from sage.rings.homset import RingHomset_generic from sage.rings.morphism import RingHomomorphism_im_gens, RingHomomorphism -from sage.rings.integer import Integer -from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.structure.sequence import Sequence from sage.structure.richcmp import richcmp -class NumberFieldHomset(RingHomset_generic): - """ - Set of homomorphisms with domain a given number field. - - TESTS:: - - sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) - sage: TestSuite(H).run() - Failure in _test_category: - ... - The following tests failed: _test_elements - """ - def __init__(self, R, S, category=None): - """ - TESTS: - - Check that :trac:`23647` is fixed:: - - sage: K. = NumberField([x^2 - 2, x^2 - 3]) - sage: e, u, v, w = End(K) - sage: e.abs_hom().parent().category() - Category of homsets of number fields - sage: (v*v).abs_hom().parent().category() - Category of homsets of number fields - """ - if category is None: - from sage.categories.all import Fields, NumberFields - if S in NumberFields: - category = NumberFields() - elif S in Fields: - category = Fields() - RingHomset_generic.__init__(self, R, S, category) - - def __call__(self, im_gens, check=True): - """ - Create the homomorphism sending the generators to ``im_gens``. - - EXAMPLES:: - - sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) - sage: phi = H([H.domain().gen()]); phi # indirect doctest - Ring morphism: - From: Number Field in a with defining polynomial x^2 + 1 with a = 1*I - To: Number Field in b with defining polynomial x^2 + 1 with b = 1*I - Defn: a |--> b - """ - if isinstance(im_gens, NumberFieldHomomorphism_im_gens): - return self._coerce_impl(im_gens) - try: - return NumberFieldHomomorphism_im_gens(self, im_gens, check=check) - except (NotImplementedError, ValueError): - try: - return self._coerce_impl(im_gens) - except TypeError: - raise TypeError("images do not define a valid homomorphism") - - def _coerce_impl(self, x): - r""" - Canonical coercion of ``x`` into this homset. The only things that - coerce canonically into self are elements of self and of homsets equal - to self. - - EXAMPLES:: - - sage: H1 = End(QuadraticField(-1, 'a')) - sage: H1.coerce(loads(dumps(H1[1]))) # indirect doctest - Ring endomorphism of Number Field in a with defining polynomial x^2 + 1 with a = 1*I - Defn: a |--> -a - - TESTS: - - We can move morphisms between categories:: - - sage: f = H1.an_element() - sage: g = End(H1.domain(), category=Rings())(f) - sage: f == End(H1.domain(), category=NumberFields())(g) - True - """ - if not isinstance(x, NumberFieldHomomorphism_im_gens): - raise TypeError - if x.parent() is self: - return x - from sage.categories.all import NumberFields, Rings - if (x.parent() == self or - (x.domain() == self.domain() and x.codomain() == self.codomain() and - # This would be the better check, however it returns False currently: - # self.homset_category().is_full_subcategory(x.category_for()) - # So we check instead that this is a morphism anywhere between - # Rings and NumberFields where the hom spaces do not change. - NumberFields().is_subcategory(self.homset_category()) and - self.homset_category().is_subcategory(Rings()) and - NumberFields().is_subcategory(x.category_for()) and - x.category_for().is_subcategory(Rings()))): - return NumberFieldHomomorphism_im_gens(self, x.im_gens(), check=False) - raise TypeError - - def _an_element_(self): - r""" - Return an element of this set of embeddings. - - EXAMPLES:: - - sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b')) - sage: H.an_element() # indirect doctest - Ring morphism: - From: Number Field in a with defining polynomial x^2 + 1 with a = 1*I - To: Number Field in b with defining polynomial x^2 + 1 with b = 1*I - Defn: a |--> b - - sage: H = Hom(QuadraticField(-1, 'a'), QuadraticField(-2, 'b')) - sage: H.an_element() - Traceback (most recent call last): - ... - EmptySetError: There is no morphism from Number Field in a with defining polynomial x^2 + 1 with a = 1*I to Number Field in b with defining polynomial x^2 + 2 with b = 1.414213562373095?*I - """ - L = self.list() - if len(L) != 0: - return L[0] - else: - from sage.categories.sets_cat import EmptySetError - raise EmptySetError("There is no morphism from {} to {}".format( - self.domain(), self.codomain())) - - def _repr_(self): - r""" - String representation of this homset. - - EXAMPLES:: - - sage: repr(Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'b'))) # indirect doctest - 'Set of field embeddings from Number Field in a with defining polynomial x^2 + 1 with a = 1*I to Number Field in b with defining polynomial x^2 + 1 with b = 1*I' - sage: repr(Hom(QuadraticField(-1, 'a'), QuadraticField(-1, 'a'))) # indirect doctest - 'Automorphism group of Number Field in a with defining polynomial x^2 + 1 with a = 1*I' - """ - D = self.domain() - C = self.codomain() - if C == D: - return "Automorphism group of {}".format(D) - else: - return "Set of field embeddings from {} to {}".format(D, C) - - def order(self): - """ - Return the order of this set of field homomorphism. - - EXAMPLES:: - - sage: k. = NumberField(x^2 + 1) - sage: End(k) - Automorphism group of Number Field in a with defining polynomial x^2 + 1 - sage: End(k).order() - 2 - sage: k. = NumberField(x^3 + 2) - sage: End(k).order() - 1 - - sage: K. = NumberField( [x^3 + 2, x^2 + x + 1] ) - sage: End(K).order() - 6 - """ - return Integer(len(self.list())) - - cardinality = order - - @cached_method - def list(self): - """ - Return a list of all the elements of self. - - EXAMPLES:: - - sage: K. = NumberField(x^3 - 3*x + 1) - sage: End(K).list() - [ - Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 - Defn: a |--> a, - Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 - Defn: a |--> a^2 - 2, - Ring endomorphism of Number Field in a with defining polynomial x^3 - 3*x + 1 - Defn: a |--> -a^2 - a + 2 - ] - sage: Hom(K, CyclotomicField(9))[0] # indirect doctest - Ring morphism: - From: Number Field in a with defining polynomial x^3 - 3*x + 1 - To: Cyclotomic Field of order 9 and degree 6 - Defn: a |--> -zeta9^4 + zeta9^2 - zeta9 - - An example where the codomain is a relative extension:: - - sage: K. = NumberField(x^3 - 2) - sage: L. = K.extension(x^2 + 3) - sage: Hom(K, L).list() - [ - Ring morphism: - From: Number Field in a with defining polynomial x^3 - 2 - To: Number Field in b with defining polynomial x^2 + 3 over its base field - Defn: a |--> a, - Ring morphism: - From: Number Field in a with defining polynomial x^3 - 2 - To: Number Field in b with defining polynomial x^2 + 3 over its base field - Defn: a |--> -1/2*a*b - 1/2*a, - Ring morphism: - From: Number Field in a with defining polynomial x^3 - 2 - To: Number Field in b with defining polynomial x^2 + 3 over its base field - Defn: a |--> 1/2*a*b - 1/2*a - ] - """ - D = self.domain() - C = self.codomain() - if D.degree().divides(C.absolute_degree()): - roots = D.polynomial().roots(ring=C, multiplicities=False) - v = [D.hom([r], codomain=C, check=False) for r in roots] - else: - v = [] - return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) - - def __getitem__(self, n): - r""" - Return the ``n``th element of ``self.list()``. - - EXAMPLES:: - - sage: End(CyclotomicField(37))[3] # indirect doctest - Ring endomorphism of Cyclotomic Field of order 37 and degree 36 - Defn: zeta37 |--> zeta37^4 - """ - return self.list()[n] - class NumberFieldHomomorphism_im_gens(RingHomomorphism_im_gens): def __invert__(self): r""" @@ -363,228 +142,6 @@ def preimage(self, y): raise ValueError("Element '{}' is not in the image of this homomorphism.".format(y)) return VtoK(xvec) # pass from the vector space representation of K back to a point in K -class RelativeNumberFieldHomset(NumberFieldHomset): - """ - Set of homomorphisms with domain a given relative number field. - - EXAMPLES: - - We construct a homomorphism from a relative field by giving - the image of a generator:: - - sage: L. = CyclotomicField(3).extension(x^3 - 2) - sage: phi = L.hom([cuberoot2 * zeta3]); phi - Relative number field endomorphism of Number Field in cuberoot2 with defining polynomial x^3 - 2 over its base field - Defn: cuberoot2 |--> zeta3*cuberoot2 - zeta3 |--> zeta3 - sage: phi(cuberoot2 + zeta3) - zeta3*cuberoot2 + zeta3 - - In fact, this phi is a generator for the Kummer Galois group of this - cyclic extension:: - - sage: phi(phi(cuberoot2 + zeta3)) - (-zeta3 - 1)*cuberoot2 + zeta3 - sage: phi(phi(phi(cuberoot2 + zeta3))) - cuberoot2 + zeta3 - """ - def __call__(self, im_gen, base_map=None, base_hom=None, check=True): - r""" - Create a homomorphism in this homset from the given data, which can be: - - - A homomorphism from this number field. - - A homomorphism from the absolute number field corresponding to this - relative number field. - - An element (specifying the image of the generator) of a ring into - which the base ring coerces. - - A pair consisting of an element of a ring R and a homomorphism from - the base ring to R. - - EXAMPLES:: - - sage: K. = NumberField(x^2 + 1) - sage: L. = K.extension(x^4 - 2) - sage: E = End(L) - sage: E(E[0]) # indirect doctest - Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field - Defn: b |--> b - a |--> a - sage: E(L.absolute_field('c').hom(b+a, L)) # indirect doctest - Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field - Defn: b |--> b - a |--> -a - sage: E(-b*a) # indirect doctest - Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field - Defn: b |--> -a*b - a |--> a - sage: E(-a*b, K.hom([-a])) # indirect doctest - Relative number field endomorphism of Number Field in b with defining polynomial x^4 - 2 over its base field - Defn: b |--> -a*b - a |--> -a - - You can specify a map on the base field:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^2 + 1) - sage: L. = K.extension(x^2-17) - sage: cc = K.hom([-i]) - sage: phi = L.hom([-b],base_map=cc); phi - Relative number field endomorphism of Number Field in b with defining polynomial x^2 - 17 over its base field - Defn: b |--> -b - i |--> -i - - Using check=False, it is possible to construct homomorphisms into fields such as CC - where calculations are only approximate. - - sage: K. = QuadraticField(-7) - sage: f = K.hom([CC(sqrt(-7))], check=False) - sage: x = polygen(K) - sage: L. = K.extension(x^2 - a - 5) - sage: L.Hom(CC)(f(a + 5).sqrt(), f, check=False) - Relative number field morphism: - From: Number Field in b with defining polynomial x^2 - a - 5 over its base field - To: Complex Field with 53 bits of precision - Defn: b |--> 2.30833860703888 + 0.573085617291335*I - a |--> -8.88178419700125e-16 + 2.64575131106459*I - """ - if base_hom is not None: - deprecation(26105, "Use base_map rather than base_hom") - base_map = base_hom - if isinstance(im_gen, NumberFieldHomomorphism_im_gens): - # Then it must be a homomorphism from the corresponding - # absolute number field - abs_hom = im_gen - K = abs_hom.domain() - if K != self.domain().absolute_field(K.variable_name()): - raise TypeError("domain of morphism must be absolute field of domain.") - from_K, to_K = K.structure() - if abs_hom.domain() != K: - raise ValueError("domain of absolute homomorphism must be absolute field of domain.") - if abs_hom.codomain() != self.codomain(): - raise ValueError("codomain of absolute homomorphism must be codomain of this homset.") - return RelativeNumberFieldHomomorphism_from_abs(self, abs_hom) - if isinstance(im_gen, RelativeNumberFieldHomomorphism_from_abs): - return self._coerce_impl(im_gen) - if base_map is None: - base_map = self.default_base_hom() - if isinstance(im_gen, (list, tuple)) and len(im_gen) == 1: - im_gen = im_gen[0] - if check: - im_gen = self.codomain()(im_gen) - return self._from_im(im_gen, base_map=base_map, check=check) - - def _coerce_impl(self, x): - r""" - Canonically coerce ``x`` into this homset. This will only work if ``x`` - is already in the homset. - - EXAMPLES:: - - sage: L. = NumberField([x^3 - x + 1, x^2 + 23]) - sage: E = End(L) - sage: E.coerce(loads(dumps(E[0]))) # indirect doctest - Relative number field endomorphism of Number Field in a with defining polynomial x^3 - x + 1 over its base field - Defn: a |--> a - b |--> b - """ - if not isinstance(x, RelativeNumberFieldHomomorphism_from_abs): - raise TypeError - if x.parent() is self: - return x - if x.parent() == self: - return RelativeNumberFieldHomomorphism_from_abs(self, x.abs_hom()) - raise TypeError - - def _from_im(self, im_gen, base_map, check=True): - """ - Return the homomorphism that acts on the base as given and - sends the generator of the domain to im_gen. - - EXAMPLES:: - - sage: K. = NumberField(x^2 + 23) - sage: L. = K.extension(x^3 - x + 1) - sage: End(L)._from_im( -3/23*a*b^2 + (-9/46*a - 1/2)*b + 2/23*a, K.hom([-a], K)) - Relative number field endomorphism of Number Field in b with defining polynomial x^3 - x + 1 over its base field - Defn: b |--> -3/23*a*b^2 + (-9/46*a - 1/2)*b + 2/23*a - a |--> -a - """ - K = self.domain().absolute_field('a') - from_K, to_K = K.structure() - a = from_K(K.gen()) - # We just have to figure out where a goes to - # under the morphism defined by im_gen and base_map. - L = self.codomain() - R = L['x'] - f = R([base_map(x) for x in a.list()]) - b = f(im_gen) - abs_hom = K.hom([b], check=check) - return RelativeNumberFieldHomomorphism_from_abs(self, abs_hom) - - @cached_method - def default_base_hom(self): - r""" - Pick an embedding of the base field of self into the codomain of this - homset. This is done in an essentially arbitrary way. - - EXAMPLES:: - - sage: L. = NumberField([x^3 - x + 1, x^2 + 23]) - sage: M. = NumberField(x^4 + 80*x^2 + 36) - sage: Hom(L, M).default_base_hom() - Ring morphism: - From: Number Field in b with defining polynomial x^2 + 23 - To: Number Field in c with defining polynomial x^4 + 80*x^2 + 36 - Defn: b |--> 1/12*c^3 + 43/6*c - """ - v = self.domain().base_field().embeddings(self.codomain()) - if len(v) == 0: - raise ValueError("no way to map base field to codomain.") - return v[0] - - @cached_method - def list(self): - """ - Return a list of all the elements of self (for which the domain - is a relative number field). - - EXAMPLES:: - - sage: K. = NumberField([x^2 + x + 1, x^3 + 2]) - sage: End(K).list() - [ - Relative number field endomorphism of Number Field in a with defining polynomial x^2 + x + 1 over its base field - Defn: a |--> a - b |--> b, - ... - Relative number field endomorphism of Number Field in a with defining polynomial x^2 + x + 1 over its base field - Defn: a |--> a - b |--> -b*a - b - ] - - An example with an absolute codomain:: - - sage: K. = NumberField([x^2 - 3, x^2 + 2]) - sage: Hom(K, CyclotomicField(24, 'z')).list() - [ - Relative number field morphism: - From: Number Field in a with defining polynomial x^2 - 3 over its base field - To: Cyclotomic Field of order 24 and degree 8 - Defn: a |--> z^6 - 2*z^2 - b |--> -z^5 - z^3 + z, - ... - Relative number field morphism: - From: Number Field in a with defining polynomial x^2 - 3 over its base field - To: Cyclotomic Field of order 24 and degree 8 - Defn: a |--> -z^6 + 2*z^2 - b |--> z^5 + z^3 - z - ] - """ - D = self.domain() - C = self.codomain() - D_abs = D.absolute_field('a') - v = [self(f, check=False) for f in D_abs.Hom(C).list()] - return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) class RelativeNumberFieldHomomorphism_from_abs(RingHomomorphism): r""" @@ -698,106 +255,10 @@ def _call_(self, x): return self._abs_hom(self._to_K(x)) -class CyclotomicFieldHomset(NumberFieldHomset): - """ - Set of homomorphisms with domain a given cyclotomic field. - - EXAMPLES:: - - sage: End(CyclotomicField(16)) - Automorphism group of Cyclotomic Field of order 16 and degree 8 - """ - def __call__(self, im_gens, check=True): - """ - Create an element of this homset. - - EXAMPLES:: - - sage: K. = CyclotomicField(16) - sage: E = End(K) - sage: E(E[0]) # indirect doctest - Ring endomorphism of Cyclotomic Field of order 16 and degree 8 - Defn: z |--> z - sage: E(z^5) # indirect doctest - Ring endomorphism of Cyclotomic Field of order 16 and degree 8 - Defn: z |--> z^5 - sage: E(z^6) # indirect doctest - Traceback (most recent call last): - ... - TypeError: images do not define a valid homomorphism - """ - if isinstance(im_gens, CyclotomicFieldHomomorphism_im_gens): - return self._coerce_impl(im_gens) - try: - return CyclotomicFieldHomomorphism_im_gens(self, im_gens, check=check) - except (NotImplementedError, ValueError): - try: - return self._coerce_impl(im_gens) - except TypeError: - raise TypeError("images do not define a valid homomorphism") - - def _coerce_impl(self, x): - r""" - Coerce ``x`` into self. This will only work if ``x`` is already in self. - - EXAMPLES:: - - sage: E = End(CyclotomicField(16)) - sage: E.coerce(E[0]) # indirect doctest - Ring endomorphism of Cyclotomic Field of order 16 and degree 8 - Defn: zeta16 |--> zeta16 - sage: E.coerce(17) # indirect doctest - Traceback (most recent call last): - ... - TypeError: no canonical coercion from Integer Ring to Automorphism group of Cyclotomic Field of order 16 and degree 8 - """ - if not isinstance(x, CyclotomicFieldHomomorphism_im_gens): - raise TypeError - if x.parent() is self: - return x - if x.parent() == self: - return CyclotomicFieldHomomorphism_im_gens(self, x.im_gens()) - raise TypeError - - @cached_method - def list(self): - """ - Return a list of all the elements of self (for which the domain - is a cyclotomic field). - - EXAMPLES:: - - sage: K. = CyclotomicField(12) - sage: G = End(K); G - Automorphism group of Cyclotomic Field of order 12 and degree 4 - sage: [g(z) for g in G] - [z, z^3 - z, -z, -z^3 + z] - sage: L. = NumberField([x^2 + x + 1, x^4 + 1]) - sage: L - Number Field in a with defining polynomial x^2 + x + 1 over its base field - sage: Hom(CyclotomicField(12), L)[3] - Ring morphism: - From: Cyclotomic Field of order 12 and degree 4 - To: Number Field in a with defining polynomial x^2 + x + 1 over its base field - Defn: zeta12 |--> -b^2*a - sage: list(Hom(CyclotomicField(5), K)) - [] - sage: Hom(CyclotomicField(11), L).list() - [] - """ - D = self.domain() - C = self.codomain() - z = D.gen() - n = z.multiplicative_order() - if not n.divides(C.zeta_order()): - v =[] - else: - if D == C: - w = z - else: - w = C.zeta(n) - v = [self([w**k], check=False) for k in Zmod(n) if k.is_unit()] - return Sequence(v, universe=self, check=False, immutable=True, cr=v!=[]) - class CyclotomicFieldHomomorphism_im_gens(NumberFieldHomomorphism_im_gens): pass + + +lazy_import('sage.rings.number_field.homset', + ('NumberFieldHomset', 'RelativeNumberFieldHomset', 'CyclotomicFieldHomset'), + deprecation=29010) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index f2996456448..8268f5f4a54 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -153,7 +153,7 @@ def is_NumberFieldHomsetCodomain(codomain): Returns whether ``codomain`` is a valid codomain for a number field homset. This is used by NumberField._Hom_ to determine whether the created homsets should be a - :class:`sage.rings.number_field.morphism.NumberFieldHomset`. + :class:`sage.rings.number_field.homset.NumberFieldHomset`. EXAMPLES: @@ -932,6 +932,11 @@ def QuadraticField(D, name='a', check=True, embedding=True, latex_name='sqrt', * False sage: QuadraticField(-11, 'a') is QuadraticField(-11, 'a', latex_name=None) False + + Check quadratic fields without embedding (:trac:`28932`):: + + sage: QuadraticField(3, embedding=False) + Number Field in a with defining polynomial x^2 - 3 """ D = QQ(D) if check: @@ -944,6 +949,8 @@ def QuadraticField(D, name='a', check=True, embedding=True, latex_name='sqrt', * embedding = RLF(D).sqrt() else: embedding = CLF(D).sqrt() + elif embedding is False: + embedding = None if latex_name == 'sqrt': latex_name = r'\sqrt{%s}' % D return NumberField(f, name, check=False, embedding=embedding, latex_name=latex_name, **args) @@ -1917,7 +1924,7 @@ def _Hom_(self, codomain, category=None): # Using LazyFormat fixes #28036 - infinite loop from sage.misc.lazy_format import LazyFormat raise TypeError(LazyFormat("%s is not suitable as codomain for homomorphisms from %s") % (codomain, self)) - from .morphism import NumberFieldHomset + from sage.rings.number_field.homset import NumberFieldHomset return NumberFieldHomset(self, codomain, category) @cached_method @@ -10561,8 +10568,8 @@ def _Hom_(self, codomain, cat=None): Automorphism group of Cyclotomic Field of order 21 and degree 12 """ if is_NumberFieldHomsetCodomain(codomain): - from . import morphism - return morphism.CyclotomicFieldHomset(self, codomain) + from sage.rings.number_field.homset import CyclotomicFieldHomset + return CyclotomicFieldHomset(self, codomain) else: raise TypeError diff --git a/src/sage/rings/number_field/number_field_rel.py b/src/sage/rings/number_field/number_field_rel.py index 49d1e377d75..ca4ab7294e3 100644 --- a/src/sage/rings/number_field/number_field_rel.py +++ b/src/sage/rings/number_field/number_field_rel.py @@ -765,7 +765,7 @@ def _Hom_(self, codomain, category=None): sage: K.Hom(K) # indirect doctest Automorphism group of Number Field in a with defining polynomial x^3 - 2 over its base field sage: type(K.Hom(K)) - + TESTS:: @@ -776,7 +776,7 @@ def _Hom_(self, codomain, category=None): if not is_NumberFieldHomsetCodomain(codomain): raise TypeError("{} is not suitable as codomain for homomorphisms from {}".format(codomain, self)) - from .morphism import RelativeNumberFieldHomset + from sage.rings.number_field.homset import RelativeNumberFieldHomset return RelativeNumberFieldHomset(self, codomain, category) def _latex_(self): diff --git a/src/sage/rings/padics/misc.py b/src/sage/rings/padics/misc.py index def3b8d177d..3bedd534e69 100644 --- a/src/sage/rings/padics/misc.py +++ b/src/sage/rings/padics/misc.py @@ -138,7 +138,7 @@ def gauss_sum(a, p, f, prec=20, factored=False, algorithm='pari', parent=None): a = (a*p) % (q-1) s = sum(a.digits(base=p)) if factored: - return(s, out) + return s, out X = PolynomialRing(R, name='X').gen() pi = R.ext(X**(p - 1) + p, names='pi').gen() out *= pi**s diff --git a/src/sage/rings/padics/padic_generic_element.pxd b/src/sage/rings/padics/padic_generic_element.pxd index 9166e3fcf2c..780aa4655bf 100644 --- a/src/sage/rings/padics/padic_generic_element.pxd +++ b/src/sage/rings/padics/padic_generic_element.pxd @@ -1,3 +1,5 @@ +from cpython cimport array + cimport sage.structure.element from sage.libs.gmp.types cimport mpz_t, mpq_t from sage.structure.element cimport Element, RingElement @@ -6,6 +8,8 @@ from sage.rings.padics.pow_computer cimport PowComputer_class from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational +cpdef gauss_table(long long p, int f, int prec, bint use_longs) + cdef class pAdicGenericElement(LocalGenericElement): cdef long valuation_c(self) cpdef val_unit(self) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 11968020ce8..655c60f5f46 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -28,6 +28,7 @@ AUTHORS: #***************************************************************************** from sage.ext.stdsage cimport PY_NEW +from cysignals.memory cimport sig_malloc, sig_free cimport sage.rings.padics.local_generic_element from sage.libs.gmp.mpz cimport mpz_set_si @@ -42,7 +43,6 @@ from sage.structure.richcmp cimport rich_to_bool cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 - cdef class pAdicGenericElement(LocalGenericElement): cpdef _richcmp_(left, right, int op): """ @@ -1230,7 +1230,7 @@ cdef class pAdicGenericElement(LocalGenericElement): INPUT: - - ``bd`` -- integer. Is a bound for precision, defaults to 20 + - ``bd`` -- integer. Precision bound, defaults to 20 - ``a`` -- integer. Offset parameter, defaults to 0 OUTPUT: @@ -1265,6 +1265,8 @@ cdef class pAdicGenericElement(LocalGenericElement): 4 + 4*5 + 4*5^2 + 4*5^3 + 2*5^4 + 4*5^5 + 5^7 + 3*5^9 + 4*5^10 + 3*5^11 + 5^13 + 4*5^14 + 2*5^15 + 2*5^16 + 2*5^17 + 3*5^18 + O(5^20) + TESTS: + This test was added in :trac:`24433`:: sage: F = Qp(7) @@ -1274,36 +1276,19 @@ cdef class pAdicGenericElement(LocalGenericElement): 6 + 4*7^19 + O(7^20) """ R = self.parent() - cdef int p = R.prime() - cdef int b = a - cdef int k + p = R.prime() - s = R.zero().add_bigoh(bd) - t = R.one().add_bigoh(bd) + # If p == 2, must work in Qp rather than Zp. + if p == 2 and not R.is_field(): + S = R.fraction_field() + return R(S(self).dwork_expansion(bd, a)) try: v = R.dwork_coeffs + if len(v) < p*bd: + raise AttributeError except AttributeError: - v = None - if v is not None and len(v) < p * bd: - v = None - if v is not None: - for k in range(bd): - s += t * v[p*k+b] - t *= (self + k) - else: - u = [t] - v = [] - for j in range(1, p): - u.append(u[j-1] / j) - for k in range(bd): - v += [x << k for x in u] - s += t * (u[a] << k) - t *= (self + k) - u[0] = ((u[-1] + u[0]) / (k+1)) >> 1 - for j in range(1, p): - u[j] = (u[j-1] + u[j]) / (j + (k+1) * p ) - R.dwork_coeffs = v - return -s + v = dwork_mahler_coeffs(R, bd) + return evaluate_dwork_mahler(v, self, p, bd, a) def gamma(self, algorithm='pari'): r""" @@ -1410,11 +1395,9 @@ cdef class pAdicGenericElement(LocalGenericElement): return parent(self.__pari__().gamma()) elif algorithm == 'sage': p = parent.prime() - bd = -((-n*p)//(p-1)) + bd = -((-n*p) // (p-1)) k = (-self) % p x = (self+k) >> 1 - if p==2 and n>=3: - x = x.lift_to_precision(n) return -x.dwork_expansion(bd, k.lift()) @coerce_binop @@ -1438,7 +1421,7 @@ cdef class pAdicGenericElement(LocalGenericElement): 0 and 3 in the 3-adic ring `\ZZ_3`. The greatest common divisor of `O(3)` and `O(3)` could be (among others) 3 or 0 which have different valuation. The algorithm implemented here, will - return an element of minimal valuation among the possible greatest + return an element of minimal valuation among the possible greatest common divisors. EXAMPLES: @@ -4411,3 +4394,191 @@ def _compute_g(p, n, prec, terms): for i in range(n): g[i+1] = -(g[i]/(v-v**2)).integral() return [x.truncate(terms) for x in g] + +cpdef dwork_mahler_coeffs(R, int bd=20): + r""" + Compute Dwork's formula for Mahler coefficients of `p`-adic Gamma. + + This is called internally when one computes Gamma for a `p`-adic + integer. Normally there is no need to call it directly. + + INPUT: + + - ``R`` -- p-adic ring in which to compute + - ``bd`` -- integer. Number of terms in the expansion to use + + OUTPUT: + + A list of `p`-adic integers. + + EXAMPLES:: + + sage: from sage.rings.padics.padic_generic_element import dwork_mahler_coeffs, evaluate_dwork_mahler + sage: R = Zp(3) + sage: v = dwork_mahler_coeffs(R) + sage: x = R(1/7) + sage: evaluate_dwork_mahler(v, x, 3, 20, 1) + 2 + 2*3 + 3^2 + 3^3 + 3^4 + 3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + 2*3^11 + 2*3^12 + 3^13 + 3^14 + 2*3^16 + 3^17 + 3^19 + O(3^20) + sage: x.dwork_expansion(a=1) # Same result + 2 + 2*3 + 3^2 + 3^3 + 3^4 + 3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + 2*3^11 + 2*3^12 + 3^13 + 3^14 + 2*3^16 + 3^17 + 3^19 + O(3^20) + """ + from sage.rings.padics.factory import Qp + cdef int i + cdef long k, p + + v = [R.one()] + p = R.prime() + for k in range(1, p): + v.append(v[-1] / R(k)) + if bd > 1: + R1 = Qp(p, prec=bd) # Need divisions in this calculation + u = [R1(x) for x in v] + for i in range(1, bd): + u[0] = ((u[-1] + u[0]) / i) >> 1 + for j in range(1, p): + u[j] = (u[j-1] + u[j]) / (j + i * p) + for x in u: + v.append(R(x << i)) + return v + +cpdef evaluate_dwork_mahler(v, x, long long p, int bd, long long a): + """ + Evaluate Dwork's Mahler series for `p`-adic Gamma. + + EXAMPLES:: + + sage: from sage.rings.padics.padic_generic_element import dwork_mahler_coeffs, evaluate_dwork_mahler + sage: R = Zp(3) + sage: v = dwork_mahler_coeffs(R) + sage: x = R(1/7) + sage: evaluate_dwork_mahler(v, x, 3, 20, 1) + 2 + 2*3 + 3^2 + 3^3 + 3^4 + 3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + 2*3^11 + 2*3^12 + 3^13 + 3^14 + 2*3^16 + 3^17 + 3^19 + O(3^20) + sage: x.dwork_expansion(a=1) # Same result + 2 + 2*3 + 3^2 + 3^3 + 3^4 + 3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + 2*3^11 + 2*3^12 + 3^13 + 3^14 + 2*3^16 + 3^17 + 3^19 + O(3^20) + """ + cdef int k + bd -= 1 + a1 = a + bd*p + s = v[a1] + u = x + bd + one = x.parent().one() + for k in range(bd): + a1 -= p + u -= one + s = s*u + v[a1] + return -s + +cdef long long evaluate_dwork_mahler_long(array.array v, long long x, long long p, int bd, + long long a, long long q): + cdef int k + cdef long long a1, s, u + bd -= 1 + a1 = a + bd*p + s = v[a1] + u = x + bd + for k in range(bd): + a1 -= p + u -= 1 + s = s*u + v[a1] # force cast to long long + s = s % q + return -s + +cpdef gauss_table(long long p, int f, int prec, bint use_longs): + r""" + Compute a table of Gauss sums using the Gross-Koblitz formula. + + This is used in the computation of L-functions of hypergeometric motives. + The Gross-Koblitz formula is used as in `sage.rings.padics.misc.gauss_sum`, + but further unpacked for efficiency. + + INPUT: + + - `p` - prime + - `f`, `prec` - positive integers + - `use_longs` - boolean; if True, computations are done in C long long + integers rather than Sage `p`-adics, and the results are returned + as a Python array rather than a list. + + OUTPUT: + + A list of length `q-1=p^f-1`. The entries are `p`-adic units created with + absolute precision `prec`. + + EXAMPLES:: + + sage: from sage.rings.padics.padic_generic_element import gauss_table + sage: gauss_table(2,2,4,False) + [1 + 2 + 2^2 + 2^3, 1 + 2 + 2^2 + 2^3, 1 + 2 + 2^2 + 2^3] + sage: gauss_table(3,2,4,False)[3] + 2 + 3 + 2*3^2 + """ + from sage.rings.padics.factory import Zp, Qp + + cdef int i, j, bd + cdef long long q, q1, q3, r, r1, r2, s1, s2, k + cdef array.array vv, ans1 + + if (f == 1 and prec == 1): # Shortcut for this key special case + ans1 = array.array('l', [0]) * p + ans1[0] = p-1 + for r in range(1, p-1): + k = ans1[r-1] + ans1[r] = k * r % p + return ans1 + + q = p ** f + q1 = q - 1 + bd = (p*prec+p-2) // (p-1) - 1 + R = Zp(p, prec, 'fixed-mod') + if p == 2: # Dwork expansion has denominators when p = 2 + R1 = Qp(p, prec) + use_longs = False + else: + R1 = R + d = ~R1(q1) + v = dwork_mahler_coeffs(R1, bd) + if use_longs: + q3 = p ** prec + r2 = d.lift() % q3 + vv = array.array('l', [0]) * len(v) + for k in range(len(v)): + vv[k] = v[k].lift() % q3 + ans1 = array.array('l', [0]) * q1 + ans1[0] = -1 + ans = ans1 + else: + u = R1.one() + ans = [0 for r in range(q1)] + ans[0] = -u + for r in range(1, q1): + if ans[r]: continue + if use_longs: + s1 = 1 + else: + s = u + r1 = r + for j in range(1, f+1): + k = r1 % p + r1 = (r1 + k * q1) // p + if use_longs: # Use Dwork expansion to compute p-adic Gamma + s1 *= -evaluate_dwork_mahler_long(vv, r1*r2%q3, p, bd, k, q3) + s1 %= q3 + else: + s *= -evaluate_dwork_mahler(v, R1(r1)*d, p, bd, k) + if r1 == r: + break + if use_longs: + if j < f: + s2 = s1 + for i in range(f//j-1): + s1 = s1 * s2 % q3 + ans1[r] = -s1 + else: + if j < f: + s **= f // j + ans[r] = -s + for i in range(j-1): + r1 = r1 * p % q1 # Initially r1 == r + ans[r1] = ans[r] + if p != 2: return ans + return [R(x) for x in ans] diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 74f964c9845..caaf1650e9b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -16,32 +16,6 @@ supported by this implementation: - and absolute number fields `\QQ(a)`. -AUTHORS: - -The libSINGULAR interface was implemented by - -- Martin Albrecht (2007-01): initial implementation - -- Joel Mohler (2008-01): misc improvements, polishing - -- Martin Albrecht (2008-08): added `\QQ(a)` and `\ZZ` support - -- Simon King (2009-04): improved coercion - -- Martin Albrecht (2009-05): added `\ZZ/n\ZZ` support, refactoring - -- Martin Albrecht (2009-06): refactored the code to allow better - re-use - -- Simon King (2011-03): Use a faster way of conversion from the base - ring. - -- Volker Braun (2011-06): Major cleanup, refcount singular rings, bugfixes. - -.. TODO:: - - Implement Real, Complex coefficient rings via libSINGULAR - EXAMPLES: We show how to construct various multivariate polynomial rings:: @@ -150,6 +124,31 @@ Check if :trac:`6160` is fixed:: sage: R. = K[] sage: b-j*c b - 1728*c + +.. TODO:: + + Implement Real, Complex coefficient rings via libSINGULAR + +AUTHORS: + +- Martin Albrecht (2007-01): initial implementation + +- Joel Mohler (2008-01): misc improvements, polishing + +- Martin Albrecht (2008-08): added `\QQ(a)` and `\ZZ` support + +- Simon King (2009-04): improved coercion + +- Martin Albrecht (2009-05): added `\ZZ/n\ZZ` support, refactoring + +- Martin Albrecht (2009-06): refactored the code to allow better + re-use + +- Simon King (2011-03): use a faster way of conversion from the base + ring. + +- Volker Braun (2011-06): major cleanup, refcount singular rings, bugfixes. + """ #***************************************************************************** @@ -825,7 +824,7 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_base): p_SetExp(mon, pos+1, m[pos], _ring) p_Setm(mon, _ring) #we can use "_m" because we're merging a monomial and - #"Merge" because this monomial is different from the rest + #"Merge" because this monomial is different from the rest sBucket_Merge_m(bucket, mon) e=0 #we can use "Merge" because the monomials are distinct @@ -2590,22 +2589,20 @@ cdef class MPolynomial_libsingular(MPolynomial): def degree(self, MPolynomial_libsingular x=None, int std_grading=False): """ - Return the maximal degree of this polynomial in ``x``, where - ``x`` must be one of the generators for the parent of this - polynomial. + Return the degree of this polynomial. INPUT: - - ``x`` - (default: ``None``) a multivariate polynomial which is (or - coerces to) a generator of the parent of self. If ``x`` is ``None``, - return the total degree, which is the maximum degree of any monomial. - Note that a matrix term ordering alters the grading of the generators - of the ring; see the tests below. To avoid this behavior, use either - ``exponents()`` for the exponents themselves, or the optional - argument ``std_grading=False``. + - ``x`` -- (default: ``None``) a generator of the parent ring OUTPUT: - integer + + If ``x`` is not given, return the maximum degree of the monomials of + the polynomial. Note that the degree of a monomial is affected by the + gradings given to the generators of the parent ring. If ``x`` is given, + it is (or coercible to) a generator of the parent ring and the output + is the maximum degree in ``x``. This is not affected by the gradings of + the generators. EXAMPLES:: @@ -2620,6 +2617,49 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: (y^10*x - 7*x^2*y^5 + 5*x^3).degree(y) 10 + The term ordering of the parent ring determines the grading of the + generators. :: + + sage: T = TermOrder('wdegrevlex', (1,2,3,4)) + sage: R = PolynomialRing(QQ, 'x', 12, order=T+T+T) + sage: [x.degree() for x in R.gens()] + [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] + + A matrix term ordering determines the grading of the generators by the + first row of the matrix. :: + + sage: m = matrix(3, [3,2,1,1,1,0,1,0,0]) + sage: m + [3 2 1] + [1 1 0] + [1 0 0] + sage: R. = PolynomialRing(QQ, order=TermOrder(m)) + sage: x.degree(), y.degree(), z.degree() + (3, 2, 1) + sage: f = x^3*y + x*z^4 + sage: f.degree() + 11 + + If the first row contains zero, the grading becomes the standard one. :: + + sage: m = matrix(3, [3,0,1,1,1,0,1,0,0]) + sage: m + [3 0 1] + [1 1 0] + [1 0 0] + sage: R. = PolynomialRing(QQ, order=TermOrder(m)) + sage: x.degree(), y.degree(), z.degree() + (1, 1, 1) + sage: f = x^3*y + x*z^4 + sage: f.degree() + 5 + + To get the degree with the standard grading regardless of the term + ordering of the parent ring, use ``std_grading=True``. :: + + sage: f.degree(std_grading=True) + 5 + TESTS:: sage: P. = QQ[] @@ -2628,25 +2668,10 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: P(1).degree(x) 0 - With a matrix term ordering, the grading of the generators is - determined by the first row of the matrix. This affects the behavior - of ``degree()`` when no variable is specified. - To evaluate the degree with a standard grading, use the optional - argument ``std_grading=True``. - - sage: tord = TermOrder(matrix([3,0,1,1,1,0,1,0,0])) - sage: R. = PolynomialRing(QQ,3,order=tord) - sage: (x^3*y+x*z^4).degree() - 9 - sage: (x^3*y+x*z^4).degree(std_grading=True) - 5 - sage: x.degree(x), y.degree(y), z.degree(z) - (1, 1, 1) - The following example is inspired by :trac:`11652`:: sage: R. = ZZ[] - sage: poly = p+q^2+t^3 + sage: poly = p + q^2 + t^3 sage: poly = poly.polynomial(t)[0] sage: poly q^2 + p @@ -2657,7 +2682,7 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: poly.degree(q) Traceback (most recent call last): ... - TypeError: argument must canonically coerce to parent + TypeError: argument is not coercible to the parent Using a non-canonical coercion does work, but we require this to be done explicitly, since it can lead to confusing results @@ -2679,7 +2704,7 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: poly.degree(pp+1) Traceback (most recent call last): ... - TypeError: argument must be a generator + TypeError: argument is not a generator Canonical coercions are used:: @@ -2692,18 +2717,17 @@ cdef class MPolynomial_libsingular(MPolynomial): cdef ring *r = self._parent_ring cdef poly *p = self._poly if not x: - if not std_grading: - return singular_polynomial_deg(p,NULL,r) - else: + if std_grading: return self.total_degree(std_grading=True) + return singular_polynomial_deg(p, NULL, r) if not x.parent() is self.parent(): try: x = self.parent().coerce(x) except TypeError: - raise TypeError("argument must canonically coerce to parent") + raise TypeError("argument is not coercible to the parent") if not x.is_generator(): - raise TypeError("argument must be a generator") + raise TypeError("argument is not a generator") return singular_polynomial_deg(p, x._poly, r) @@ -2715,25 +2739,41 @@ cdef class MPolynomial_libsingular(MPolynomial): EXAMPLES:: sage: R. = QQ[] - sage: f=2*x*y^3*z^2 + sage: f = 2*x*y^3*z^2 sage: f.total_degree() 6 - sage: f=4*x^2*y^2*z^3 + sage: f = 4*x^2*y^2*z^3 sage: f.total_degree() 7 - sage: f=99*x^6*y^3*z^9 + sage: f = 99*x^6*y^3*z^9 sage: f.total_degree() 18 - sage: f=x*y^3*z^6+3*x^2 + sage: f = x*y^3*z^6+3*x^2 sage: f.total_degree() 10 - sage: f=z^3+8*x^4*y^5*z + sage: f = z^3+8*x^4*y^5*z sage: f.total_degree() 10 - sage: f=z^9+10*x^4+y^8*x^2 + sage: f = z^9+10*x^4+y^8*x^2 sage: f.total_degree() 10 + A matrix term ordering changes the grading. To get the total degree + using the standard grading, use ``std_grading=True``:: + + sage: tord = TermOrder(matrix(3, [3,2,1,1,1,0,1,0,0])) + sage: tord + Matrix term order with matrix + [3 2 1] + [1 1 0] + [1 0 0] + sage: R. = PolynomialRing(QQ, order=tord) + sage: f = x^2*y + sage: f.total_degree() + 8 + sage: f.total_degree(std_grading=True) + 3 + TESTS:: sage: R. = QQ[] @@ -2741,30 +2781,18 @@ cdef class MPolynomial_libsingular(MPolynomial): -1 sage: R(1).total_degree() 0 - - With a matrix term ordering, the grading changes. - To evaluate the total degree using the standard grading, - use the optional argument``std_grading=True``:: - - sage: tord=TermOrder(matrix([3,0,1,1,1,0,1,0,0])) - sage: R. = PolynomialRing(QQ,3,order=tord) - sage: (x^2*y).total_degree() - 6 - sage: (x^2*y).total_degree(std_grading=True) - 3 """ cdef int i, result cdef poly *p = self._poly cdef ring *r = self._parent_ring - if not std_grading: - return singular_polynomial_deg(p,NULL,r) - else: + if std_grading: result = 0 while p: result = max(result, sum([p_GetExp(p,i,r) for i in xrange(1,r.N+1)])) p = pNext(p) return result + return singular_polynomial_deg(p, NULL, r) def degrees(self): """ diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index fca56f4c76f..389c237d462 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -7988,10 +7988,12 @@ cdef class Polynomial(CommutativeAlgebraElement): [a,b], counted without multiplicity. The endpoints a, b default to -Infinity, Infinity (which are also valid input values). - Calls the PARI routine polsturm. Note that as of version 2.8, PARI - includes the left endpoint of the interval (and no longer uses - Sturm's algorithm on exact inputs). polsturm requires a polynomial - with real coefficients; in case PARI returns an error, we try again + Calls the PARI routine :pari:`polsturm`. + + Note that as of version 2.8, PARI includes the left endpoint + of the interval (and no longer uses Sturm's algorithm on exact + inputs). polsturm requires a polynomial with real + coefficients; in case PARI returns an error, we try again after taking the GCD of `self` with its complex conjugate. EXAMPLES:: @@ -8030,7 +8032,7 @@ cdef class Polynomial(CommutativeAlgebraElement): 3 """ - pol = self // self.gcd(self.derivative()) #squarefree part + pol = self // self.gcd(self.derivative()) # squarefree part if a is None: a1 = -infinity.infinity else: @@ -8040,12 +8042,12 @@ cdef class Polynomial(CommutativeAlgebraElement): else: b1 = b try: - return(pari(pol).polsturm([a1,b1])) + return pari(pol).polsturm([a1, b1]) except PariError: # Take GCD with the conjugate, to extract the maximum factor # with real coefficients. pol2 = pol.gcd(pol.map_coefficients(lambda z: z.conjugate())) - return(pari(pol2).polsturm([a1,b1])) + return pari(pol2).polsturm([a1, b1]) def number_of_real_roots(self): r""" @@ -8092,7 +8094,7 @@ cdef class Polynomial(CommutativeAlgebraElement): True """ pol = self // self.gcd(self.derivative()) - return(pol.number_of_roots_in_interval(a,b) == pol.degree()) + return pol.number_of_roots_in_interval(a, b) == pol.degree() def is_real_rooted(self): r""" @@ -9669,9 +9671,11 @@ cdef class Polynomial(CommutativeAlgebraElement): # stable under squaring. This factor is constant if and only if # the original polynomial has no cyclotomic factor. while True: - if pol1.is_constant(): return(False) + if pol1.is_constant(): + return False pol2 = pol1.gcd(polRing((pol1*pol1(-x)).list()[::2])) - if pol1.degree() == pol2.degree(): return(True) + if pol1.degree() == pol2.degree(): + return True pol1 = pol2 def homogenize(self, var='h'): diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 14cc009a7b0..397e92d0fdc 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -311,7 +311,8 @@ class of the quotient ring and its newly created elements. sage: P. = QQ[] sage: Q = P.quotient(x^2+2) sage: Q.category() - Category of commutative no zero divisors quotients of algebras over Rational Field + Category of commutative no zero divisors quotients of algebras over + (number fields and quotient fields and metric spaces) We verify that the elements belong to the correct element class. Also, we list the attributes that are provided by the element @@ -330,8 +331,8 @@ class of the category, and store the current class of the quotient sage: Q in Fields() True sage: Q.category() - Category of commutative division no zero divisors - quotients of algebras over Rational Field + Category of commutative division no zero divisors quotients of algebras + over (number fields and quotient fields and metric spaces) sage: first_class == Q.__class__ False sage: [s for s in dir(Q.category().element_class) if not s.startswith('_')] @@ -399,7 +400,7 @@ def __init__(self, ring, polynomial, name=None, category=None): self.__ring = ring self.__polynomial = polynomial - category = CommutativeAlgebras(ring.base_ring()).Quotients().or_subcategory(category) + category = CommutativeAlgebras(ring.base_ring().category()).Quotients().or_subcategory(category) CommutativeRing.__init__(self, ring, names=name, category=category) def _element_constructor_(self, x): @@ -2057,8 +2058,17 @@ def __init__(self, ring, polynomial, name=None, category=None): sage: S in IntegralDomains() True + + Check that :trac:`29017` is fixed:: + + sage: R. = ZZ[] + sage: Q = R.quo(x-1) + sage: H = R.Hom(Q) + sage: h = R.hom(Q) + sage: h.parent() is H + True """ - category = CommutativeAlgebras(ring.base_ring()).Quotients().NoZeroDivisors().or_subcategory(category) + category = CommutativeAlgebras(ring.base_ring().category()).Quotients().NoZeroDivisors().or_subcategory(category) PolynomialQuotientRing_generic.__init__(self, ring, polynomial, name, category) def field_extension(self, names): diff --git a/src/sage/rings/power_series_pari.pyx b/src/sage/rings/power_series_pari.pyx index 65c3ec23643..b4d32da0b98 100644 --- a/src/sage/rings/power_series_pari.pyx +++ b/src/sage/rings/power_series_pari.pyx @@ -59,23 +59,21 @@ AUTHORS: """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2013-2017 Peter Bruin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # 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 cypari2.gen cimport Gen as pari_gen from cypari2.pari_instance cimport get_var from cypari2.paridecl cimport gel, typ, lg, valp, varn, t_POL, t_SER, t_RFRAC, t_VEC from sage.libs.pari.all import pari -from sage.misc.superseded import deprecated_function_alias - from sage.rings.polynomial.polynomial_element cimport Polynomial from sage.rings.power_series_ring_element cimport PowerSeries from sage.structure.element cimport Element, RingElement diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index f411b46b3ae..6baccef4138 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -9,7 +9,6 @@ from .power_series_ring_element cimport PowerSeries from sage.structure.element cimport Element, ModuleElement, RingElement from .infinity import infinity, is_Infinite from sage.libs.all import pari_gen, PariError -from sage.misc.superseded import deprecated_function_alias cdef class PowerSeries_poly(PowerSeries): @@ -103,7 +102,7 @@ cdef class PowerSeries_poly(PowerSeries): def polynomial(self): """ - Return the underlying polynomial of self. + Return the underlying polynomial of ``self``. EXAMPLES:: @@ -116,7 +115,7 @@ cdef class PowerSeries_poly(PowerSeries): def valuation(self): """ - Return the valuation of self. + Return the valuation of ``self``. EXAMPLES:: @@ -137,10 +136,11 @@ cdef class PowerSeries_poly(PowerSeries): def degree(self): """ - Return the degree of the underlying polynomial of self. That - is, if self is of the form f(x) + O(x^n), we return the degree - of f(x). Note that if f(x) is 0, we return -1, just as with - polynomials. + Return the degree of the underlying polynomial of ``self``. + + That is, if ``self`` is of the form `f(x) + O(x^n)`, we return + the degree of `f(x)`. Note that if `f(x)` is `0`, we return `-1`, + just as with polynomials. EXAMPLES:: @@ -156,7 +156,7 @@ cdef class PowerSeries_poly(PowerSeries): def __nonzero__(self): """ - Return True if self is nonzero, and False otherwise. + Return ``True`` if ``self`` is nonzero, and ``False`` otherwise. EXAMPLES:: @@ -179,14 +179,14 @@ cdef class PowerSeries_poly(PowerSeries): - ``x``: - a tuple of elements the first of which can be meaningfully - substituted in self, with the remainder used for substitution - in the coefficients of self. + substituted in ``self``, with the remainder used for substitution + in the coefficients of ``self``. - a dictionary for kwds:value pairs. If the variable name of self is a keyword it is substituted for. Other keywords are used for substitution in the coefficients of self. - OUTPUT: the value of self after substitution. + OUTPUT: the value of ``self`` after substitution. EXAMPLES:: @@ -404,14 +404,14 @@ cdef class PowerSeries_poly(PowerSeries): def __getitem__(self, n): """ - Return the nth coefficient of self. + Return the ``n``-th coefficient of ``self``. - If n is a slice object, this will return a power series of the - same precision, whose coefficients are the same as self for + If ``n`` is a slice object, this will return a power series of the + same precision, whose coefficients are the same as ``self`` for those indices in the slice, and 0 otherwise. - Returns 0 for negative coefficients. Raises an IndexError if - try to access beyond known coefficients. + This returns 0 for negative coefficients and raises an + ``IndexError`` if trying to access beyond known coefficients. EXAMPLES:: @@ -545,7 +545,7 @@ cdef class PowerSeries_poly(PowerSeries): cpdef _rmul_(self, Element c): """ - Multiply self on the right by a scalar. + Multiply ``self`` on the right by a scalar. EXAMPLES:: @@ -558,7 +558,7 @@ cdef class PowerSeries_poly(PowerSeries): cpdef _lmul_(self, Element c): """ - Multiply self on the left by a scalar. + Multiply ``self`` on the left by a scalar. EXAMPLES:: @@ -571,7 +571,7 @@ cdef class PowerSeries_poly(PowerSeries): def __lshift__(PowerSeries_poly self, n): """ - Shift self to the left by n, i.e. multiply by x^n. + Shift ``self`` to the left by ``n``, i.e. multiply by `x^n`. EXAMPLES:: @@ -587,7 +587,7 @@ cdef class PowerSeries_poly(PowerSeries): def __rshift__(PowerSeries_poly self, n): """ - Shift self to the right by n, i.e. multiply by x^-n and + Shift ``self`` to the right by ``n``, i.e. multiply by `x^{-n}` and remove any terms of negative exponent. EXAMPLES:: @@ -720,9 +720,9 @@ cdef class PowerSeries_poly(PowerSeries): cdef _inplace_truncate(self, long prec): """ - Truncate self to precision ``prec`` in place. + Truncate ``self`` to precision ``prec`` in place. - NOTE:: + .. NOTE:: This is very unsafe, since power series are supposed to be immutable in Sage. Use at your own risk! @@ -749,9 +749,11 @@ cdef class PowerSeries_poly(PowerSeries): def list(self): """ - Return the list of known coefficients for self. This is just - the list of coefficients of the underlying polynomial, so in - particular, need not have length equal to self.prec(). + Return the list of known coefficients for ``self``. + + This is just the list of coefficients of the underlying + polynomial, so in particular, need not have length equal to + ``self.prec()``. EXAMPLES:: @@ -764,9 +766,11 @@ cdef class PowerSeries_poly(PowerSeries): def dict(self): """ - Return a dictionary of coefficients for self. This is simply a - dict for the underlying polynomial, so need not have keys - corresponding to every number smaller than self.prec(). + Return a dictionary of coefficients for ``self``. + + This is simply a dict for the underlying polynomial, so need + not have keys corresponding to every number smaller than + ``self.prec()``. EXAMPLES:: @@ -780,17 +784,17 @@ cdef class PowerSeries_poly(PowerSeries): def _derivative(self, var=None): """ Return the derivative of this power series with respect - to the variable var. + to the variable ``var``. - If var is None or is the generator of this ring, we take the derivative - with respect to the generator. + If ``var`` is ``None`` or is the generator of this ring, we + take the derivative with respect to the generator. - Otherwise, we call _derivative(var) on each coefficient of + Otherwise, we call ``_derivative(var)`` on each coefficient of the series. - SEEALSO:: + .. SEEALSO:: - self.derivative() + ``self.derivative()`` EXAMPLES:: @@ -836,7 +840,7 @@ cdef class PowerSeries_poly(PowerSeries): def integral(self,var=None): """ - The integral of this power series + Return the integral of this power series. By default, the integration variable is the variable of the power series. @@ -880,6 +884,7 @@ cdef class PowerSeries_poly(PowerSeries): def reverse(self, precision=None): """ Return the reverse of f, i.e., the series g such that g(f(x)) = x. + Given an optional argument ``precision``, return the reverse with given precision (note that the reverse can have precision at most ``f.prec()``). If ``f`` has infinite precision, and the argument @@ -991,7 +996,6 @@ cdef class PowerSeries_poly(PowerSeries): sage: (x - x^2).reverse(precision=3) x + x^2 + O(x^3) - TESTS:: sage: R. = PowerSeriesRing(QQ) @@ -1000,9 +1004,6 @@ cdef class PowerSeries_poly(PowerSeries): Traceback (most recent call last): ... ValueError: Series must have valuation one for reversion. - - - """ if self.valuation() != 1: raise ValueError("Series must have valuation one for reversion.") @@ -1038,8 +1039,7 @@ cdef class PowerSeries_poly(PowerSeries): from sage.misc.all import verbose verbose("passing to pari failed; trying Lagrange inversion") - - if f.parent().characteristic() > 0: + if f.parent().characteristic(): # over a ring of positive characteristic, attempt lifting to # characteristic zero ring verbose("parent ring has positive characteristic; attempting lift to characteristic zero") @@ -1064,7 +1064,7 @@ cdef class PowerSeries_poly(PowerSeries): def pade(self, m, n): r""" - Returns the Padé approximant of ``self`` of index `(m, n)`. + Return the Padé approximant of ``self`` of index `(m, n)`. The Padé approximant of index `(m, n)` of a formal power series `f` is the quotient `Q/P` of two polynomials `Q` and `P` @@ -1149,7 +1149,6 @@ cdef class PowerSeries_poly(PowerSeries): u, v = c.rational_reconstruct(z**(n + m + 1), m, n); return u/v - def _symbolic_(self, ring): """ Conversion to symbolic series. @@ -1188,9 +1187,9 @@ cdef class PowerSeries_poly(PowerSeries): return pex.series(var, self.prec()) -def make_powerseries_poly_v0(parent, f, prec, is_gen): +def make_powerseries_poly_v0(parent, f, prec, is_gen): """ - Return the power series specified by f, prec, and is_gen. + Return the power series specified by ``f``, ``prec``, and ``is_gen``. This function exists for the purposes of pickling. Do not delete this function -- if you change the internal representation, diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 9c6ec027d09..14a9bcf0b97 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -1604,7 +1604,7 @@ def gens(self): sage: QQbar.gens() (I,) """ - return(QQbar_I, ) + return (QQbar_I,) def gen(self, n=0): r""" diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index 79e7f08c995..a3b8232866a 100644 --- a/src/sage/schemes/affine/affine_morphism.py +++ b/src/sage/schemes/affine/affine_morphism.py @@ -564,7 +564,7 @@ def homogenize(self, n): pass d = max([F[i].degree() for i in range(M+1)]) F = [F[i].homogenize(str(newvar))*newvar**(d-F[i].degree()) for i in range(M+1)] - return(H(F)) + return H(F) def as_dynamical_system(self): """ @@ -872,7 +872,7 @@ def weil_restriction(self): result = R.ideal(self._polys).weil_restriction().gens() H = Hom(DS.weil_restriction(), self.codomain().weil_restriction()) - return(H(result)) + return H(result) def reduce_base_field(self): """ diff --git a/src/sage/schemes/affine/affine_point.py b/src/sage/schemes/affine/affine_point.py index 4cf2521dc36..c217783f26a 100644 --- a/src/sage/schemes/affine/affine_point.py +++ b/src/sage/schemes/affine/affine_point.py @@ -210,9 +210,9 @@ def global_height(self, prec=None): else: R = RealField(prec) H = max([self[i].abs() for i in range(self.codomain().ambient_space().dimension_relative())]) - return(R(max(H,1)).log()) + return R(max(H,1)).log() if self.domain().base_ring() in _NumberFields or is_NumberFieldOrder(self.domain().base_ring()): - return(max([self[i].global_height(prec) for i in range(self.codomain().ambient_space().dimension_relative())])) + return max([self[i].global_height(prec) for i in range(self.codomain().ambient_space().dimension_relative())]) else: raise NotImplementedError("must be over a number field or a number field Order") @@ -244,7 +244,8 @@ def homogenize(self, n): True """ phi = self.codomain().projective_embedding(n) - return(phi(self)) + return phi(self) + class SchemeMorphism_point_affine_field(SchemeMorphism_point_affine): @@ -318,7 +319,7 @@ def weil_restriction(self): if L.is_finite(): d = L.degree() if d == 1: - return(self) + return self newP = [] for t in self: c = t.polynomial().coefficients(sparse=False) @@ -327,7 +328,7 @@ def weil_restriction(self): else: d = L.relative_degree() if d == 1: - return(self) + return self #create a CoordinateFunction that gets the relative coordinates in terms of powers from sage.rings.number_field.number_field_element import CoordinateFunction v = L.gen() @@ -343,7 +344,7 @@ def weil_restriction(self): newP = [] for t in self: newP += p(t) - return(WR(newP)) + return WR(newP) def intersection_multiplicity(self, X): r""" diff --git a/src/sage/schemes/affine/affine_space.py b/src/sage/schemes/affine/affine_space.py index a4d5476e790..53c9736643a 100644 --- a/src/sage/schemes/affine/affine_space.py +++ b/src/sage/schemes/affine/affine_space.py @@ -2,13 +2,13 @@ Affine `n` space over a ring """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 William Stein # # Distributed under the terms of the GNU General Public License (GPL) # # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from __future__ import print_function from six import integer_types @@ -758,7 +758,7 @@ def projective_embedding(self, i=None, PP=None): #assume that if you've passed in a new codomain you want to override #the existing embedding if PP is None or phi.codomain() == PP: - return(phi) + return phi except AttributeError: self.__projective_embedding = {} except KeyError: diff --git a/src/sage/schemes/affine/affine_subscheme.py b/src/sage/schemes/affine/affine_subscheme.py index 820b4a0ecd2..5957ab489a3 100644 --- a/src/sage/schemes/affine/affine_subscheme.py +++ b/src/sage/schemes/affine/affine_subscheme.py @@ -7,15 +7,15 @@ - Ben Hutz (2013): affine subschemes """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # Copyright (C) 2013 Ben Hutz # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.categories.fields import Fields from sage.interfaces.all import singular @@ -231,7 +231,7 @@ def projective_embedding(self, i=None, PP=None): #assume that if you've passed in a new ambient projective space #you want to override the existing embedding if PP is None or phi.codomain().ambient_space() == PP: - return(phi) + return phi except AttributeError: self.__projective_embedding = {} except KeyError: diff --git a/src/sage/schemes/curves/curve.py b/src/sage/schemes/curves/curve.py index 036745f4e7b..27f187f5da0 100644 --- a/src/sage/schemes/curves/curve.py +++ b/src/sage/schemes/curves/curve.py @@ -502,4 +502,4 @@ def change_ring(self, R): """ new_AS = self.ambient_space().change_ring(R) I = [f.change_ring(R) for f in self.defining_polynomials()] - return(new_AS.curve(I)) + return new_AS.curve(I) diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index cda5e68d735..722dc71cab0 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -1229,7 +1229,7 @@ def base_ring(self): sage: f.base_ring() Multivariate Polynomial Ring in t over Integer Ring """ - return(self.domain().base_ring()) + return self.domain().base_ring() def coordinate_ring(self): r""" @@ -1258,7 +1258,7 @@ def coordinate_ring(self): Multivariate Polynomial Ring in x, y over Multivariate Polynomial Ring in t over Integer Ring """ - return(self._polys[0].parent()) + return self._polys[0].parent() def change_ring(self, R, check=True): r""" @@ -1935,7 +1935,7 @@ def change_ring(self, R, check=True): """ S = self.codomain().change_ring(R) Q = [R(t) for t in self] - return(S.point(Q, check=check)) + return S.point(Q, check=check) def __copy__(self): r""" @@ -1955,7 +1955,7 @@ def __copy__(self): sage: Q2 == Q True """ - return(self._codomain.point(self._coords, check=False)) + return self._codomain.point(self._coords, check=False) def specialization(self, D=None, phi=None, ambient=None): r""" diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 9185bac4f98..f3b823e59d7 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -692,7 +692,7 @@ def count_points(self, n): F1, psi = F.extension(i, map=True) S1 = self.change_ring(psi) a.append(len(S1.rational_points())) - return(a) + return a def zeta_function(self): r""" diff --git a/src/sage/schemes/product_projective/morphism.py b/src/sage/schemes/product_projective/morphism.py index 88d5ee84466..61908d9b363 100644 --- a/src/sage/schemes/product_projective/morphism.py +++ b/src/sage/schemes/product_projective/morphism.py @@ -12,14 +12,14 @@ Defn: Defined by sending (x : y , u : v) to (x^2*u : y^2*v , x*v^2 : y*u^2). """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2014 Ben Hutz # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.schemes.generic.morphism import SchemeMorphism_polynomial from sage.categories.fields import Fields from sage.categories.number_fields import NumberFields @@ -144,7 +144,7 @@ def __getitem__(self, i): sage: F[2] z^2*u """ - return(self._polys[i]) + return self._polys[i] def _repr_defn(self): r""" @@ -234,7 +234,7 @@ def __call__(self, P, check=True): A = self.codomain() Q = list(P) newP = [f(Q) for f in self.defining_polynomials()] - return(A.point(newP, check)) + return A.point(newP, check) def __eq__(self, right): """ diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index e5af0f495f9..1c94e986f3e 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -965,7 +965,7 @@ def degree(self): sage: f.degree() 2 """ - return(self._polys[0].degree()) + return self._polys[0].degree() def dehomogenize(self, n): r""" @@ -1241,7 +1241,7 @@ def global_height(self, prec=None): C = f[i].coefficients() h = max([c.global_height(prec) for c in C]) H = max(H, h) - return(H) + return H def local_height(self, v, prec=None): r""" @@ -1372,7 +1372,7 @@ def wronskian_ideal(self): N = dom.dimension_relative()+1 R = dom.coordinate_ring() J = jacobian(self.defining_polynomials(),dom.gens()) - return(R.ideal(J.minors(N))) + return R.ideal(J.minors(N)) class SchemeMorphism_polynomial_projective_space_field(SchemeMorphism_polynomial_projective_space): @@ -1505,11 +1505,11 @@ def rational_preimages(self, Q, k=1): """ k = ZZ(k) if k <= 0: - raise ValueError("k (=%s) must be a positive integer"%(k)) + raise ValueError("k (=%s) must be a positive integer" % k) #first check if subscheme from sage.schemes.projective.projective_subscheme import AlgebraicScheme_subscheme_projective if isinstance(Q, AlgebraicScheme_subscheme_projective): - return(Q.preimage(self, k)) + return Q.preimage(self, k) #else assume a point BR = self.base_ring() diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 2d0ae5b9f39..399e995a045 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -660,10 +660,10 @@ def dehomogenize(self,n): PS = self.codomain() A = PS.affine_patch(n) Q = [] - for i in range(0,PS.ambient_space().dimension_relative()+1): - if i !=n: - Q.append(self[i]/self[n]) - return(A.point(Q)) + for i in range(PS.ambient_space().dimension_relative() + 1): + if i != n: + Q.append(self[i] / self[n]) + return A.point(Q) def global_height(self, prec=None): r""" @@ -723,7 +723,7 @@ def global_height(self, prec=None): P = self._number_field_from_algebraics() except TypeError: raise TypeError("must be defined over an algebraic field") - return(max([P[i].global_height(prec=prec) for i in range(self.codomain().ambient_space().dimension_relative()+1)])) + return max([P[i].global_height(prec=prec) for i in range(self.codomain().ambient_space().dimension_relative()+1)]) def local_height(self, v, prec=None): r""" @@ -1185,7 +1185,7 @@ def _number_field_from_algebraics(self): P = [ psi(p) for p in P ] # The elements of P were elements of K_pre from sage.schemes.projective.projective_space import ProjectiveSpace PS = ProjectiveSpace(K,self.codomain().dimension_relative(),'z') - return(PS(P)) + return PS(P) def clear_denominators(self): r""" diff --git a/src/sage/schemes/projective/projective_space.py b/src/sage/schemes/projective/projective_space.py index 4e855731305..19169b95bcb 100644 --- a/src/sage/schemes/projective/projective_space.py +++ b/src/sage/schemes/projective/projective_space.py @@ -1017,7 +1017,7 @@ def affine_patch(self, i, AA=None): #assume that if you've passed in a new affine space you want to override #the existing patch if AA is None or A == AA: - return(A) + return A except AttributeError: self.__affine_patches = {} except KeyError: @@ -1850,7 +1850,8 @@ def rational_points_dictionary(self): P[j] = zero j += 1 i -= 1 - return(D) + return D + class ProjectiveSpace_rational_field(ProjectiveSpace_field): def rational_points(self, bound=0): diff --git a/src/sage/schemes/projective/projective_subscheme.py b/src/sage/schemes/projective/projective_subscheme.py index 126e471739c..7a22576d870 100644 --- a/src/sage/schemes/projective/projective_subscheme.py +++ b/src/sage/schemes/projective/projective_subscheme.py @@ -521,7 +521,7 @@ def orbit(self, f, N): if N[0] < 0 or N[1] < 0: raise TypeError("orbit bounds must be non-negative") if N[0] > N[1]: - return([]) + return [] Q = self for i in range(1, N[0]+1): @@ -531,7 +531,7 @@ def orbit(self, f, N): for i in range(N[0]+1, N[1]+1): Q = f(Q) Orb.append(Q) - return(Orb) + return Orb def nth_iterate(self, f, n): r""" @@ -781,7 +781,7 @@ def _forward_image(self, f, check = True): v = G[i].variables() if all(Rvars[j] not in v for j in range(n)): newL.append(psi(G[i])) - return(codom.subscheme(newL)) + return codom.subscheme(newL) def preimage(self, f, k=1, check=True): r""" @@ -903,7 +903,7 @@ def preimage(self, f, k=1, check=True): else: F = f dict = {R.gen(i): F[i] for i in range(codom.dimension_relative()+1)} - return(dom.subscheme([t.subs(dict) for t in self.defining_polynomials()])) + return dom.subscheme([t.subs(dict) for t in self.defining_polynomials()]) def dual(self): r""" diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index fe7b8fdec93..a5fbd555f2d 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -1038,15 +1038,15 @@ cdef class Element(SageObject): def _cache_key(self): """ - Provide a hashable key for an element if it is not hashable + Provide a hashable key for an element if it is not hashable. EXAMPLES:: - sage: a=sage.structure.element.Element(ZZ) + sage: a = sage.structure.element.Element(ZZ) sage: a._cache_key() (Integer Ring, 'Generic element of a structure') """ - return(self.parent(),str(self)) + return self.parent(), str(self) #################################################################### # In a Cython or a Python class, you must define either _cmp_ @@ -2713,7 +2713,7 @@ cdef class RingElement(ModuleElement): sage: Mod(-15, 37).abs() Traceback (most recent call last): ... - ArithmeticError: absolute valued not defined on integers modulo n. + ArithmeticError: absolute value not defined on integers modulo n. """ return abs(self) @@ -2794,12 +2794,36 @@ cdef class CommutativeRingElement(RingElement): """ Base class for elements of commutative rings. """ + def inverse_mod(self, I): r""" Return an inverse of ``self`` modulo the ideal `I`, if defined, i.e., if `I` and ``self`` together generate the unit ideal. - """ - raise NotImplementedError + + EXAMPLES:: + + sage: F = GF(25) + sage: x = F.gen() + sage: z = F.zero() + sage: x.inverse_mod(F.ideal(z)) + 2*z2 + 3 + sage: x.inverse_mod(F.ideal(1)) + 1 + sage: z.inverse_mod(F.ideal(1)) + 1 + sage: z.inverse_mod(F.ideal(z)) + Traceback (most recent call last): + ... + ValueError: an element of a proper ideal does not have an inverse modulo that ideal + """ + if I.is_one(): + return self.parent().one() + elif self in I: + raise ValueError("an element of a proper ideal does not have an inverse modulo that ideal") + elif hasattr(self, "is_unit") and self.is_unit(): + return self.inverse_of_unit() + else: + raise NotImplementedError def divides(self, x): """ diff --git a/src/sage/symbolic/constants.py b/src/sage/symbolic/constants.py index 78e4a5a7283..8f882893092 100644 --- a/src/sage/symbolic/constants.py +++ b/src/sage/symbolic/constants.py @@ -1085,10 +1085,7 @@ class Khinchin(Constant): sage: m = mathematica(khinchin); m # optional - mathematica Khinchin sage: m.N(200) # optional - mathematica - 2.6854520010653064453097148354817956938203822939944629530511523455572 - > 188595371520028011411749318476979951534659052880900828976777164109630517 - > 925334832596683818523154213321194996260393285220448194096181 - + 2.685452001065306445309714835481795693820382293...32852204481940961807 """ def __init__(self, name='khinchin'): """ diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 3fe25264c7e..028f6287971 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -1242,11 +1242,11 @@ def _solve_expression(f, x, explicit_solutions, multiplicities, return sympy_set_to_list(ret, sympy_vars) else: try: - return(solve_ineq(f)) # trying solve_ineq_univar + return solve_ineq(f) # trying solve_ineq_univar except Exception: pass try: - return(solve_ineq([f])) # trying solve_ineq_fourier + return solve_ineq([f]) # trying solve_ineq_fourier except Exception: raise NotImplementedError("solving only implemented for equalities and few special inequalities, see solve_ineq") ex = f @@ -1809,7 +1809,6 @@ def solve_ineq(ineq, vars=None): - Robert Marik (01-2010) """ - if isinstance(ineq,list): - return(solve_ineq_fourier(ineq, vars)) - else: - return(solve_ineq_univar(ineq)) + if isinstance(ineq, list): + return solve_ineq_fourier(ineq, vars) + return solve_ineq_univar(ineq) diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py index f09c79c2626..0012b375731 100644 --- a/src/sage/tensor/modules/format_utilities.py +++ b/src/sage/tensor/modules/format_utilities.py @@ -7,6 +7,7 @@ - Eric Gourgoulhon, Michal Bejger (2014-2015): initial version - Joris Vankerschaver (2010): for the function :func:`is_atomic()` +- Michael Jung (2020): extended usage of :func:`is_atomic()` """ @@ -23,7 +24,7 @@ import six from sage.structure.sage_object import SageObject -def is_atomic(expression): +def is_atomic(expr, sep=['+', '-']): r""" Helper function to check whether some LaTeX expression is atomic. @@ -31,15 +32,17 @@ def is_atomic(expression): :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` of class :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` - written by Joris Vankerschaver (2010). + written by Joris Vankerschaver (2010) and modified by Michael Jung (2020). INPUT: - - ``expression`` -- string representing the expression (e.g. LaTeX string) + - ``expr`` -- string representing the expression (e.g. LaTeX string) + - ``sep`` -- (default: ``['+', '-']``) a list of strings representing the + operations (e.g. LaTeX strings) OUTPUT: - - ``True`` if additive operations are enclosed in parentheses and + - ``True`` if the operations are enclosed in parentheses and ``False`` otherwise. EXAMPLES:: @@ -52,16 +55,31 @@ def is_atomic(expression): sage: is_atomic("(2+x)") True + Moreover the separator can be changed:: + + sage: is_atomic("a*b", sep=['*']) + False + sage: is_atomic("(a*b)", sep=['*']) + True + sage: is_atomic("a mod b", sep=['mod']) + False + sage: is_atomic("(a mod b)", sep=['mod']) + True + """ - if not isinstance(expression, six.string_types): + if not isinstance(expr, six.string_types): raise TypeError("The argument must be a string") + if not isinstance(sep, list): + raise TypeError("the argument 'sep' must be a list") + elif any(not isinstance(s, six.string_types) for s in sep): + raise TypeError("the argument 'sep' must consist of strings") level = 0 - for n, c in enumerate(expression): + for n, c in enumerate(expr): if c == '(': - level += 1 + level += 1; continue elif c == ')': - level -= 1 - if c == '+' or c == '-': + level -= 1; continue + if any(expr[n:n + len(s)] == s for s in sep): if level == 0 and n > 0: return False return True @@ -76,7 +94,7 @@ def is_atomic_wedge_txt(expression): :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` of class :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` - written by Joris Vankerschaver (2010). + written by Joris Vankerschaver (2010) and modified by Michael Jung (2020). INPUT: @@ -102,18 +120,7 @@ def is_atomic_wedge_txt(expression): True """ - if not isinstance(expression, six.string_types): - raise TypeError("The argument must be a string.") - level = 0 - for n, c in enumerate(expression): - if c == '(': - level += 1 - elif c == ')': - level -= 1 - if c == '/' and expression[n+1:n+2] == '\\': - if level == 0 and n > 0: - return False - return True + return is_atomic(expression, sep=['/\\']) def is_atomic_wedge_latex(expression): @@ -125,7 +132,7 @@ def is_atomic_wedge_latex(expression): :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` of class :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` - written by Joris Vankerschaver (2010). + written by Joris Vankerschaver (2010) and modified by Michael Jung (2020). INPUT: @@ -159,18 +166,7 @@ def is_atomic_wedge_latex(expression): False """ - if not isinstance(expression, six.string_types): - raise TypeError("The argument must be a string.") - level = 0 - for n, c in enumerate(expression): - if c == '(': - level += 1 - elif c == ')': - level -= 1 - if c == '\\' and expression[n+1:n+6] == 'wedge': - if level == 0 and n > 0: - return False - return True + return is_atomic(expression, sep=['\\wedge']) def format_mul_txt(name1, operator, name2): diff --git a/src/sage/tensor/modules/free_module_automorphism.py b/src/sage/tensor/modules/free_module_automorphism.py index c526e24129c..73de99ffbc0 100644 --- a/src/sage/tensor/modules/free_module_automorphism.py +++ b/src/sage/tensor/modules/free_module_automorphism.py @@ -729,7 +729,7 @@ def __invert__(self): sage: ~a is a.inverse() True - sage: a^(-1) is a.inverse() + sage: (a)^(-1) is a.inverse() True The inverse of the identity map is of course itself:: @@ -740,23 +740,41 @@ def __invert__(self): and we have:: - sage: a*a^(-1) == id + sage: a*(a)^(-1) == id True - sage: a^(-1)*a == id + sage: (a)^(-1)*a == id True + + If the name could cause some confusion, a bracket is added around the + element before taking the inverse:: + + sage: c = M.automorphism(name='a^(-1)*b') + sage: c[e,:] = [[1,0,0],[0,-1,1],[0,2,-1]] + sage: c.inverse() + Automorphism (a^(-1)*b)^(-1) of the Rank-3 free module M over the + Integer Ring + """ from .comp import Components if self._is_identity: return self if self._inverse is None: + from sage.tensor.modules.format_utilities import is_atomic if self._name is None: inv_name = None else: - inv_name = self._name + '^(-1)' + if is_atomic(self._name, ['*']): + inv_name = self._name + '^(-1)' + else: + inv_name = '(' + self._name + ')^(-1)' if self._latex_name is None: inv_latex_name = None else: - inv_latex_name = self._latex_name + r'^{-1}' + if is_atomic(self._latex_name, ['\\circ', '\\otimes']): + inv_latex_name = self._latex_name + r'^{-1}' + else: + inv_latex_name = r'\left(' + self._latex_name + \ + r'\right)^{-1}' fmodule = self._fmodule si = fmodule._sindex nsi = fmodule._rank + si diff --git a/src/sage/version.py b/src/sage/version.py index dafb2c7d3f4..48c223a1d1d 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.1.beta0' -date = '2020-01-10' -banner = 'SageMath version 9.1.beta0, Release Date: 2020-01-10' +version = '9.1.beta1' +date = '2020-01-21' +banner = 'SageMath version 9.1.beta1, Release Date: 2020-01-21'