diff --git a/COPYING.txt b/COPYING.txt index 4fc2393e21e..fc4dbd08c5b 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -28,6 +28,7 @@ the licenses of the components of Sage are included below as well. SOFTWARE LICENSE ----------------------------------------------------------------------- +arb GPLv2+ atlas Modified BSD boehm_gc MIT-like license (see below) backports_ssl_match_hostname Python License diff --git a/Makefile b/Makefile index 1024330110f..a7df7e96cb9 100644 --- a/Makefile +++ b/Makefile @@ -13,13 +13,28 @@ build: all-build # Defer unknown targets to build/make/Makefile %:: - $(MAKE) configure logs - +cd build/make && ./pipestatus \ - "./install '$@' 2>&1" \ - "tee -a ../../logs/install.log" - -logs: - mkdir -p $@ + $(MAKE) build/make/Makefile + +build/bin/sage-logger \ + "cd build/make && ./install '$@'" logs/install.log + +# If configure was run before, rerun it with the old arguments. +# Otherwise, run configure with argument $PREREQ_OPTIONS. +build/make/Makefile: configure + rm -f config.log + mkdir -p logs/pkgs + ln -s logs/pkgs/config.log config.log + @if [ -x config.status ]; then \ + ./config.status --recheck && ./config.status; \ + else \ + ./configure $$PREREQ_OPTIONS; \ + fi || ( \ + if [ "x$$SAGE_PORT" = x ]; then \ + echo "If you would like to try to build Sage anyway (to help porting),"; \ + echo "export the variable 'SAGE_PORT' to something non-empty."; \ + exit 1; \ + else \ + echo "Since 'SAGE_PORT' is set, we will try to build anyway."; \ + fi; ) # Preemptively download all standard upstream source tarballs. download: @@ -62,7 +77,7 @@ bootstrap-clean: maintainer-clean: distclean bootstrap-clean rm -rf upstream -micro_release: bdist-clean lib-clean +micro_release: bdist-clean sagelib-clean @echo "Stripping binaries ..." LC_ALL=C find local/lib local/bin -type f -exec strip '{}' ';' 2>&1 | grep -v "File format not recognized" | grep -v "File truncated" || true diff --git a/VERSION.txt b/VERSION.txt index c5de210ce0a..feb64723094 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.9.beta0, released 2015-07-29 +Sage version 6.10.beta2, released 2015-10-28 diff --git a/build/bin/sage-logger b/build/bin/sage-logger new file mode 100755 index 00000000000..60ee3bca523 --- /dev/null +++ b/build/bin/sage-logger @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# +# sage-logger COMMAND LOGFILE +# +# Evaluate shell command COMMAND while logging stdout and stderr to +# LOGFILE. If either the command or the logging failed, return a +# non-zero exit status. +# +# AUTHOR: +# +# - Jeroen Demeyer (2015-07-26): initial version based on old pipestatus +# script (#18953) +# +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# 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/ +#***************************************************************************** + +cmd="$1" +logfile="$2" +logdir=`dirname "$logfile"` + +mkdir -p "$logdir" + +# Redirect stdout and stderr to a subprocess running tee. +# We trap SIGINT such that SIGINT interrupts the main process being +# run, not the logging. +( exec 2>&1; eval "$cmd" ) | ( trap '' SIGINT; tee -a "$logfile" ) + +pipestatus=(${PIPESTATUS[*]}) + +if [ ${pipestatus[1]} -ne 0 ]; then + exit ${pipestatus[1]} +else + exit ${pipestatus[0]} +fi diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index f4b085b515d..f734d49bee5 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -6,7 +6,6 @@ # sage -i ... # # Options can be: -# -f: install a package even if the same version is already installed # -s: do not delete temporary build directory # -c: after installing, run the test suite for the spkg. This should # override the settings of SAGE_CHECK and SAGE_CHECK_PACKAGES. @@ -57,6 +56,24 @@ # Avoid surprises with character ranges [a-z] in regular expressions export LC_ALL=C +usage() +{ +cat < + +If is a URL, download and install it. If it is a file +name, install it. Otherwise, search Sage's list of packages (see +'sage --package list') for a matching package, and if a match is +found, install it. + +Options: + -s: do not delete the temporary build directory + -c: after installing, run the test suite for the package + -d: only download the package + +EOF +} + # error_msg(header, command) # This is for printing an error message if something went wrong. # The first argument is the header to print, the second argument should @@ -152,42 +169,31 @@ fi # Handle special command-line options ################################################################## if [ $# -eq 0 ]; then - echo "Currently installed packages:" - exec ls -1 "$SAGE_SPKG_INST" + usage + exit 0 fi -# Options have to come in a specific order, -# this is ensured by spkg/bin/sage. +# Parse options INFO=0 -if [ "$1" = '--info' ]; then - INFO=1 - shift -fi - -FORCE=0 -if [ "$1" = '-f' ]; then - FORCE=1 - shift -fi - -DOWNLOAD_ONLY=0 -if [ "$1" = '-d' ]; then - DOWNLOAD_ONLY=1 +while true; do + case "$1" in + --info) + INFO=1;; + -d) + SAGE_INSTALL_FETCH_ONLY=1;; + -s) + export SAGE_KEEP_BUILT_SPKGS=yes;; + -c|--check) + SAGE_CHECK_PACKAGES=x # nonempty, so not set to '!python2' later + export SAGE_CHECK=yes;; + -*) + echo >&2 "Error: unknown option '$1'" + exit 2;; + *) break;; + esac shift -elif [ -n "$SAGE_INSTALL_FETCH_ONLY" ]; then - DOWNLOAD_ONLY=1 -fi +done -if [ "$1" = '-s' ]; then - export SAGE_KEEP_BUILT_SPKGS=yes - shift -fi - -if [ "$1" = '-c' ]; then - export SAGE_CHECK=yes - SAGE_CHECK_PACKAGES=x # nonempty, so not set to '!python' later - shift -fi ################################################################## # Figure out the package filename, download it if needed. @@ -207,8 +213,6 @@ fi # 3. , i.e. the name of the package without a version number. # 4. /-x.y.z.spkg, i.e. the full URL where the package # is hosted. Any local packages matching are ignored. -# The package is only downloaded if it has not yet been installed -# (unless -f is given, then it's always downloaded and installed). # # In cases 2a, 2b and 3 we first look locally inside spkg/* for a # matching package. Otherwise, we try to download it. In all cases, @@ -225,7 +229,7 @@ fi # PKG_NAME is the last path component without .spkg # This already reduces case 2b to case 2a. PKG_NAME=`basename "$PKG_SRC" | sed 's/\.spkg$//'` -PKG_BASE=`echo "$PKG_NAME" | sed 's/-[0-9].*//'` +PKG_BASE=`echo "$PKG_NAME" | sed 's/-.*//'` # USE_LOCAL_SCRIPTS is a flag that if non-empty will cause # this script to try to install the package using local metadata @@ -262,7 +266,6 @@ elif [ -z "$PKG_HAS_PATH" ]; then # Warning for experimental packages if [ x`cat "$PKG_SCRIPTS/type"` = x"experimental" ]; then - echo # The following message appears twice in this file echo "=========================== WARNING ===========================" echo "You are about to download and install an experimental package." echo "This probably won't work at all for you! There is no guarantee" @@ -294,18 +297,6 @@ if [ $INFO -ne 0 -a "$USE_LOCAL_SCRIPTS" = yes ]; then exit 0 fi -# Check whether the package is already installed. We do this before -# we download the package. We do a second check later, in case the -# user didn't supply a version number and we don't find a local -# matching package (case 3 above). -if [ $INFO -eq 0 -a $FORCE -eq 0 -a -f "$SAGE_SPKG_INST/$PKG_NAME" ]; then - echo "Package $PKG_NAME is already installed." - echo "Use 'sage -f $PKG_BASE' to force a reinstallation." - # Touch installed file such that "make" considers it up-to-date. - touch "$SAGE_SPKG_INST/$PKG_NAME" - exit 0 -fi - # If we haven't found the package yet, we must download it if [ ! -f "$PKG_SRC" ]; then if [ -n "$PKG_NAME_UPSTREAM" ]; then @@ -331,7 +322,7 @@ if [ ! -f "$PKG_SRC" ]; then if [ $? -ne 0 ]; then exit 1 fi - for repo in optional experimental standard huge archive; do + for repo in optional experimental huge; do # Download the list of packages. echo ">>> Checking online list of $repo packages." # File inside DOT_SAGE should be writable @@ -360,32 +351,40 @@ if [ ! -f "$PKG_SRC" ]; then # fall through and use the .spkg file. else # Warn and ask the user if downloading an - # experimental or archive package. + # experimental package. + # Add a deprecation note for other packages, + # since old-style packages are deprecated. if [ $repo = experimental ]; then - echo # The following message appears twice in this file - echo "=========================== WARNING ===========================" - echo "You are about to download and install an experimental package." - echo "This probably won't work at all for you! There is no guarantee" - echo "that it will build correctly, or behave as expected." - echo "Use at your own risk!" - echo "===============================================================" - ask_download=yes - elif [ $repo = archive ]; then - echo "=========================== WARNING ===========================" - echo "You are about to download and install an archived package." - echo "This means the package is likely to be outdated and it probably" - echo "won't work at all for you! Use at your own risk!" - echo "===============================================================" - ask_download=yes - else - ask_download=no - fi - - if [ $ask_download = yes ]; then + echo "================================ WARNING =================================" + echo "You are about to download and install an unmaintained experimental" + echo "package. This probably won't work at all for you! There is no guarantee" + echo "that it will build correctly, or behave as expected. Use at your own risk!" + echo + echo "This package will be removed in future versions of SageMath. If you care" + echo "about this package, you should make a proper new-style package instead." + echo "For more information about making Sage packages, see" + echo "http://doc.sagemath.org/html/en/developer/packaging.html" + echo "==========================================================================" read -p "Are you sure you want to continue [Y/n]? " answer case "$answer" in n*|N*) exit 0;; esac + else + # Deprecated since Sage 6.9, Trac #19158 + echo "================================== NOTE ==================================" + echo "You are about to download and install an old-style package. While this" + echo "might still work fine, old-style packages are unmaintained and deprecated." + echo + echo "This package will be removed in future versions of SageMath. If you care" + echo "about this package, you should make a proper new-style package instead." + echo "For more information about making Sage packages, see" + echo "http://doc.sagemath.org/html/en/developer/packaging.html" + echo "==========================================================================" + echo + read -t 30 -p "Are you sure (automatically continuing in 30 seconds) [Y/n]? " answer + case "$answer" in + n*|N*) exit 0;; + esac fi fi PKG_URL="$MIRROR/$repo/$pkg.spkg" @@ -394,18 +393,11 @@ if [ ! -f "$PKG_SRC" ]; then done if [ -z "$PKG_URL" ]; then - echo >&2 "Error: could not find a package matching $PKG_NAME on $MIRROR" + echo >&2 "Error: could not find a package matching $PKG_NAME" + echo >&2 " Try 'sage --package list' to see the available packages" + echo >&2 " $(sage-package apropos $PKG_NAME)" exit 1 fi - - # Check a second time whether the package is already installed. - if [ $INFO -eq 0 -a $FORCE -eq 0 -a -f "$SAGE_SPKG_INST/$PKG_NAME" ]; then - echo "Package $PKG_NAME is already installed." - echo "Use 'sage -f $PKG_BASE' to force a reinstallation." - # Touch installed file such that "make" considers it up-to-date. - touch "$SAGE_SPKG_INST/$PKG_NAME" - exit 0 - fi fi # Trac #5852: check write permissions @@ -453,7 +445,7 @@ if [ -n "$SAGE_SPKG_COPY_UPSTREAM" ]; then exit 1 fi fi -if [ $DOWNLOAD_ONLY -ne 0 ]; then +if [ -n "$SAGE_INSTALL_FETCH_ONLY" ]; then exit 0 fi @@ -494,7 +486,7 @@ if [ ! -w "$SAGE_BUILD_DIR" ]; then exit 1 fi if [ ! -d "$SAGE_LOCAL" ]; then - # If you just unpack Sage and run "sage -f " then local does not yet exist + # If you just unpack Sage and run "sage -p " then local does not yet exist mkdir "$SAGE_LOCAL" fi if [ ! -w "$SAGE_LOCAL" ]; then @@ -605,7 +597,7 @@ fi # Since Python's self-tests seem to fail on all platforms, we disable # its test suite by default. if [ -z "$SAGE_CHECK_PACKAGES" ]; then - SAGE_CHECK_PACKAGES='!python' + SAGE_CHECK_PACKAGES='!python2' fi # Allow spaces, commas, or colons as separator (the documentation suggests commas). if echo ",$SAGE_CHECK_PACKAGES," | grep -i "[ ,:]\!$PKG_BASE[ ,:]" > /dev/null ; then diff --git a/build/make/deps b/build/make/deps index 771b666d574..009d592a273 100644 --- a/build/make/deps +++ b/build/make/deps @@ -4,15 +4,14 @@ ############################################################################### # Do not put an explicit path for sage-spkg here, it will be found in $PATH. -SAGE_SPKG = sage-spkg -f -PIPE = $(SAGE_ROOT)/build/make/pipestatus +SAGE_SPKG = sage-spkg STARTED = $(SAGE_LOCAL)/etc/sage-started.txt # Tell make not to look for files with these names: .PHONY: all all-sage all-toolchain all-build start \ base toolchain toolchain-deps sagelib \ doc doc-html doc-html-jsmath doc-html-mathjax doc-pdf \ - doc-clean clean lib-clean build-clean + doc-clean clean sagelib-clean build-clean # Build everything and start Sage. # Note that we put the "doc" target first in the rule below because @@ -27,13 +26,12 @@ all-build: all-toolchain $(MAKE) all-sage -# Make the 4 preliminary build phases: -# prereq, base, toolchain, toolchain-deps. +# Make the 3 preliminary build phases: +# base, toolchain, toolchain-deps. # During the toolchain build, we export SAGE_BUILD_TOOLCHAIN=yes # such that packages can do different things when they are built # as prerequisite of GCC. all-toolchain: - $(MAKE) $(INST)/prereq $(MAKE) base env SAGE_BUILD_TOOLCHAIN=yes $(MAKE) toolchain $(MAKE) toolchain-deps @@ -46,6 +44,12 @@ all-sage: \ $(EXTCODE) \ $(SCRIPTS) +# Download all packages which should be inside an sdist tarball (the -B +# option to make forces all targets to be built unconditionally) +download-for-sdist: base + env SAGE_INSTALL_FETCH_ONLY=yes $(MAKE) -B SAGERUNTIME= \ + $(STANDARD_PACKAGES) gcc mpir python2 + # TOOLCHAIN consists of dependencies determined by build/make/install, # including for example the GCC package. toolchain: $(TOOLCHAIN) @@ -87,18 +91,6 @@ $(STARTED): $(STANDARD_PACKAGES) ############################################################################### base: $(INST)/$(BZIP2) $(INST)/$(PATCH) $(INST)/$(PKGCONF) -$(INST)/prereq: ../../configure - @cd ../..; rm -f config.log; ln -s logs/pkgs/config.log config.log; \ - ./configure $$PREREQ_OPTIONS || ( \ - if [ "x$$SAGE_PORT" = x ]; then \ - echo "If you would like to try to build Sage anyway (to help porting),"; \ - echo "export the variable 'SAGE_PORT' to something non-empty."; \ - exit 1; \ - else \ - echo "Since 'SAGE_PORT' is set, we will try to build anyway."; \ - fi; ) - touch $@ - ############################################################################### # Building normal packages ############################################################################### @@ -108,7 +100,9 @@ $(INST)/prereq: ../../configure # Sage library (e.g. CYTHON, JINJA2), and on the other hand all # dependencies for Cython files (e.g. PARI, NTL, SAGE_MP_LIBRARY). sagelib: \ + $(INST)/$(ARB) \ $(INST)/$(ATLAS) \ + $(INST)/$(BRIAL) \ $(INST)/$(CEPHES) \ $(INST)/$(CLIQUER) \ $(INST)/$(CYTHON) \ @@ -123,6 +117,7 @@ sagelib: \ $(INST)/$(GSL) \ $(INST)/$(IML) \ $(INST)/$(JINJA2) \ + $(INST)/$(JUPYTER_CORE) \ $(INST)/$(LCALC) \ $(INST)/$(LRCALC) \ $(INST)/$(LIBGAP) \ @@ -138,7 +133,6 @@ sagelib: \ $(INST)/$(NUMPY) \ $(INST)/$(PARI) \ $(INST)/$(PLANARITY) \ - $(INST)/$(POLYBORI) \ $(INST)/$(PPL) \ $(INST)/$(PYNAC) \ $(INST)/$(PYTHON) \ @@ -148,10 +142,11 @@ sagelib: \ $(INST)/$(SINGULAR) \ $(INST)/$(SIX) \ $(INST)/$(SYMMETRICA) \ - $(INST)/$(ZN_POLY) + $(INST)/$(ZN_POLY) \ + $(EXTCODE) if [ -z "$$SAGE_INSTALL_FETCH_ONLY" ]; then \ cd $(SAGE_SRC) && source bin/sage-env && \ - $(PIPE) 'time $(MAKE) sage 2>&1' 'tee -a $(SAGE_LOGS)/sage-$(SAGE_VERSION).log'; \ + sage-logger 'time $(MAKE) sage' '$(SAGE_LOGS)/sage-$(SAGE_VERSION).log'; \ fi @@ -193,28 +188,28 @@ $(SAGE_EXTCODE)/%: $(SAGE_SRC)/ext/% DOC_DEPENDENCIES = sagelib $(INST)/$(SPHINX) $(INST)/$(SAGENB) \ | $(SAGERUNTIME) $(INST)/$(MAXIMA) $(INST)/$(NETWORKX) \ $(INST)/$(SCIPY) $(INST)/$(MATPLOTLIB) $(INST)/$(PILLOW) \ - $(INST)/$(MATHJAX) + $(INST)/$(MATHJAX) $(INST)/$(IPYKERNEL) $(INST)/$(JUPYTER_CLIENT) doc: doc-html doc-html: $(DOC_DEPENDENCIES) - cd ../.. && $(PIPE) "./sage --docbuild --no-pdf-links all html $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a logs/dochtml.log" + cd ../.. && sage-logger './sage --docbuild --no-pdf-links all html $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log # 'doc-html-no-plot': build docs without building the graphics coming # from the '.. plot' directive, in case you want to save a few # megabytes of disk space. 'doc-clean' is a prerequisite because the # presence of graphics is cached in src/doc/output. doc-html-no-plot: doc-clean $(DOC_DEPENDENCIES) - cd ../.. && $(PIPE) "./sage --docbuild --no-pdf-links --no-plot all html $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a logs/dochtml.log" + cd ../.. && sage-logger './sage --docbuild --no-pdf-links --no-plot all html $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log doc-html-mathjax: $(DOC_DEPENDENCIES) - cd ../.. && $(PIPE) "./sage --docbuild --no-pdf-links all html -j $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a logs/dochtml.log" + cd ../.. && sage-logger './sage --docbuild --no-pdf-links all html -j $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log # Keep target 'doc-html-jsmath' for backwards compatibility. doc-html-jsmath: doc-html-mathjax doc-pdf: $(DOC_DEPENDENCIES) - cd ../.. && $(PIPE) "./sage --docbuild all pdf $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a logs/docpdf.log" + cd ../.. && sage-logger './sage --docbuild all pdf $(SAGE_DOCBUILD_OPTS)' logs/docpdf.log doc-clean: cd "$(SAGE_SRC)/doc" && $(MAKE) clean @@ -228,7 +223,7 @@ clean: @echo "Deleting package build directories..." rm -rf "$(SAGE_LOCAL)/var/tmp/sage/build" -lib-clean: +sagelib-clean: cd "$(SAGE_SRC)" && $(MAKE) clean -build-clean: clean doc-clean lib-clean +build-clean: clean doc-clean sagelib-clean diff --git a/build/make/install b/build/make/install index 13c99ec3601..9a8ada98f79 100755 --- a/build/make/install +++ b/build/make/install @@ -28,435 +28,6 @@ export SAGE_ROOT SAGE_SRC SAGE_LOCAL SAGE_EXTCODE SAGE_LOGS SAGE_SPKG_INST SAGE_ # "SAGE_ROOT/.BUILDSTART". See ticket #6744. echo `date -u "+%s"` > "$SAGE_ROOT/.BUILDSTART" -######################################################################## -# Support for upgrading -######################################################################## -# Check that we aren't accidentally upgrading from old (< 6.0) -# versions of Sage. See #14715. -# The file $SAGE_ROOT/spkg/install no longer exists in Sage 6.0, -# but it did exist in all earlier versions. -if [ -f "$SAGE_ROOT/spkg/install" ]; then -cat >&2 < "$SAGE_LOCAL/share/mp_config" - fi - SAGE_MP_LIBRARY=`cat "$SAGE_LOCAL/share/mp_config"` -else - if [ -f "$SAGE_LOCAL/share/mp_config" -a "$SAGE_MP_LIBRARY" != `cat "$SAGE_LOCAL/share/mp_config"` ]; then - echo "SAGE_MP_LIBRARY differs from the value stored in" - echo "\"$SAGE_LOCAL/share/mp_config\"." - echo "This is not supported; you should rebuild Sage from scratch" - echo "using the new SAGE_MP_LIBRARY value." - exit 1 - fi - - case "$SAGE_MP_LIBRARY" in - MPIR|GMP) - echo "Using $SAGE_MP_LIBRARY as default MP library." - echo $SAGE_MP_LIBRARY > "$SAGE_LOCAL/share/mp_config" - ;; - *) - echo "Allowed values for SAGE_MP_LIBRARY are \"MPIR\" and \"GMP\"." - echo "If you did not set this variable, check the content of" - echo "\"$SAGE_LOCAL/share/mp_config\"." - exit 1 - ;; - esac -fi - -############################################################################### -# Determine whether to install GCC (gcc, g++, gfortran). -############################################################################### - -# Determine various compilers. These variables should not be exported, -# they are only used in this build/make/install script to determine whether to -# install GCC. The "real" $CC, $CXX,... variables for building Sage are -# set in sage-env. - -if [ -z "$CXX" ]; then - CXX=g++ -fi - -if [ -z "$CC" ]; then - if command -v gcc >/dev/null 2>/dev/null; then - CC=gcc - fi -fi - -if [ -z "$FC" ]; then - if command -v gfortran >/dev/null 2>/dev/null; then - FC=gfortran - elif command -v g95 >/dev/null 2>/dev/null; then - FC=g95 - elif command -v g77 >/dev/null 2>/dev/null; then - FC=g77 - fi -fi - -if [ -f "$SAGE_LOCAL/bin/gcc" ]; then - # Ignore SAGE_INSTALL_GCC if GCC is already installed - SAGE_INSTALL_GCC="" -fi - -if [ -n "$SAGE_INSTALL_GCC" ]; then - # Check the value of the environment variable SAGE_INSTALL_GCC - case "$SAGE_INSTALL_GCC" in - yes) - echo >&2 "Installing GCC because SAGE_INSTALL_GCC is set to 'yes'." - need_to_install_gcc=yes;; - no) - need_to_install_gcc=no;; - *) - echo >&2 "Error: SAGE_INSTALL_GCC should be set to 'yes' or 'no'." - echo >&2 "You can also leave it unset to install GCC when needed." - exit 2;; - esac -else - # SAGE_INSTALL_GCC is not set, install GCC when needed. - need_to_install_gcc=no - - # Check whether $CXX is some version of GCC. If it's a different - # compiler, install GCC. - CXXtype=`source sage-env; testcxx.sh $CXX` - if [ "$CXXtype" != GCC ]; then - echo >&2 "Installing GCC because your '$CXX' isn't GCC (GNU C++)." - need_to_install_gcc=yes - else - # $CXX points to some version of GCC, find out which version. - GCCVERSION=`$CXX -dumpversion` - case $GCCVERSION in - [0-3].*|4.[0-3]|4.[0-3].*) - # Install our own GCC if the system-provided one is older than gcc-4.4. - # * gcc-4.2.4 compiles a slow IML: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/Ux3t0dW2FSI - # * gcc-4.3 might have trouble building ATLAS: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/KCeFqQ_w2FE - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION, which is quite old." - need_to_install_gcc=yes;; - 4.4.*|4.5.*) - # GCC 4.4.x and GCC 4.5.x fail to compile PARI/GP on ia64: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46044 - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc <= 4.5 fails to compile PARI/GP on ia64." - need_to_install_gcc=yes - fi;; - 4.6.*) - # Also install GCC if we have version 4.6.* which is - # known to give trouble within Sage: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48702 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52061 - # * https://groups.google.com/d/msg/sage-release/xgmJ3nAcUOY/jH8OZjftYRsJ - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION." - echo >&2 "gcc-4.6.* has known bugs affecting Sage." - need_to_install_gcc=yes;; - 4.7.0) - # GCC 4.7.0 is very broken on ia64, see - # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48496 - # This is fixed in GCC 4.7.1. - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc-4.7.0 has a serious bug on ia64." - need_to_install_gcc=yes - fi;; - esac - fi - - # Check C, C++ and Fortran compilers. - if [ -z "$CC" ]; then - echo >&2 "Installing GCC because a C compiler is missing." - need_to_install_gcc=yes - fi - - if [ -z "$FC" ]; then - echo >&2 "Installing GCC because a Fortran compiler is missing." - need_to_install_gcc=yes - fi -fi - -# If we are not installing GCC: check that the assembler and linker -# used by $CXX match $AS and $LD. -# See http://trac.sagemath.org/sage_trac/ticket/14296 -if [ $need_to_install_gcc != yes ]; then - if [ "$AS" != "" ]; then - CXX_as=`$CXX -print-file-name=as 2>/dev/null` - CXX_as=`command -v $CXX_as 2>/dev/null` - cmd_AS=`command -v $AS` - - if [ "$CXX_as" != "" -a "$CXX_as" != "$cmd_AS" ]; then - echo >&2 "Error: Mismatch of assemblers between" - echo >&2 " * $CXX using $CXX_as" - echo >&2 " * \$AS equal to $AS" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset AS or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use assembler $AS)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi - if [ "$LD" != "" ]; then - CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` - CXX_ld=`command -v $CXX_ld 2>/dev/null` - cmd_LD=`command -v $LD` - - if [ "$CXX_ld" != "" -a "$CXX_ld" != "$cmd_LD" ]; then - echo >&2 "Error: Mismatch of linkers between" - echo >&2 " * $CXX using $CXX_ld" - echo >&2 " * \$LD equal to $LD" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset LD or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use linker $LD)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi -fi - - -############################################################################### -# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps -############################################################################### - -# Trac #15624: use file descriptor 5 since make uses 3 and 4 -exec 5>Makefile - -cat >&5 <&5 "SHELL = `command -v bash`" -echo >&5 - -# Usage: newest_version $pkg -# Print version number of latest standard package $pkg -newest_version() { - PKG=$1 - if [ -f "$SAGE_ROOT/build/pkgs/$PKG/package-version.txt" ]; then - echo -n $PKG- - cat "$SAGE_ROOT/build/pkgs/$PKG/package-version.txt" - else - echo >&2 "Cannot determine latest version of $PKG." - echo "$PKG" - return 1 - fi -} - -# Outputs the list of packages, filtered by 'type', e.g.: -# -# filtered_packages_list base -# filtered_packages_list standard -# filtered_packages_list optional -# filtered_packages_list experimental -# -# Or, if you want all packages: -# -# filtered_packages_list all -# -# The output consists of triples -# PKG_NAME PKG_VERSION PKG_VAR -# - -filtered_packages_list() { - # for each package in pkgs/ - for DIR in $SAGE_ROOT/build/pkgs/*; do - - PKG_TYPE_FILE="$DIR/type" - if [ -f "$PKG_TYPE_FILE" ]; then - PKG_TYPE=`cat $PKG_TYPE_FILE` - else - echo >&2 "\"$PKG_TYPE_FILE\" is missing. Create it, and set its content" - echo >&2 "to 'base', 'standard', 'optional' or 'experimental'." - return 1 - fi - - # Check consistency of 'DIR/type' file - if [ "$PKG_TYPE" != "base" ] && [ "$PKG_TYPE" != "standard" ] && [ "$PKG_TYPE" != "optional" ] && [ "$PKG_TYPE" != "experimental" ]; then - echo >&2 "The content of \"$PKG_TYPE_FILE\" must be 'base', 'standard', 'optional' or 'experimental'" - return 1 - fi - - # Filter - if [ "$1" = "$PKG_TYPE" -o "$1" = all ]; then - PKG_NAME=$(basename $DIR) - PKG_VAR="$(echo $PKG_NAME | sed 's/^4/FOUR/' | tr '[:lower:]' '[:upper:]')" - PKG_VERSION=$(newest_version $PKG_NAME) - echo "$PKG_NAME $PKG_VERSION $PKG_VAR" - fi - done -} - -# Check that all packages define a (valid) 'type' file, or exit -filtered_packages_list none || exit $? - -echo >&5 "# All Sage packages" - -filtered_packages_list all | while read PKG_NAME PKG_VERSION PKG_VAR; do - echo >&5 "$PKG_VAR = $PKG_VERSION" - echo "$PKG_VAR=$PKG_VERSION" -done - -cat >&5 <&5 'PYTHON = $(PYTHON3)' -else - echo >&5 'PYTHON = $(PYTHON2)' -fi - -# Sage MP library -echo >&5 "SAGE_MP_LIBRARY = \$($SAGE_MP_LIBRARY)" - -# $(TOOLCHAIN) variable containing prerequisites for the build -echo >&5 -n 'TOOLCHAIN =' -if [ "$SAGE_INSTALL_CCACHE" = yes ]; then - echo >&5 -n ' $(INST)/$(CCACHE)' -fi -if [ "$need_to_install_gcc" = yes ]; then - echo >&5 -n ' $(INST)/$(GCC)' - # Use this option for the prereq configure script, such that it - # will skip all compiler checks. - export PREREQ_OPTIONS="--disable-compiler-checks $PREREQ_OPTIONS" -fi -echo >&5 -echo >&5 - -echo >&5 '# All standard packages' -echo >&5 'STANDARD_PACKAGES = \' -filtered_packages_list standard | while read PKG_NAME PKG_VERSION PKG_VAR; do - echo >&5 " \$(INST)/\$($PKG_VAR) \\" -done -echo >&5 -echo >&5 - -echo >&5 '# All optional installed packages (triggers the auto-update)' -echo >&5 'OPTIONAL_INSTALLED_PACKAGES = \' -filtered_packages_list optional | while read PKG_NAME PKG_VERSION PKG_VAR; do - if [ "$PKG_NAME" = "gcc" ]; then - continue - fi - if [ -f $SAGE_SPKG_INST/$PKG_NAME-* ]; then - echo >&5 " \$(INST)/\$($PKG_VAR) \\" - fi; -done -echo >&5 -echo >&5 - -echo >&5 'SCRIPT_SOURCES = \' -for file in "$SAGE_SRC/bin/"*; do - echo >&5 " \$(SAGE_SRC)${file#$SAGE_SRC} \\" -done -echo >&5 -echo >&5 'SCRIPTS = \' -for file in "$SAGE_SRC/bin/"*; do - echo >&5 " \$(SAGE_LOCAL)${file#$SAGE_SRC} \\" -done -echo >&5 -echo >&5 'EXTCODE_SOURCES = \' -for file in `find "$SAGE_SRC"/ext -type f`; do - echo >&5 " \$(SAGE_SRC)${file#$SAGE_SRC} \\" -done -echo >&5 -echo >&5 'EXTCODE = \' -for file in `find "$SAGE_SRC"/ext -type f`; do - echo >&5 " \$(SAGE_EXTCODE)${file#$SAGE_SRC/ext} \\" -done -echo >&5 - -# Copy build/make/deps -cat >&5 <&5 -# Copy build/make/deps - -cat >&5 </dependencies files -#============================================================================== - -EOF - -# Add a Makefile target corresponding to a given package -filtered_packages_list all | while read PKG_NAME PKG_VERSION PKG_VAR; do - DEP_FILE="$SAGE_ROOT/build/pkgs/$PKG_NAME/dependencies" - TYPE_FILE="$SAGE_ROOT/build/pkgs/$PKG_NAME/type" - if [ -f "$DEP_FILE" ]; then - DEPS=" $(head -n 1 $DEP_FILE)" - elif [ x`cat "$TYPE_FILE"` = xoptional ]; then - DEPS=' | $(STANDARD_PACKAGES)' # default for optional packages - else - DEPS="" - fi - echo >&5 "\$(INST)/$PKG_VERSION:$DEPS" - echo >&5 " +\$(PIPE) \"\$(SAGE_SPKG) \$($PKG_VAR) 2>&1\" \"tee -a \$(SAGE_LOGS)/\$($PKG_VAR).log\"" - echo >&5 - - # Add a target with just the bare package name such that - # "make ccache" for example builds the ccache package with - # dependency checking. - echo >&5 "$PKG_NAME: \$(INST)/\$($PKG_VAR)" - echo >&5 -done - -# Close the Makefile -exec 5>&- - ############################################################################### # Skip the rest if nothing to do (i.e., to [re]build). ############################################################################### diff --git a/build/make/pipestatus b/build/make/pipestatus deleted file mode 100755 index 7b7a5660754..00000000000 --- a/build/make/pipestatus +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# -# spkg/pipestatus: run two commands in a pipeline CMD1 | CMD2 and exit -# with the status of the last command to exit with a non-zero status, or -# zero if no command exited with a non-zero status. -# -# This is useful, for example, in a Makefile, where we tee the output -# of a build command to a log file. Using pipestatus, make will stop if -# tee succeeds but a preceding command in the pipeline fails. -# -# Syntactically, the command executed is "CMD1 | CMD2" where CMD1 and -# CMD2 are *not* treated as atoms. If CMD1 is "A && B" and CMD2 is "C", -# then pipestatus CMD1 CMD2 will act as A && (B | C). This also implies -# that CMD1 and CMD2 *MUST NOT* contain pipes, or this script will break. -# -# -# AUTHORS: -# -# - Jeroen Demeyer (2010-12-19): complete rewrite (#10339) -# -#***************************************************************************** -# Copyright (C) 2010 Jeroen Demeyer -# -# 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/ -#***************************************************************************** - - -if [ $# -ne 2 ] || [ -z "$1" -o -z "$2" ]; then - echo >&2 "Usage: $0 CMD1 CMD2" - echo >&2 "Run two commands in a pipeline 'CMD1 | CMD2' and exit with the status" - echo >&2 "of the last command to exit with a non-zero status, or zero if no" - echo >&2 "command exited with a non-zero status." - exit 2 -fi - -eval "$1 | $2; pipestatus=(\${PIPESTATUS[*]})" - -if [ ${pipestatus[1]} -ne 0 ]; then - exit ${pipestatus[1]} -else - exit ${pipestatus[0]} -fi diff --git a/build/pkgs/4ti2/SPKG.txt b/build/pkgs/4ti2/SPKG.txt index 7f1fdbbcea2..9a791bd4ce2 100644 --- a/build/pkgs/4ti2/SPKG.txt +++ b/build/pkgs/4ti2/SPKG.txt @@ -7,11 +7,6 @@ A software package for algebraic, geometric and combinatorial problems on linear == License == 4ti2 is released under a GPL v2 license. -== SPKG Maintainers == - -Marshall Hampton -Dima Pasechnik - == Upstream Contact == Raymond Hemmecke, TU Munich, Germany diff --git a/build/pkgs/arb/type b/build/pkgs/arb/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/arb/type +++ b/build/pkgs/arb/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/atlas/SPKG.txt b/build/pkgs/atlas/SPKG.txt index 0b771d5c571..14744b8881c 100644 --- a/build/pkgs/atlas/SPKG.txt +++ b/build/pkgs/atlas/SPKG.txt @@ -9,12 +9,6 @@ Solaris, but should also work on OSX and Cygwin. 3-clause BSD -== SPKG Maintainers == - - * David Kirkby - * William Stein - * Volker Braun - == Upstream Contact == * Atlas devel mailing list. diff --git a/build/pkgs/autotools/SPKG.txt b/build/pkgs/autotools/SPKG.txt index b1fd17ed307..e81fcd7ea6c 100644 --- a/build/pkgs/autotools/SPKG.txt +++ b/build/pkgs/autotools/SPKG.txt @@ -31,10 +31,6 @@ chosen version. GNU General Public License version 3 or later. -== SPKG Maintainers == - -* Jeroen Demeyer - == Upstream Contact == * http://www.gnu.org/software/texinfo/ diff --git a/build/pkgs/beautifulsoup/type b/build/pkgs/beautifulsoup/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/beautifulsoup/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/benzene/SPKG.txt b/build/pkgs/benzene/SPKG.txt index d2e244354e1..2ce530cf1d2 100644 --- a/build/pkgs/benzene/SPKG.txt +++ b/build/pkgs/benzene/SPKG.txt @@ -6,9 +6,6 @@ Benzene is a program for the efficient generation of all nonisomorphic fusenes a == License == Benzene is licensed under the GNU General Public License v2 or later ( June 2007 ) -== SPKG Maintainers == -Nico Van Cleemput (nico.vancleemput@gmail.com) - == Upstream Contact == Benzene was written by Gunnar Brinkmann and Gilles Caporossi. This version was adapted by Gunnar Brinkmann and Nico Van Cleemput for Grinvin. http://www.grinvin.org/ diff --git a/build/pkgs/biopython/type b/build/pkgs/biopython/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/biopython/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/bliss/SPKG.txt b/build/pkgs/bliss/SPKG.txt index a74709f7da1..84b97f0f8fc 100644 --- a/build/pkgs/bliss/SPKG.txt +++ b/build/pkgs/bliss/SPKG.txt @@ -7,12 +7,7 @@ forms of graphs. == License == -GPL - -== SPKG Maintainers == - -Jernej Azarija (jernej.azarija@gmail.com) -Nathann Cohen (nathann.cohen@gmail.com) +LGPL == Upstream Contact == diff --git a/build/pkgs/bliss/checksums.ini b/build/pkgs/bliss/checksums.ini index 740953e6b30..7fc59156c20 100644 --- a/build/pkgs/bliss/checksums.ini +++ b/build/pkgs/bliss/checksums.ini @@ -1,4 +1,4 @@ -tarball=bliss-VERSION.tar.bz2 -sha1=500796365fdf142dcfb34f979d678ffa456f1431 -md5=4e57118f9acad9a80baaefd93f967bdf -cksum=662785944 +tarball=bliss-VERSION.tar.gz +sha1=46322da1a03750e199e156d8967d88b89ebad5de +md5=b936d5d54b618a77ed59dfeeced3fa58 +cksum=76340303 diff --git a/build/pkgs/bliss/package-version.txt b/build/pkgs/bliss/package-version.txt index 615e437dd31..6ab5ccfd03e 100644 --- a/build/pkgs/bliss/package-version.txt +++ b/build/pkgs/bliss/package-version.txt @@ -1 +1 @@ -0.72.p1 +0.73 diff --git a/build/pkgs/bliss/patches/digraph_heuristic.patch b/build/pkgs/bliss/patches/digraph_heuristic.patch deleted file mode 100644 index 6e61458f424..00000000000 --- a/build/pkgs/bliss/patches/digraph_heuristic.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- src/graph.cc 2015-02-11 13:20:39.922021355 +0100 -+++ src-patched/graph_new.cc 2015-02-11 13:20:15.546020960 +0100 -@@ -1920,6 +1920,7 @@ - Digraph::Digraph(const unsigned int nof_vertices) - { - vertices.resize(nof_vertices); -+ sh = shs_f; - } - - diff --git a/build/pkgs/bliss/spkg-install b/build/pkgs/bliss/spkg-install index e3f9c7cb15a..746526d99b8 100755 --- a/build/pkgs/bliss/spkg-install +++ b/build/pkgs/bliss/spkg-install @@ -18,7 +18,7 @@ for patch in ../patches/*.patch; do done -$MAKE && mv libbliss.a "$SAGE_LOCAL/lib" && mv *.hh "$SAGE_LOCAL/include" +$MAKE && cp libbliss.a "$SAGE_LOCAL/lib" && cp *.hh "$SAGE_LOCAL/include" if [ $? -ne 0 ]; then echo "An error occurred whilst building bliss" diff --git a/build/pkgs/polybori/dependencies b/build/pkgs/boehm_gc/dependencies similarity index 56% rename from build/pkgs/polybori/dependencies rename to build/pkgs/boehm_gc/dependencies index 0b47764f31e..3546cda4614 100644 --- a/build/pkgs/polybori/dependencies +++ b/build/pkgs/boehm_gc/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) $(INST)/$(IPYTHON) $(INST)/$(SCONS) $(INST)/$(BOOST_CROPPED) $(INST)/$(M4RI) $(INST)/$(LIBGD) +# no dependencies ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/boost_cropped/dependencies b/build/pkgs/boost_cropped/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/boost_cropped/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/brial/SPKG.txt b/build/pkgs/brial/SPKG.txt new file mode 100644 index 00000000000..6dc0cead9e7 --- /dev/null +++ b/build/pkgs/brial/SPKG.txt @@ -0,0 +1,15 @@ += BRiAl = + +== Description == + +BRiAl is the successor to PolyBoRi. + +The core of PolyBoRi is a C++ library, which provides high-level data +types for Boolean polynomials and monomials, exponent vectors, as well +as for the underlying polynomial rings and subsets of the powerset of +the Boolean variables. As a unique approach, binary decision diagrams +are used as internal storage type for polynomial structures. On top of +this C++-library we provide a Python interface. This allows parsing of +complex polynomial systems, as well as sophisticated and extendable +strategies for Gröbner base computation. PolyBoRi features a powerful +reference implementation for Gröbner basis computation. diff --git a/build/pkgs/brial/checksums.ini b/build/pkgs/brial/checksums.ini new file mode 100644 index 00000000000..3c43fcb5c59 --- /dev/null +++ b/build/pkgs/brial/checksums.ini @@ -0,0 +1,4 @@ +tarball=brial-VERSION.tar.bz2 +sha1=8b30a7b331323eae094752dd7f88b398e6ed742a +md5=66275eea0654cb1eb6438a6262140885 +cksum=2952216147 diff --git a/build/pkgs/brial/dependencies b/build/pkgs/brial/dependencies new file mode 100644 index 00000000000..110acac9204 --- /dev/null +++ b/build/pkgs/brial/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(BOOST_CROPPED) $(INST)/$(M4RI) $(INST)/$(LIBPNG) $(INST)/$(PKGCONF) $(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/brial/package-version.txt b/build/pkgs/brial/package-version.txt new file mode 100644 index 00000000000..6f890028057 --- /dev/null +++ b/build/pkgs/brial/package-version.txt @@ -0,0 +1 @@ +0.8.4.3 diff --git a/build/pkgs/brial/spkg-install b/build/pkgs/brial/spkg-install new file mode 100755 index 00000000000..a6f4ada5110 --- /dev/null +++ b/build/pkgs/brial/spkg-install @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +echo "Cleaning out old PolyBoRi and BRiAl installations" +rm -rf "$SAGE_LOCAL"/lib/python/site-packages/{polybori,brial} +rm -f "$SAGE_LOCAL"/lib/lib{polybori,brial}* +rm -rf "$SAGE_LOCAL"/include/polybori* +rm -rf "$SAGE_LOCAL"/share/polybori + +cd src + +./configure \ + --prefix="$SAGE_LOCAL" \ + --libdir="$SAGE_LOCAL/lib" \ + --enable-shared --disable-static +if [ $? -ne 0 ]; then + echo "Error configuring BRiAl" + exit 1 +fi + +$MAKE +if [ $? -ne 0 ]; then + echo "Error building BRiAl" + exit 1 +fi + +$MAKE install -j1 +if [ $? -ne 0 ]; then + echo "Error installing BRiAl" + exit 1 +fi diff --git a/build/pkgs/polybori/type b/build/pkgs/brial/type similarity index 100% rename from build/pkgs/polybori/type rename to build/pkgs/brial/type diff --git a/build/pkgs/brian/type b/build/pkgs/brian/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/brian/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/buckygen/SPKG.txt b/build/pkgs/buckygen/SPKG.txt index 4dd5a280a36..08e5f764ed0 100644 --- a/build/pkgs/buckygen/SPKG.txt +++ b/build/pkgs/buckygen/SPKG.txt @@ -6,9 +6,6 @@ Buckygen is a program for the efficient generation of all nonisomorphic fulleren == License == Buckygen is licensed under the GNU General Public License v3 ( June 2007 ) -== SPKG Maintainers == -Nico Van Cleemput (nico.vancleemput@gmail.com) - == Upstream Contact == Buckygen was mainly written by Jan Goedgebeur, jan.goedgebeur[at]ugent.be. http://caagt.ugent.be/buckygen/ diff --git a/build/pkgs/buckygen/dependencies b/build/pkgs/buckygen/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/buckygen/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/bzip2/SPKG.txt b/build/pkgs/bzip2/SPKG.txt index dc5d6e404bb..63950e6014d 100644 --- a/build/pkgs/bzip2/SPKG.txt +++ b/build/pkgs/bzip2/SPKG.txt @@ -12,10 +12,6 @@ decompression. BSD-style -== SPKG Maintainers == - -None - == Upstream Contact == * Website http://bzip.org/ diff --git a/build/pkgs/cddlib/SPKG.txt b/build/pkgs/cddlib/SPKG.txt index dba244a12b5..bd1deec70b0 100644 --- a/build/pkgs/cddlib/SPKG.txt +++ b/build/pkgs/cddlib/SPKG.txt @@ -21,10 +21,6 @@ a linear function over P. == License == GPL v2 -== SPKG Maintainers == - * Marshall Hampton - * Volker Braun - == Upstream Contact == Komei Fukuda Institute for Operations Research diff --git a/build/pkgs/cephes/dependencies b/build/pkgs/cephes/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cephes/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/cliquer/SPKG.txt b/build/pkgs/cliquer/SPKG.txt index 4b1557c587a..853c784786e 100644 --- a/build/pkgs/cliquer/SPKG.txt +++ b/build/pkgs/cliquer/SPKG.txt @@ -8,9 +8,6 @@ developed by Patr Ostergard. == License == GNU General Public License v2 -== SPKG Maintainers == -Nathann Cohen (nathann.cohen@gmail.com) - == Upstream Contact == Cliquer was mainly written by Sampo Niskanen, sampo.niskanenQiki.fi (Q=@). http://users.tkk.fi/pat/cliquer.html diff --git a/build/pkgs/cliquer/dependencies b/build/pkgs/cliquer/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cliquer/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/cliquer/spkg-install b/build/pkgs/cliquer/spkg-install index 4e7d6a5c5a8..87f23bb59c7 100755 --- a/build/pkgs/cliquer/spkg-install +++ b/build/pkgs/cliquer/spkg-install @@ -24,7 +24,6 @@ fi # Flags for building a dynamically linked shared object. if [ "$UNAME" = "Darwin" ]; then - export MACOSX_DEPLOYMENT_TARGET="10.3" SAGESOFLAGS="-dynamiclib -single_module -flat_namespace -undefined dynamic_lookup" elif [ "$UNAME" = "SunOS" ]; then SAGESOFLAGS="-shared -Wl,-h,libcliquer.so -Wl,-ztext" @@ -63,6 +62,7 @@ cp *.h "$SAGE_LOCAL/include/cliquer/" if [ "$UNAME" = "Darwin" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dylib" + install_name_tool -id "${SAGE_LOCAL}"/lib/libcliquer.dylib "${SAGE_LOCAL}"/lib/libcliquer.dylib elif [ "$UNAME" = "CYGWIN" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dll" fi diff --git a/build/pkgs/cmake/SPKG.txt b/build/pkgs/cmake/SPKG.txt new file mode 100644 index 00000000000..765f543db9d --- /dev/null +++ b/build/pkgs/cmake/SPKG.txt @@ -0,0 +1,29 @@ += CMake = + +== Description == + +The "cmake" executable is the CMake command-line interface. It may be +used to configure projects in scripts. Project configuration settings +may be specified on the command line with the -D option. The -i +option will cause cmake to interactively prompt for such settings. + +CMake is a cross-platform build system generator. Projects specify +their build process with platform-independent CMake listfiles +included in each directory of a source tree with the name +CMakeLists.txt. Users build a project by using CMake to generate a +build system for a native tool on their platform. + +Website: www.cmake.org + +== License == + +CMake is distributed under the OSI-approved BSD 3-clause License. + +== Upstream Contact == + + * cmake-developers@cmake.org + +== Dependencies == + +* zlib +* bzip2 diff --git a/build/pkgs/cmake/checksums.ini b/build/pkgs/cmake/checksums.ini new file mode 100644 index 00000000000..da9dbd8cd7e --- /dev/null +++ b/build/pkgs/cmake/checksums.ini @@ -0,0 +1,4 @@ +tarball=cmake-VERSION.tar.gz +sha1=fa176cc5b1ccf2e98196b50908432d0268323501 +md5=d51c92bf66b1e9d4fe2b7aaedd51377c +cksum=3408528151 diff --git a/build/pkgs/cmake/package-version.txt b/build/pkgs/cmake/package-version.txt new file mode 100644 index 00000000000..b347b11eac8 --- /dev/null +++ b/build/pkgs/cmake/package-version.txt @@ -0,0 +1 @@ +3.2.3 diff --git a/build/pkgs/cmake/patches/osx10.10.patch b/build/pkgs/cmake/patches/osx10.10.patch new file mode 100644 index 00000000000..78045dcbb63 --- /dev/null +++ b/build/pkgs/cmake/patches/osx10.10.patch @@ -0,0 +1,44 @@ +Patch CMake to avoid using faulty system header in OSX 10.10 + +CMake project files include CoreFoundation/CoreFoundation.h which is a collection +of headers one of which is CoreFoundation/CFStream.h. It includes faulty header +dispatch/dispatch.h. Since CoreFoundation/CFStream.h is not needed for CMake, +specific headers needed are included to replace CoreFoundation/CoreFoundation.h + +diff -dru src/Source/cmFindProgramCommand.cxx new/Source/cmFindProgramCommand.cxx +--- src/Source/cmFindProgramCommand.cxx 2015-03-10 20:08:44.000000000 +0530 ++++ new/Source/cmFindProgramCommand.cxx 2015-06-17 13:26:32.543667394 +0530 +@@ -14,7 +14,7 @@ + #include + + #if defined(__APPLE__) +-#include ++#include + #endif + + // cmFindProgramCommand +diff -dru src/Source/cmXCodeObject.cxx new/Source/cmXCodeObject.cxx +--- src/Source/cmXCodeObject.cxx 2015-03-10 20:08:44.000000000 +0530 ++++ new/Source/cmXCodeObject.cxx 2015-06-17 13:26:37.191667311 +0530 +@@ -12,7 +12,7 @@ + #include "cmXCodeObject.h" + #include "cmSystemTools.h" + +-#include // CFUUIDCreate ++#include // CFUUIDCreate + + //---------------------------------------------------------------------------- + const char* cmXCodeObject::PBXTypeNames[] = { +diff -dru src/Source/CPack/OSXScriptLauncher.cxx new/Source/CPack/OSXScriptLauncher.cxx +--- src/Source/CPack/OSXScriptLauncher.cxx 2015-03-10 20:08:44.000000000 +0530 ++++ new/Source/CPack/OSXScriptLauncher.cxx 2015-06-17 13:26:21.575667590 +0530 +@@ -14,7 +14,7 @@ + #include + #include + +-#include ++#include + + // For the PATH_MAX constant + #include + diff --git a/build/pkgs/cmake/spkg-check b/build/pkgs/cmake/spkg-check new file mode 100755 index 00000000000..ec72970e697 --- /dev/null +++ b/build/pkgs/cmake/spkg-check @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd src +ctest -E CTestTestStopTime diff --git a/build/pkgs/cmake/spkg-install b/build/pkgs/cmake/spkg-install new file mode 100755 index 00000000000..786c15225c9 --- /dev/null +++ b/build/pkgs/cmake/spkg-install @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +system_curl="" + +if [ "$UNAME" = "Darwin" ]; then + system_curl="--system-curl" + unset MACOSX_DEPLOYMENT_TARGET +fi + +./bootstrap --prefix=$SAGE_LOCAL \ + --system-bzip2 \ + --system-zlib \ + $system_curl \ + -- \ + -DCMAKE_PREFIX_PATH=$SAGE_LOCAL \ + -DCMAKE_TESTS_CDASH_SERVER=NOTFOUND + +if [ $? -ne 0 ]; then + echo >&2 "Error configuring CMake." + exit 1 +fi + +$MAKE install +if [ $? -ne 0 ]; then + echo >&2 "Error installing CMake." + exit 1 +fi diff --git a/build/pkgs/cmake/type b/build/pkgs/cmake/type new file mode 100644 index 00000000000..9839eb20815 --- /dev/null +++ b/build/pkgs/cmake/type @@ -0,0 +1 @@ +experimental diff --git a/build/pkgs/combinatorial_designs/SPKG.txt b/build/pkgs/combinatorial_designs/SPKG.txt index 339880eaa80..e0fb5cfb6b2 100644 --- a/build/pkgs/combinatorial_designs/SPKG.txt +++ b/build/pkgs/combinatorial_designs/SPKG.txt @@ -7,10 +7,6 @@ Data for Combinatorial Designs. Current content: - The table of MOLS (10 000 integers) from the Handbook of Combinatorial Designs, 2ed. -== SPKG Maintainers == - - * Nathann Cohen - == License == Public domain. diff --git a/build/pkgs/combinatorial_designs/dependencies b/build/pkgs/combinatorial_designs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/combinatorial_designs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/compilerwrapper/dependencies b/build/pkgs/compilerwrapper/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/compilerwrapper/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/configure/SPKG.txt b/build/pkgs/configure/SPKG.txt index 932c9c5c3d9..72f87196c77 100644 --- a/build/pkgs/configure/SPKG.txt +++ b/build/pkgs/configure/SPKG.txt @@ -10,11 +10,6 @@ autotools version installed. GPLv3+ -== SPKG Maintainers == - -* Jeroen Demeyer -* Volker Braun - == Upstream Contact == diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index e6e6098c990..c6a3fac1146 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=74c12f15bd843d9bb723a17bac629846e4e8f57a -md5=84df33fa9762cb8826ced5a7e6aadbc0 -cksum=4042526937 +sha1=d7f2ecde69b59bec3887fd0ba1f4e2784394fd6b +md5=fa6127892f72b56b96a592fcec729f25 +cksum=1455899060 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index fe4afb0df86..9f54fe3133b 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -106 +122 diff --git a/build/pkgs/conway_polynomials/SPKG.txt b/build/pkgs/conway_polynomials/SPKG.txt index 5ccc07a0924..de510059e7a 100644 --- a/build/pkgs/conway_polynomials/SPKG.txt +++ b/build/pkgs/conway_polynomials/SPKG.txt @@ -4,11 +4,6 @@ Contains a small database of Conway polynomials. -== SPKG Maintainers == - - * R. Andrew Ohana - * William Stein - == Dependencies == * Sage library diff --git a/build/pkgs/conway_polynomials/spkg-install b/build/pkgs/conway_polynomials/spkg-install index 0023233e122..441dc8e8381 100755 --- a/build/pkgs/conway_polynomials/spkg-install +++ b/build/pkgs/conway_polynomials/spkg-install @@ -1,7 +1,7 @@ #!/usr/bin/env python import os -from sage.all import save +from sage.structure.sage_object import save from sage.env import SAGE_SHARE install_root = os.path.join(SAGE_SHARE, 'conway_polynomials') diff --git a/build/pkgs/coxeter3/SPKG.txt b/build/pkgs/coxeter3/SPKG.txt new file mode 100644 index 00000000000..4ae292eea1c --- /dev/null +++ b/build/pkgs/coxeter3/SPKG.txt @@ -0,0 +1,49 @@ += coxeter3 = + +== Description == + +This package wraps Fokko Ducloux's Coxeter 3 C++ library + +Features: + +- General Coxeter groups, implemented through the combinatorics of reduced words; +- Reduced expression and normal form computations; +- Bruhat ordering; +- Ordinary Kazhdan-Lusztig polynomials; +- Kazhdan-Lusztig polynomials with unequal parameters; +- Inverse Kazhdan-Lusztig polynomials; +- Cells and W-graphs; + +http://math.univ-lyon1.fr/~ducloux/coxeter/coxeter3/english/coxeter3_e.html + +This is a patched version done by Mike Hansen 2009-2013 and some fixes +by Nicolas M. Thiéry and Jean-Pierre Flori. + +== License == + +GPL + +== Upstream Contact == + +github: https://github.com/tscrim/coxeter + +Alas, Fokko Ducloux passed away in 2006. + +http://math.univ-lyon1.fr/~ducloux/du_Cloux.html + +== Dependencies == + +None + +== Special Update/Build Instructions == + +The application of the following patches is automatically handled +by spkg-install: + +- directories.h: update coxeter3 directory information to fetch + its data and messages from the Sage installation tree +- makefile.patch: handle compilation flags for Darwin and Cygwin +- warning.patch: remove deprecation warnings +- sage.cpp: add a function for Bruhat invervals which is simpler to + code in C++ than Cython + diff --git a/build/pkgs/coxeter3/checksums.ini b/build/pkgs/coxeter3/checksums.ini new file mode 100644 index 00000000000..1108974e4e4 --- /dev/null +++ b/build/pkgs/coxeter3/checksums.ini @@ -0,0 +1,4 @@ +tarball=coxeter3-VERSION.tar.gz +sha1=1f4b7450d3a59453f3491c1f3ffe914f6cb7b7fe +md5=60303e1d96801b8186fdf538c038a457 +cksum=3408091063 diff --git a/build/pkgs/coxeter3/dependencies b/build/pkgs/coxeter3/dependencies new file mode 100644 index 00000000000..2f9f3849682 --- /dev/null +++ b/build/pkgs/coxeter3/dependencies @@ -0,0 +1 @@ +# no dependencies diff --git a/build/pkgs/coxeter3/package-version.txt b/build/pkgs/coxeter3/package-version.txt new file mode 100644 index 00000000000..9459d4ba2a0 --- /dev/null +++ b/build/pkgs/coxeter3/package-version.txt @@ -0,0 +1 @@ +1.1 diff --git a/build/pkgs/coxeter3/patches/directories.h b/build/pkgs/coxeter3/patches/directories.h new file mode 100644 index 00000000000..1ddd9f3873c --- /dev/null +++ b/build/pkgs/coxeter3/patches/directories.h @@ -0,0 +1,32 @@ +/* + This is directories.h + + Coxeter version 3.0 Copyright (C) 2002 Fokko du Cloux + See file main.cpp for full copyright notice +*/ + +#ifndef DIRECTORIES_H /* guard against multiple inclusions */ +#define DIRECTORIES_H + +/* + This file tells where the directories can be found which contain some + auxiliary files used by the program. The following directories are defined : + + - COXMATRIX_DIR : contains the files for predefined Coxeter matrices; + these are the files that are loaded through the "X" group type. + + - HEADER_DIR : contains headers for the output to files done by some + of the commands. + + - MESSAGE_DIR : contains the text of various error and warning messages. + This is used mostly by the help facility, and also in some of the + error handling. +*/ + +namespace directories { + const char* const COXMATRIX_DIR = "SAGE_LOCAL/coxeter/coxeter_matrices"; + const char* const HEADER_DIR = "SAGE_LOCAL/coxeter/headers"; + const char* const MESSAGE_DIR = "SAGE_LOCAL/coxeter/messages"; +}; + +#endif diff --git a/build/pkgs/coxeter3/patches/makefile.patch b/build/pkgs/coxeter3/patches/makefile.patch new file mode 100644 index 00000000000..e2f7a523675 --- /dev/null +++ b/build/pkgs/coxeter3/patches/makefile.patch @@ -0,0 +1,107 @@ +diff -druN src/makefile src/makefile +--- src/makefile 2013-02-14 18:26:28.000000000 +0100 ++++ src/makefile 2013-02-15 14:34:54.082756900 +0100 +@@ -12,6 +12,7 @@ + gflags = -c $(includedirs) -g + + cflags = $(gflags) # the default setting ++cflags = -c $(includedirs) -O2 -fPIC -g -DFAST -DALLTRUE + + ifdef optimize + NDEBUG = true +@@ -22,18 +23,74 @@ + cflags = $(pflags) + endif + +-cc = g++ ++EXENAME = coxeter ++LIBNAME = coxeter3 ++ifeq ($(UNAME),Darwin) ++ EXEEXT = ++ LIBPREFIX = lib ++ LIBEXT = .dylib ++ LIBDIR = lib ++ LINKFLAGS = -dynamiclib -Wl,-headerpad_max_install_names,-undefined,dynamic_lookup,-compatibility_version,3.0,-current_version,3.0,-install_name,$(SAGE_LOCAL)/lib/$(LIBPREFIX)$(LIBNAME)$(LIBEXT) ++ LINKLIBS = ++else ++ifeq ($(UNAME),CYGWIN) ++ EXEEXT = .exe ++ LIBPREFIX = cyg ++ LIBEXT = .dll ++ LIBDIR = bin ++ IMPLIB = lib$(LIBNAME).dll.a ++ LINKFLAGS = -shared -Wl,--out-implib=$(IMPLIB) -Wl,--export-all-symbols ++ LINKLIBS = -lc ++else ++ EXEEXT = ++ LIBPREFIX = lib ++ LIBEXT = .so ++ LIBDIR = lib ++ LINKFLAGS = -shared -Wl,-soname,libcoxeter3.so ++ LINKLIBS = -lc ++endif ++endif ++LIBRARY = $(LIBPREFIX)$(LIBNAME)$(LIBEXT) + +-all: coxeter #clean ++all: coxeter executable + + coxeter: $(objects) +- $(cc) -o coxeter $(objects) ++ $(CXX) $(LINKFLAGS) -o $(LIBRARY) $(objects) $(LINKLIBS) ++ ++executable: $(objects) ++ $(CXX) -o $(EXENAME)$(EXEEXT) $(objects) ++ ++DATADIR="$$SAGE_LOCAL/coxeter/" ++INCLUDEDIR="$$SAGE_LOCAL/include/coxeter/" ++LIBRARYDIR="$$SAGE_LOCAL/$(LIBDIR)" ++ ++install: coxeter executable ++ cp $(EXENAME)$(EXEEXT) "$$SAGE_LOCAL/bin/" ++ cp $(LIBRARY) $(LIBRARYDIR) ++ if [ $(UNAME) = "CYGWIN" ]; then \ ++ cp $(IMPLIB) "$$SAGE_LOCAL/lib/"; \ ++ fi ++ ++ mkdir -p $(DATADIR) ++ cp -r coxeter_matrices headers messages $(DATADIR) ++ mkdir -p $(INCLUDEDIR) ++ cp -r *.h *.hpp $(INCLUDEDIR) ++ ++check: coxeter executable ++ $(EXENAME)$(EXEEXT) < test.input > test.output ++ ++ if ! diff test.output.expected test.output > /dev/null; then \ ++ echo >&2 "Error testing coxeter on test.input:"; \ ++ diff test.output.expected test.output; \ ++ exit 1; \ ++ fi ++ rm -f test.output + + clean: + rm -f $(objects) + + %.o:%.cpp +- $(cc) $(cflags) $*.cpp ++ $(CXX) $(cflags) $*.cpp + + # dependencies --- these were generated automatically by make depend on my + # system; they are explicitly copied for portability. Only local dependencies +@@ -43,7 +100,7 @@ + # contents of tmp in lieu of the dependencies listed here. + + %.d:%.cpp +- @$(cc) -MM $*.cpp ++ @$(CXX) -MM $*.cpp + depend: $(dependencies) + + affine.o: affine.cpp affine.h globals.h coxgroup.h coxtypes.h io.h list.h \ +@@ -152,6 +209,7 @@ + posets.o: posets.cpp posets.h globals.h bits.h list.h memory.h \ + constants.h list.hpp error.h io.h wgraph.h interface.h automata.h \ + coxtypes.h minroots.h dotval.h graph.h type.h transducer.h ++sage.o: sage.cpp sage.h globals.h coxtypes.h coxgroup.h list.h schubert.h + schubert.o: schubert.cpp schubert.h globals.h coxtypes.h io.h list.h \ + memory.h constants.h list.hpp error.h bits.h interface.h automata.h \ + minroots.h dotval.h graph.h type.h transducer.h stack.h stack.hpp diff --git a/build/pkgs/coxeter3/patches/sage.cpp b/build/pkgs/coxeter3/patches/sage.cpp new file mode 100644 index 00000000000..b649bb2b8b6 --- /dev/null +++ b/build/pkgs/coxeter3/patches/sage.cpp @@ -0,0 +1,60 @@ +/* + This is coxgroup.cpp + + Coxeter version 3.0 Copyright (C) 2009 Mike Hansen + See file main.cpp for full copyright notice +*/ + +#include "sage.h" + +#include "error.h" + +namespace sage { + + void interval(List& list, coxgroup::CoxGroup& W, const CoxWord& g, const CoxWord& h) + + /* + Returns a list of the elements in the Bruhat interval between g and h. + Note that this assumes that g and h are in order. + */ + { + if (not W.inOrder(g,h)) { + return; + } + + W.extendContext(h); + + CoxNbr x = W.contextNumber(g); + CoxNbr y = W.contextNumber(h); + + BitMap b(W.contextSize()); + W.extractClosure(b,y); + + BitMap::ReverseIterator b_rend = b.rend(); + List res(0); + + for (BitMap::ReverseIterator i = b.rbegin(); i != b_rend; ++i) + if (not W.inOrder(x,*i)) { + BitMap bi(W.contextSize()); + W.extractClosure(bi,*i); + CoxNbr z = *i; // andnot will invalidate iterator + b.andnot(bi); + b.setBit(z); // otherwise the decrement will not be correct + } else + res.append(*i); + + schubert::NFCompare nfc(W.schubert(),W.ordering()); + Permutation a(res.size()); + sortI(res,nfc,a); + + list.setSize(0); + for (size_t j = 0; j < res.size(); ++j) { + CoxWord w(0); + W.schubert().append(w, res[a[j]]); + list.append(w); + } + + return; + } + +} diff --git a/build/pkgs/coxeter3/patches/sage.h b/build/pkgs/coxeter3/patches/sage.h new file mode 100644 index 00000000000..40b3cf9585e --- /dev/null +++ b/build/pkgs/coxeter3/patches/sage.h @@ -0,0 +1,26 @@ +/* + This is interactive.h + + Coxeter version 3.0 Copyright (C) 2009 Mike Hansen + See file main.cpp for full copyright notice +*/ + +#ifndef SAGE_H /* guard against multiple inclusions */ +#define SAGE_H + +#include "globals.h" +#include "coxgroup.h" +#include "coxtypes.h" +#include "schubert.h" + +namespace sage { + using namespace globals; +} + +/******** function declarations **********************************************/ + +namespace sage { + void interval(List& result, coxgroup::CoxGroup& W, const CoxWord& g, const CoxWord& h); +} + +#endif diff --git a/build/pkgs/coxeter3/patches/test.input b/build/pkgs/coxeter3/patches/test.input new file mode 100644 index 00000000000..fb732368d43 --- /dev/null +++ b/build/pkgs/coxeter3/patches/test.input @@ -0,0 +1,9 @@ +type +A +3 +compute +1 3 2 1 2 3 1 2 1 +ihbetti +213 +q +q diff --git a/build/pkgs/coxeter3/patches/test.output.expected b/build/pkgs/coxeter3/patches/test.output.expected new file mode 100644 index 00000000000..0a4fca16a11 --- /dev/null +++ b/build/pkgs/coxeter3/patches/test.output.expected @@ -0,0 +1,13 @@ +This is Coxeter version 3.0_beta2. +Enter help if you need assistance, carriage return to start the program. + +coxeter : +type : +rank : coxeter : enter your element (finish with a carriage return) : +213 +coxeter : enter your element (finish with a carriage return) : +h[0] = 1 h[1] = 3 h[2] = 3 h[3] = 1 + +size : 8 + +coxeter : coxeter : \ No newline at end of file diff --git a/build/pkgs/coxeter3/patches/warnings.patch b/build/pkgs/coxeter3/patches/warnings.patch new file mode 100644 index 00000000000..4d4111c4d3d --- /dev/null +++ b/build/pkgs/coxeter3/patches/warnings.patch @@ -0,0 +1,36 @@ +diff -druN a/error.cpp b/error.cpp +--- a/error.cpp ++++ b/error.cpp +@@ -250,7 +250,7 @@ void error::Error(int number, ... ) + notCoxelt(); + break; + case NOT_DESCENT: { +- const char *const str = va_arg(ap,const char *const); ++ const char *str = va_arg(ap,const char *); + notDescent(str); + break; + } +@@ -280,7 +280,7 @@ void error::Error(int number, ... ) + parNbrOverflow(); + break; + case PARSE_ERROR: { +- const char *const str = va_arg(ap,const char *const); ++ const char *str = va_arg(ap,const char *); + parseError(str); + } + break; +diff -druN a/version.h b/version.h +--- a/version.h ++++ b/version.h +@@ -9,8 +10,8 @@ + #define VERSION_H + + namespace version { +- char* const NAME = "Coxeter"; +- char* const VERSION = "3.0_beta2"; ++ const char* const NAME = "Coxeter"; ++ const char* const VERSION = "3.1.sage"; + }; + + #endif + diff --git a/build/pkgs/coxeter3/spkg-check b/build/pkgs/coxeter3/spkg-check new file mode 100755 index 00000000000..31f90289772 --- /dev/null +++ b/build/pkgs/coxeter3/spkg-check @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting"; + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src + +$MAKE check +if [ $? -ne 0 ]; then + echo >&2 "Error running coxeter testsuite" + exit 1 +fi diff --git a/build/pkgs/coxeter3/spkg-install b/build/pkgs/coxeter3/spkg-install new file mode 100755 index 00000000000..f1c64f35e3c --- /dev/null +++ b/build/pkgs/coxeter3/spkg-install @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting"; + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + +# Configure +cd patches +cp directories.h directories.tmpl +sed -i "s|SAGE_LOCAL|$SAGE_LOCAL|g" directories.tmpl + +# Patch the Coxeter3 sources +cd ../src +cp ../patches/directories.tmpl directories.h +cp ../patches/sage.cpp ../patches/sage.h . +cp ../patches/test.input ../patches/test.output.expected . +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +# Build +$MAKE all +if [ $? -ne 0 ]; then + echo >&2 "Error building coxeter3" + exit 1 +fi + +# Install +$MAKE install +if [ $? -ne 0 ]; then + echo >&2 "Error installing coxeter3" + exit 1 +fi diff --git a/build/pkgs/coxeter3/type b/build/pkgs/coxeter3/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/coxeter3/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/cryptominisat/SPKG.txt b/build/pkgs/cryptominisat/SPKG.txt index 6cb1ce301d0..e34296fdf7a 100644 --- a/build/pkgs/cryptominisat/SPKG.txt +++ b/build/pkgs/cryptominisat/SPKG.txt @@ -17,10 +17,6 @@ * GNU General Public License Version 3 or later (see src/COPYING) -== Maintainers == - - * Martin Albrecht - == Upstream Contact == * Authors: Mate Soos diff --git a/build/pkgs/cryptominisat/dependencies b/build/pkgs/cryptominisat/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cryptominisat/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/csdp/SPKG.txt b/build/pkgs/csdp/SPKG.txt index 69d471c9ad5..d7ece63b377 100644 --- a/build/pkgs/csdp/SPKG.txt +++ b/build/pkgs/csdp/SPKG.txt @@ -10,10 +10,6 @@ see https://projects.coin-or.org/Csdp Common Public License Version 1.0 -== SPKG Maintainers == - -* Dmitrii Pasechnik - == Upstream Contact == Dmitrii Pasechnik diff --git a/build/pkgs/cvxopt/SPKG.txt b/build/pkgs/cvxopt/SPKG.txt index 0327d750ff1..fca2859161e 100644 --- a/build/pkgs/cvxopt/SPKG.txt +++ b/build/pkgs/cvxopt/SPKG.txt @@ -11,13 +11,6 @@ applications straightforward by building on Python's extensive standard library and on the strengths of Python as a high-level programming language. -== MAINTAINERS == - - * William Stein - * Josh Kantor - * Harald Schilly - * Dima Pasechnik - == Upstream Contact == * J. Dahl diff --git a/build/pkgs/cython/SPKG.txt b/build/pkgs/cython/SPKG.txt index 1bb082d461e..6baffa31c41 100644 --- a/build/pkgs/cython/SPKG.txt +++ b/build/pkgs/cython/SPKG.txt @@ -20,9 +20,6 @@ Website: http://www.cython.org/ Apache License, Version 2.0 -== SPKG Maintainers == - * Robert Bradshaw - == Upstream Contact == * cython-devel@python.org diff --git a/build/pkgs/cython/checksums.ini b/build/pkgs/cython/checksums.ini index 1b08a8dbcc1..f3ac4410434 100644 --- a/build/pkgs/cython/checksums.ini +++ b/build/pkgs/cython/checksums.ini @@ -1,4 +1,4 @@ -tarball=cython-VERSION.tar.bz2 -sha1=89cf4f3d9a840e593da066a9f723b31a27d7c6a6 -md5=6fe7c9d93970ce602d60defd903188d8 -cksum=1851959595 +tarball=Cython-VERSION.tar.gz +sha1=d5592dc3d529c55a5ef95346caccf11c556993bd +md5=813df20f7ce5f00e60568e0371fbd07c +cksum=365027876 diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index 7d86a487d40..9e40e75c5d2 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -0.22.p5 +0.23.3 diff --git a/build/pkgs/cython/patches/api_mangle.patch b/build/pkgs/cython/patches/api_mangle.patch deleted file mode 100644 index 40eb7f1f3d8..00000000000 --- a/build/pkgs/cython/patches/api_mangle.patch +++ /dev/null @@ -1,63 +0,0 @@ -commit e310ccb4e933ea0ca986b69dfe325c3643d5cf78 -Author: Jeroen Demeyer -Date: Sat Mar 21 09:10:03 2015 +0100 - - Use different mangling prefix in foo_api.h files - -diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py -index f09313c..7a06009 100644 ---- a/Cython/Compiler/ModuleNode.py -+++ b/Cython/Compiler/ModuleNode.py -@@ -223,14 +223,14 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): - h_code.putln("") - for entry in api_funcs: - type = CPtrType(entry.type) -- cname = env.mangle(Naming.func_prefix, entry.name) -+ cname = env.mangle(Naming.func_prefix_api, entry.name) - h_code.putln("static %s = 0;" % type.declaration_code(cname)) - h_code.putln("#define %s %s" % (entry.name, cname)) - if api_vars: - h_code.putln("") - for entry in api_vars: - type = CPtrType(entry.type) -- cname = env.mangle(Naming.varptr_prefix, entry.name) -+ cname = env.mangle(Naming.varptr_prefix_api, entry.name) - h_code.putln("static %s = 0;" % type.declaration_code(cname)) - h_code.putln("#define %s (*%s)" % (entry.name, cname)) - h_code.put(UtilityCode.load_as_string("PyIdentifierFromString", "ImportExport.c")[0]) -@@ -247,13 +247,13 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): - h_code.putln('module = __Pyx_ImportModule("%s");' % env.qualified_name) - h_code.putln("if (!module) goto bad;") - for entry in api_funcs: -- cname = env.mangle(Naming.func_prefix, entry.name) -+ cname = env.mangle(Naming.func_prefix_api, entry.name) - sig = entry.type.signature_string() - h_code.putln( - 'if (__Pyx_ImportFunction(module, "%s", (void (**)(void))&%s, "%s") < 0) goto bad;' - % (entry.name, cname, sig)) - for entry in api_vars: -- cname = env.mangle(Naming.varptr_prefix, entry.name) -+ cname = env.mangle(Naming.varptr_prefix_api, entry.name) - sig = entry.type.empty_declaration_code() - h_code.putln( - 'if (__Pyx_ImportVoidPtr(module, "%s", (void **)&%s, "%s") < 0) goto bad;' -diff --git a/Cython/Compiler/Naming.py b/Cython/Compiler/Naming.py -index 55c1f87..6539b34 100644 ---- a/Cython/Compiler/Naming.py -+++ b/Cython/Compiler/Naming.py -@@ -18,6 +18,7 @@ arg_prefix = pyrex_prefix + "arg_" - funcdoc_prefix = pyrex_prefix + "doc_" - enum_prefix = pyrex_prefix + "e_" - func_prefix = pyrex_prefix + "f_" -+func_prefix_api = pyrex_prefix + "api_f_" - pyfunc_prefix = pyrex_prefix + "pf_" - pywrap_prefix = pyrex_prefix + "pw_" - genbody_prefix = pyrex_prefix + "gb_" -@@ -36,6 +37,7 @@ type_prefix = pyrex_prefix + "t_" - typeobj_prefix = pyrex_prefix + "type_" - var_prefix = pyrex_prefix + "v_" - varptr_prefix = pyrex_prefix + "vp_" -+varptr_prefix_api = pyrex_prefix + "api_vp_" - wrapperbase_prefix= pyrex_prefix + "wrapperbase_" - pybuffernd_prefix = pyrex_prefix + "pybuffernd_" - pybufferstruct_prefix = pyrex_prefix + "pybuffer_" diff --git a/build/pkgs/cython/patches/c_tuple_type_memleak.patch b/build/pkgs/cython/patches/c_tuple_type_memleak.patch deleted file mode 100644 index 7dc85aa31c0..00000000000 --- a/build/pkgs/cython/patches/c_tuple_type_memleak.patch +++ /dev/null @@ -1,124 +0,0 @@ -commit bb4d9c2de71b7c7e1e02d9dfeae53f4547fa9d7d -Author: Stefan Behnel -Date: Fri Apr 24 17:57:23 2015 +0200 - - replace the incorrect and leaky global c-tuple type cache by a per-module one - -diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py -index 0fed86a..cce5c50 100644 ---- a/Cython/Compiler/ExprNodes.py -+++ b/Cython/Compiler/ExprNodes.py -@@ -6539,9 +6539,7 @@ class TupleNode(SequenceNode): - if any(type.is_pyobject or type.is_unspecified or type.is_fused for type in arg_types): - return tuple_type - else: -- type = PyrexTypes.c_tuple_type(arg_types) -- env.declare_tuple_type(self.pos, type) -- return type -+ return env.declare_tuple_type(self.pos, arg_types).type - - def analyse_types(self, env, skip_children=False): - if len(self.args) == 0: -@@ -6552,8 +6550,7 @@ class TupleNode(SequenceNode): - if not skip_children: - self.args = [arg.analyse_types(env) for arg in self.args] - if not self.mult_factor and not any(arg.type.is_pyobject or arg.type.is_fused for arg in self.args): -- self.type = PyrexTypes.c_tuple_type(arg.type for arg in self.args) -- env.declare_tuple_type(self.pos, self.type) -+ self.type = env.declare_tuple_type(self.pos, (arg.type for arg in self.args)).type - self.is_temp = 1 - return self - else: -diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py -index d6700d0..3ee9e9a 100644 ---- a/Cython/Compiler/Nodes.py -+++ b/Cython/Compiler/Nodes.py -@@ -1180,10 +1180,9 @@ class CTupleBaseTypeNode(CBaseTypeNode): - error(c.pos, "Tuple types can't (yet) contain Python objects.") - return error_type - component_types.append(type) -- type = PyrexTypes.c_tuple_type(component_types) -- entry = env.declare_tuple_type(self.pos, type) -+ entry = env.declare_tuple_type(self.pos, component_types) - entry.used = True -- return type -+ return entry.type - - - class FusedTypeNode(CBaseTypeNode): -diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py -index a4fc38d..bc61b6f 100644 ---- a/Cython/Compiler/PyrexTypes.py -+++ b/Cython/Compiler/PyrexTypes.py -@@ -3684,13 +3684,11 @@ class CTupleType(CType): - env.use_utility_code(self._convert_from_py_code) - return True - --c_tuple_types = {} -+ - def c_tuple_type(components): - components = tuple(components) -- tuple_type = c_tuple_types.get(components) -- if tuple_type is None: -- cname = Naming.ctuple_type_prefix + type_list_identifier(components) -- tuple_type = c_tuple_types[components] = CTupleType(cname, components) -+ cname = Naming.ctuple_type_prefix + type_list_identifier(components) -+ tuple_type = CTupleType(cname, components) - return tuple_type - - -diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py -index 7409420..c2f3ac8 100644 ---- a/Cython/Compiler/Symtab.py -+++ b/Cython/Compiler/Symtab.py -@@ -610,8 +610,8 @@ class Scope(object): - self.sue_entries.append(entry) - return entry - -- def declare_tuple_type(self, pos, type): -- return self.outer_scope.declare_tuple_type(pos, type) -+ def declare_tuple_type(self, pos, components): -+ return self.outer_scope.declare_tuple_type(pos, components) - - def declare_var(self, name, type, pos, - cname = None, visibility = 'private', -@@ -1056,6 +1056,7 @@ class ModuleScope(Scope): - self.cached_builtins = [] - self.undeclared_cached_builtins = [] - self.namespace_cname = self.module_cname -+ self._cached_tuple_types = {} - for var_name in ['__builtins__', '__name__', '__file__', '__doc__', '__path__']: - self.declare_var(EncodedString(var_name), py_object_type, None) - -@@ -1075,18 +1076,24 @@ class ModuleScope(Scope): - - return self.outer_scope.lookup(name, language_level=language_level) - -- def declare_tuple_type(self, pos, type): -- cname = type.cname -+ def declare_tuple_type(self, pos, components): -+ components = tuple(components) -+ try: -+ ttype = self._cached_tuple_types[components] -+ except KeyError: -+ ttype = self._cached_tuple_types[components] = PyrexTypes.c_tuple_type(components) -+ cname = ttype.cname - entry = self.lookup_here(cname) - if not entry: - scope = StructOrUnionScope(cname) -- for ix, component in enumerate(type.components): -+ for ix, component in enumerate(components): - scope.declare_var(name="f%s" % ix, type=component, pos=pos) -- struct_entry = self.declare_struct_or_union(cname + '_struct', 'struct', scope, typedef_flag=True, pos=pos, cname=cname) -+ struct_entry = self.declare_struct_or_union( -+ cname + '_struct', 'struct', scope, typedef_flag=True, pos=pos, cname=cname) - self.type_entries.remove(struct_entry) -- type.struct_entry = struct_entry -- entry = self.declare_type(cname, type, pos, cname) -- type.entry = entry -+ ttype.struct_entry = struct_entry -+ entry = self.declare_type(cname, ttype, pos, cname) -+ ttype.entry = entry - return entry - - def declare_builtin(self, name, pos): diff --git a/build/pkgs/cython/patches/embedsignatures.patch b/build/pkgs/cython/patches/embedsignatures.patch deleted file mode 100644 index c3d81f0fb75..00000000000 --- a/build/pkgs/cython/patches/embedsignatures.patch +++ /dev/null @@ -1,44 +0,0 @@ -commit 9139a7f836151fb5bdb1624a05dce13b1bb17164 -Author: Stefan Behnel -Date: Mon Apr 6 10:45:48 2015 +0200 - - support NULL as default argument in auto doc transform - -diff --git a/Cython/Compiler/AutoDocTransforms.py b/Cython/Compiler/AutoDocTransforms.py -index 775f635..88b0cd8 100644 ---- a/Cython/Compiler/AutoDocTransforms.py -+++ b/Cython/Compiler/AutoDocTransforms.py -@@ -51,6 +51,8 @@ class EmbedSignature(CythonTransform): - default_val = arg.default - if not default_val: - return None -+ if isinstance(default_val, ExprNodes.NullNode): -+ return 'NULL' - try: - denv = self.denv # XXX - ctval = default_val.compile_time_value(self.denv) -diff --git a/tests/run/embedsignatures.pyx b/tests/run/embedsignatures.pyx -index 0bfebfe..781cd21 100644 ---- a/tests/run/embedsignatures.pyx -+++ b/tests/run/embedsignatures.pyx -@@ -199,6 +199,9 @@ __doc__ = ur""" - - >>> print(funcdoc(f_defexpr5)) - f_defexpr5(int x=4) -+ -+ >>> print(funcdoc(f_charptr_null)) -+ f_charptr_null(char *s=NULL) -> char * - """ - - cdef class Ext: -@@ -403,6 +406,10 @@ cpdef f_defexpr4(int x = (Ext.CONST1 + FLAG1) * Ext.CONST2): - cpdef f_defexpr5(int x = 2+2): - pass - -+cpdef (char*) f_charptr_null(char* s=NULL): -+ return s or b'abc' -+ -+ - # no signatures for lambda functions - lambda_foo = lambda x: 10 - lambda_bar = lambda x: 20 diff --git a/build/pkgs/cython/patches/extern_impl.patch b/build/pkgs/cython/patches/extern_impl.patch deleted file mode 100644 index a274fca3fff..00000000000 --- a/build/pkgs/cython/patches/extern_impl.patch +++ /dev/null @@ -1,29 +0,0 @@ -commit d389edef323a759031b87fd3bca459847548eeb8 -Author: Robert Bradshaw -Date: Mon Mar 9 22:08:59 2015 -0700 - - Allow external definitions of functions defined in .pxd files. - - From Jeroen Demeyer. - -diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py -index 339b509..1437f83 100644 ---- a/Cython/Compiler/Symtab.py -+++ b/Cython/Compiler/Symtab.py -@@ -698,8 +698,15 @@ class Scope(object): - cname = self.mangle(Naming.func_prefix, name) - entry = self.lookup_here(name) - if entry: -+ if not in_pxd and visibility != entry.visibility and visibility == 'extern': -+ # Previously declared, but now extern => treat this -+ # as implementing the function, using the new cname -+ defining = True -+ visibility = entry.visibility -+ entry.cname = cname -+ entry.func_cname = cname - if visibility != 'private' and visibility != entry.visibility: -- warning(pos, "Function '%s' previously declared as '%s'" % (name, entry.visibility), 1) -+ warning(pos, "Function '%s' previously declared as '%s', now as '%s'" % (name, entry.visibility, visibility), 1) - if overridable != entry.is_overridable: - warning(pos, "Function '%s' previously declared as '%s'" % ( - name, 'cpdef' if overridable else 'cdef'), 1) diff --git a/build/pkgs/cython/patches/find_pxd.patch b/build/pkgs/cython/patches/find_pxd.patch deleted file mode 100644 index 83a3dd18a61..00000000000 --- a/build/pkgs/cython/patches/find_pxd.patch +++ /dev/null @@ -1,128 +0,0 @@ -commit 2796ac2e8e5a57197cf03341895646b5fe0ba260 -Author: Jeroen Demeyer -Date: Fri Jul 3 22:02:43 2015 +0200 - - Do not search sys.path for .pxd file if no explicit "cimport" is given - -diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py -index 27963f2..5f1a3e9 100644 ---- a/Cython/Compiler/Main.py -+++ b/Cython/Compiler/Main.py -@@ -187,20 +187,25 @@ class Context(object): - if not scope.pxd_file_loaded: - if debug_find_module: - print("...pxd not loaded") -- scope.pxd_file_loaded = 1 - if not pxd_pathname: - if debug_find_module: - print("...looking for pxd file") -- pxd_pathname = self.find_pxd_file(module_name, pos) -+ # Only look in sys.path if we are explicitly looking -+ # for a .pxd file. -+ pxd_pathname = self.find_pxd_file(module_name, pos, sys_path=need_pxd) - if debug_find_module: - print("......found %s" % pxd_pathname) - if not pxd_pathname and need_pxd: -+ # Set pxd_file_loaded such that we don't need to -+ # look for the non-existing pxd file next time. -+ scope.pxd_file_loaded = True - package_pathname = self.search_include_directories(module_name, ".py", pos) - if package_pathname and package_pathname.endswith('__init__.py'): - pass - else: - error(pos, "'%s.pxd' not found" % module_name.replace('.', os.sep)) - if pxd_pathname: -+ scope.pxd_file_loaded = True - try: - if debug_find_module: - print("Context.find_module: Parsing %s" % pxd_pathname) -@@ -217,15 +222,16 @@ class Context(object): - pass - return scope - -- def find_pxd_file(self, qualified_name, pos): -- # Search include path for the .pxd file corresponding to the -- # given fully-qualified module name. -+ def find_pxd_file(self, qualified_name, pos, sys_path=True): -+ # Search include path (and sys.path if sys_path is True) for -+ # the .pxd file corresponding to the given fully-qualified -+ # module name. - # Will find either a dotted filename or a file in a - # package directory. If a source file position is given, - # the directory containing the source file is searched first - # for a dotted filename, and its containing package root - # directory is searched first for a non-dotted filename. -- pxd = self.search_include_directories(qualified_name, ".pxd", pos, sys_path=True) -+ pxd = self.search_include_directories(qualified_name, ".pxd", pos, sys_path=sys_path) - if pxd is None: # XXX Keep this until Includes/Deprecated is removed - if (qualified_name.startswith('python') or - qualified_name in ('stdlib', 'stdio', 'stl')): -diff --git a/docs/src/userguide/sharing_declarations.rst b/docs/src/userguide/sharing_declarations.rst -index f30234d..838d813 100644 ---- a/docs/src/userguide/sharing_declarations.rst -+++ b/docs/src/userguide/sharing_declarations.rst -@@ -123,12 +123,15 @@ Search paths for definition files - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - When you :keyword:`cimport` a module called ``modulename``, the Cython --compiler searches for a file called :file:`modulename.pxd` along the search --path for include files, as specified by ``-I`` command line options. -+compiler searches for a file called :file:`modulename.pxd`. -+It searches for this file along the path for include files -+(as specified by ``-I`` command line options or the ``include_path`` -+option to ``cythonize()``), as well as ``sys.path``. - - Also, whenever you compile a file :file:`modulename.pyx`, the corresponding --definition file :file:`modulename.pxd` is first searched for along the same --path, and if found, it is processed before processing the ``.pyx`` file. -+definition file :file:`modulename.pxd` is first searched for along the -+include path (but not ``sys.path``), and if found, it is processed before -+processing the ``.pyx`` file. - - - Using cimport to resolve naming conflicts -diff --git a/tests/compile/find_pxd.srctree b/tests/compile/find_pxd.srctree -new file mode 100644 -index 0000000..1d7885f ---- /dev/null -+++ b/tests/compile/find_pxd.srctree -@@ -0,0 +1,39 @@ -+PYTHON setup.py build_ext --inplace -+ -+######## setup.py ######## -+ -+from Cython.Build import cythonize -+from Cython.Distutils.extension import Extension -+ -+import sys -+sys.path.append("path") -+ -+ext_modules = [ -+ Extension("a", ["a.pyx"]), -+ Extension("b", ["b.pyx"]), -+ Extension("c", ["c.pyx"]), -+] -+ -+ext_modules = cythonize(ext_modules, include_path=["include"]) -+ -+ -+######## a.pyx ######## -+# Implicit cimport looking in include_path -+cdef my_type foo -+ -+######## include/a.pxd ######## -+ctypedef int my_type -+ -+######## b.pyx ######## -+# Explicit cimport looking in sys.path -+from b cimport * -+cdef my_type foo -+ -+######## path/b.pxd ######## -+ctypedef int my_type -+ -+######## c.pyx ######## -+# Implicit cimport NOT looking in sys.path -+ -+######## path/c.pxd ######## -++++syntax error just to show that this file is not actually cimported+++ diff --git a/build/pkgs/cython/patches/includes.patch b/build/pkgs/cython/patches/includes.patch deleted file mode 100644 index 3b915b61789..00000000000 --- a/build/pkgs/cython/patches/includes.patch +++ /dev/null @@ -1,283 +0,0 @@ -Update Cython/Includes to upstream master - -diff --git a/Cython/Includes/cpython/bytes.pxd b/Cython/Includes/cpython/bytes.pxd -index 2fb3502..ea72c6a 100644 ---- a/Cython/Includes/cpython/bytes.pxd -+++ b/Cython/Includes/cpython/bytes.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - ctypedef struct va_list -diff --git a/Cython/Includes/cpython/cobject.pxd b/Cython/Includes/cpython/cobject.pxd -index 62c4706..497d8a9 100644 ---- a/Cython/Includes/cpython/cobject.pxd -+++ b/Cython/Includes/cpython/cobject.pxd -@@ -1,4 +1,3 @@ --from cpython.ref cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/datetime.pxd b/Cython/Includes/cpython/datetime.pxd -index 3c6f8a7..2e0c4bd 100644 ---- a/Cython/Includes/cpython/datetime.pxd -+++ b/Cython/Includes/cpython/datetime.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - ctypedef struct PyTypeObject: -diff --git a/Cython/Includes/cpython/dict.pxd b/Cython/Includes/cpython/dict.pxd -index 69a416a..d58faea 100644 ---- a/Cython/Includes/cpython/dict.pxd -+++ b/Cython/Includes/cpython/dict.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/exc.pxd b/Cython/Includes/cpython/exc.pxd -index 89beb21..2e4eda5 100644 ---- a/Cython/Includes/cpython/exc.pxd -+++ b/Cython/Includes/cpython/exc.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/function.pxd b/Cython/Includes/cpython/function.pxd -index e8e4f06..0002a3f 100644 ---- a/Cython/Includes/cpython/function.pxd -+++ b/Cython/Includes/cpython/function.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/getargs.pxd b/Cython/Includes/cpython/getargs.pxd -index 591aefb..be6df32 100644 ---- a/Cython/Includes/cpython/getargs.pxd -+++ b/Cython/Includes/cpython/getargs.pxd -@@ -1,4 +1,3 @@ --from cpython.ref cimport PyObject - - cdef extern from "Python.h": - ##################################################################### -diff --git a/Cython/Includes/cpython/list.pxd b/Cython/Includes/cpython/list.pxd -index 5cfd573..c6a2953 100644 ---- a/Cython/Includes/cpython/list.pxd -+++ b/Cython/Includes/cpython/list.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/method.pxd b/Cython/Includes/cpython/method.pxd -index bc09416..f51ebcc 100644 ---- a/Cython/Includes/cpython/method.pxd -+++ b/Cython/Includes/cpython/method.pxd -@@ -1,5 +1,6 @@ -+from cpython.object cimport PyObject -+ - cdef extern from "Python.h": -- ctypedef void PyObject - ############################################################################ - # 7.5.4 Method Objects - ############################################################################ -diff --git a/Cython/Includes/cpython/module.pxd b/Cython/Includes/cpython/module.pxd -index c821896..f36b989 100644 ---- a/Cython/Includes/cpython/module.pxd -+++ b/Cython/Includes/cpython/module.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - ctypedef struct _inittab -diff --git a/Cython/Includes/cpython/number.pxd b/Cython/Includes/cpython/number.pxd -index 346546e..3ad8de5 100644 ---- a/Cython/Includes/cpython/number.pxd -+++ b/Cython/Includes/cpython/number.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/object.pxd b/Cython/Includes/cpython/object.pxd -index d861965..6dc3022 100644 ---- a/Cython/Includes/cpython/object.pxd -+++ b/Cython/Includes/cpython/object.pxd -@@ -1,8 +1,61 @@ --from cpython.ref cimport PyObject, PyTypeObject - from libc.stdio cimport FILE -+cimport cpython.type - - cdef extern from "Python.h": - -+ ctypedef struct PyObject # forward declaration -+ -+ ctypedef object (*newfunc)(cpython.type.type, object, object) # (type, args, kwargs) -+ -+ ctypedef object (*unaryfunc)(object) -+ ctypedef object (*binaryfunc)(object, object) -+ ctypedef object (*ternaryfunc)(object, object, object) -+ ctypedef int (*inquiry)(object) -+ ctypedef Py_ssize_t (*lenfunc)(object) -+ ctypedef object (*ssizeargfunc)(object, Py_ssize_t) -+ ctypedef object (*ssizessizeargfunc)(object, Py_ssize_t, Py_ssize_t) -+ ctypedef int (*ssizeobjargproc)(object, Py_ssize_t, object) -+ ctypedef int (*ssizessizeobjargproc)(object, Py_ssize_t, Py_ssize_t, object) -+ ctypedef int (*objobjargproc)(object, object, object) -+ ctypedef int (*objobjproc)(object, object) -+ -+ ctypedef Py_hash_t (*hashfunc)(object) -+ ctypedef object (*reprfunc)(object) -+ -+ # The following functions use 'PyObject*' as first argument instead of 'object' to prevent -+ # accidental reference counting when calling them during a garbage collection run. -+ ctypedef void (*destructor)(PyObject*) -+ ctypedef int (*visitproc)(PyObject*, void *) -+ ctypedef int (*traverseproc)(PyObject*, visitproc, void*) -+ -+ ctypedef struct PyTypeObject: -+ const char* tp_name -+ const char* tp_doc -+ Py_ssize_t tp_basicsize -+ Py_ssize_t tp_itemsize -+ Py_ssize_t tp_dictoffset -+ unsigned long tp_flags -+ -+ newfunc tp_new -+ destructor tp_dealloc -+ traverseproc tp_traverse -+ inquiry tp_clear -+ -+ ternaryfunc tp_call -+ hashfunc tp_hash -+ reprfunc tp_str -+ reprfunc tp_repr -+ -+ ctypedef struct PyObject: -+ Py_ssize_t ob_refcnt -+ PyTypeObject *ob_type -+ -+ cdef PyTypeObject *Py_TYPE(object) -+ -+ void* PyObject_Malloc(size_t) -+ void* PyObject_Realloc(void *, size_t) -+ void PyObject_Free(void *) -+ - ##################################################################### - # 6.1 Object Protocol - ##################################################################### -diff --git a/Cython/Includes/cpython/pycapsule.pxd b/Cython/Includes/cpython/pycapsule.pxd -index f0b326b..449f369 100644 ---- a/Cython/Includes/cpython/pycapsule.pxd -+++ b/Cython/Includes/cpython/pycapsule.pxd -@@ -1,4 +1,3 @@ --from cpython.ref cimport PyObject - - # available since Python 3.1! - -diff --git a/Cython/Includes/cpython/pystate.pxd b/Cython/Includes/cpython/pystate.pxd -index d53259f..f58503f 100644 ---- a/Cython/Includes/cpython/pystate.pxd -+++ b/Cython/Includes/cpython/pystate.pxd -@@ -1,6 +1,6 @@ - # Thread and interpreter state structures and their interfaces - --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/ref.pxd b/Cython/Includes/cpython/ref.pxd -index 63ee54b..4bc9a7d 100644 ---- a/Cython/Includes/cpython/ref.pxd -+++ b/Cython/Includes/cpython/ref.pxd -@@ -1,15 +1,6 @@ --cdef extern from "Python.h": -- ctypedef struct PyTypeObject: -- Py_ssize_t tp_basicsize -- Py_ssize_t tp_itemsize -- long tp_flags -- -- ctypedef struct PyObject: -- Py_ssize_t ob_refcnt -- PyTypeObject *ob_type -- cdef PyTypeObject *Py_TYPE(object) -- -+from cpython.object cimport PyObject, PyTypeObject, Py_TYPE # legacy imports for re-export - -+cdef extern from "Python.h": - ##################################################################### - # 3. Reference Counts - ##################################################################### -diff --git a/Cython/Includes/cpython/sequence.pxd b/Cython/Includes/cpython/sequence.pxd -index 61ddca2..eb27996 100644 ---- a/Cython/Includes/cpython/sequence.pxd -+++ b/Cython/Includes/cpython/sequence.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/string.pxd b/Cython/Includes/cpython/string.pxd -index 65c6d37..8af78f3 100644 ---- a/Cython/Includes/cpython/string.pxd -+++ b/Cython/Includes/cpython/string.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - ctypedef struct va_list -diff --git a/Cython/Includes/cpython/tuple.pxd b/Cython/Includes/cpython/tuple.pxd -index 6da2890..6564b50 100644 ---- a/Cython/Includes/cpython/tuple.pxd -+++ b/Cython/Includes/cpython/tuple.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/cpython/weakref.pxd b/Cython/Includes/cpython/weakref.pxd -index 8f51052..ae710be 100644 ---- a/Cython/Includes/cpython/weakref.pxd -+++ b/Cython/Includes/cpython/weakref.pxd -@@ -1,4 +1,4 @@ --from cpython.ref cimport PyObject -+from cpython.object cimport PyObject - - cdef extern from "Python.h": - -diff --git a/Cython/Includes/numpy/__init__.pxd b/Cython/Includes/numpy/__init__.pxd -index edb1dbf..0ad89f7 100644 ---- a/Cython/Includes/numpy/__init__.pxd -+++ b/Cython/Includes/numpy/__init__.pxd -@@ -241,7 +241,6 @@ cdef extern from "numpy/arrayobject.h": - cdef int t - cdef char* f = NULL - cdef dtype descr = self.descr -- cdef list stack - cdef int offset - - cdef bint hasfields = PyDataType_HASFIELDS(descr) -@@ -788,8 +787,6 @@ cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset - # string. The new location in the format string is returned. - - cdef dtype child -- cdef int delta_offset -- cdef tuple i - cdef int endian_detector = 1 - cdef bint little_endian = ((&endian_detector)[0] != 0) - cdef tuple fields diff --git a/build/pkgs/cython/patches/includes_cmp.patch b/build/pkgs/cython/patches/includes_cmp.patch deleted file mode 100644 index 3c9c3a5da72..00000000000 --- a/build/pkgs/cython/patches/includes_cmp.patch +++ /dev/null @@ -1,32 +0,0 @@ -commit fc728e4a0b32d27567c0a2f31c7bf57084bcc6df -Author: Jeroen Demeyer -Date: Tue Apr 28 14:34:52 2015 +0200 - - PyTypeObject members tp_compare, tp_richcompare, tp_base - -diff --git a/Cython/Includes/cpython/object.pxd b/Cython/Includes/cpython/object.pxd -index 6dc3022..ebd1acd 100644 ---- a/Cython/Includes/cpython/object.pxd -+++ b/Cython/Includes/cpython/object.pxd -@@ -22,6 +22,9 @@ cdef extern from "Python.h": - ctypedef Py_hash_t (*hashfunc)(object) - ctypedef object (*reprfunc)(object) - -+ ctypedef int (*cmpfunc)(object, object) -+ ctypedef object (*richcmpfunc)(object, object, int) -+ - # The following functions use 'PyObject*' as first argument instead of 'object' to prevent - # accidental reference counting when calling them during a garbage collection run. - ctypedef void (*destructor)(PyObject*) -@@ -46,6 +49,11 @@ cdef extern from "Python.h": - reprfunc tp_str - reprfunc tp_repr - -+ cmpfunc tp_compare -+ richcmpfunc tp_richcompare -+ -+ PyTypeObject* tp_base -+ - ctypedef struct PyObject: - Py_ssize_t ob_refcnt - PyTypeObject *ob_type diff --git a/build/pkgs/cython/patches/show_progress.patch b/build/pkgs/cython/patches/show_progress.patch deleted file mode 100644 index e7df738e79c..00000000000 --- a/build/pkgs/cython/patches/show_progress.patch +++ /dev/null @@ -1,72 +0,0 @@ -commit ce5250b8d893e17b18add232ba1e9263c73b5909 -Author: Jeroen Demeyer -Date: Sat Apr 25 22:24:40 2015 +0200 - - Progress indicator for cythonize() - -diff --git a/Cython/Build/Dependencies.py b/Cython/Build/Dependencies.py -index 4514493..853c591 100644 ---- a/Cython/Build/Dependencies.py -+++ b/Cython/Build/Dependencies.py -@@ -832,7 +832,15 @@ def cythonize(module_list, exclude=[], nthreads=0, aliases=None, quiet=False, fo - if not os.path.exists(options.cache): - os.makedirs(options.cache) - to_compile.sort() -- if len(to_compile) <= 1: -+ # Drop "priority" component of "to_compile" entries and add a -+ # simple-minded progress indicator. -+ N = len(to_compile) -+ progress_fmt = "[{:%i}/{}] " % len(str(N)) -+ for i in range(N): -+ progress = progress_fmt.format(i+1, N) -+ to_compile[i] = to_compile[i][1:] + (progress,) -+ -+ if N <= 1: - nthreads = 0 - if nthreads: - # Requires multiprocessing (or Python >= 2.6) -@@ -862,7 +870,7 @@ def cythonize(module_list, exclude=[], nthreads=0, aliases=None, quiet=False, fo - pool.join() - if not nthreads: - for args in to_compile: -- cythonize_one(*args[1:]) -+ cythonize_one(*args) - - if exclude_failures: - failed_modules = set() -@@ -927,7 +935,7 @@ else: - - # TODO: Share context? Issue: pyx processing leaks into pxd module - @record_results --def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_failure=True, embedded_metadata=None): -+def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_failure=True, embedded_metadata=None, progress=""): - from ..Compiler.Main import compile, default_options - from ..Compiler.Errors import CompileError, PyrexError - -@@ -944,7 +952,7 @@ def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_f - options.cache, "%s-%s%s" % (os.path.basename(c_file), fingerprint, gzip_ext)) - if os.path.exists(fingerprint_file): - if not quiet: -- print("Found compiled %s in cache" % pyx_file) -+ print("%sFound compiled %s in cache" % (progress, pyx_file)) - os.utime(fingerprint_file, None) - g = gzip_open(fingerprint_file, 'rb') - try: -@@ -957,7 +965,7 @@ def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_f - g.close() - return - if not quiet: -- print("Cythonizing %s" % pyx_file) -+ print("%sCythonizing %s" % (progress, pyx_file)) - if options is None: - options = CompilationOptions(default_options) - options.output_file = c_file -@@ -1000,7 +1008,7 @@ def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_f - def cythonize_one_helper(m): - import traceback - try: -- return cythonize_one(*m[1:]) -+ return cythonize_one(*m) - except Exception: - traceback.print_exc() - raise diff --git a/build/pkgs/cython/patches/sig_includes.patch b/build/pkgs/cython/patches/sig_includes.patch new file mode 100644 index 00000000000..8e5056e1947 --- /dev/null +++ b/build/pkgs/cython/patches/sig_includes.patch @@ -0,0 +1,66 @@ +Additions to includes + +diff --git a/Cython/Includes/libc/setjmp.pxd b/Cython/Includes/libc/setjmp.pxd +index 74d1c59..f2283fe 100644 +--- a/Cython/Includes/libc/setjmp.pxd ++++ b/Cython/Includes/libc/setjmp.pxd +@@ -3,3 +3,8 @@ cdef extern from "setjmp.h" nogil: + pass + int setjmp(jmp_buf state) + void longjmp(jmp_buf state, int value) ++ ++ ctypedef struct sigjmp_buf: ++ pass ++ int sigsetjmp(sigjmp_buf state, int savesigs) ++ void siglongjmp(sigjmp_buf state, int value) +diff --git a/Cython/Includes/posix/select.pxd b/Cython/Includes/posix/select.pxd +new file mode 100644 +index 0000000..37bc9d3 +--- /dev/null ++++ b/Cython/Includes/posix/select.pxd +@@ -0,0 +1,19 @@ ++from .types cimport sigset_t ++from .time cimport timeval, timespec ++ ++cdef extern from "sys/select.h" nogil: ++ ctypedef struct fd_set: ++ pass ++ ++ int FD_SETSIZE ++ void FD_SET(int, fd_set*) ++ void FD_CLR(int, fd_set*) ++ bint FD_ISSET(int, fd_set*) ++ void FD_ZERO(fd_set*) ++ ++ int select(int nfds, fd_set *readfds, fd_set *writefds, ++ fd_set *exceptfds, const timeval *timeout) ++ ++ int pselect(int nfds, fd_set *readfds, fd_set *writefds, ++ fd_set *exceptfds, const timespec *timeout, ++ const sigset_t *sigmask) +diff --git a/Cython/Includes/posix/signal.pxd b/Cython/Includes/posix/signal.pxd +index 9168b2f..2d6cce8 100644 +--- a/Cython/Includes/posix/signal.pxd ++++ b/Cython/Includes/posix/signal.pxd +@@ -12,7 +12,7 @@ cdef extern from "signal.h" nogil: + int sigev_notify + int sigev_signo + sigval sigev_value +- void *sigev_notify_function(sigval) ++ void sigev_notify_function(sigval) + + ctypedef struct siginfo_t: + int si_signo +@@ -26,10 +26,10 @@ cdef extern from "signal.h" nogil: + sigval si_value + + cdef struct sigaction_t "sigaction": +- void *sa_handler(int) ++ void sa_handler(int) ++ void sa_sigaction(int, siginfo_t *, void *) + sigset_t sa_mask + int sa_flags +- void sa_sigaction(int, siginfo_t *, void *) + + enum: SA_NOCLDSTOP + enum: SIG_BLOCK diff --git a/build/pkgs/cython/patches/transitive_dependencies.patch b/build/pkgs/cython/patches/transitive_dependencies.patch deleted file mode 100644 index 09968e3f448..00000000000 --- a/build/pkgs/cython/patches/transitive_dependencies.patch +++ /dev/null @@ -1,78 +0,0 @@ -commit 2285a087a3976ef301a8ea740fb5568f91930c8e -Author: Jeroen Demeyer -Date: Tue Apr 21 14:40:12 2015 +0200 - - When merging a transitive_list, copy the list - -diff --git a/Cython/Build/Dependencies.py b/Cython/Build/Dependencies.py -index 3fe6885..ae47c62 100644 ---- a/Cython/Build/Dependencies.py -+++ b/Cython/Build/Dependencies.py -@@ -216,12 +216,13 @@ class DistutilsInfo(object): - self.values[key] = value - elif type is transitive_list: - if key in self.values: -- all = self.values[key] -+ # Change a *copy* of the list (Trac #845) -+ all = self.values[key][:] - for v in value: - if v not in all: - all.append(v) -- else: -- self.values[key] = value -+ value = all -+ self.values[key] = value - return self - - def subs(self, aliases): -@@ -250,9 +251,8 @@ class DistutilsInfo(object): - for key, value in self.values.items(): - type = distutils_settings[key] - if type in [list, transitive_list]: -- getattr(extension, key).extend(value) -- else: -- setattr(extension, key, value) -+ value = getattr(extension, key) + list(value) -+ setattr(extension, key, value) - - @cython.locals(start=long, q=long, single_q=long, double_q=long, hash_mark=long, - end=long, k=long, counter=long, quote_len=long) -diff --git a/tests/compile/distutils_libraries_T845.srctree b/tests/compile/distutils_libraries_T845.srctree -new file mode 100644 -index 0000000..69c8549 ---- /dev/null -+++ b/tests/compile/distutils_libraries_T845.srctree -@@ -0,0 +1,33 @@ -+PYTHON setup.py build_ext --inplace -+ -+######## setup.py ######## -+ -+from Cython.Build import cythonize -+from Cython.Distutils.extension import Extension -+ -+ext_modules = [ -+ Extension("a", ["a.pyx"]), -+ Extension("x", ["x.pyx"]), -+ Extension("y", ["y.pyx"]), -+] -+ -+ext_modules = cythonize(ext_modules) -+ -+assert ext_modules[1].libraries == ["lib_x"] -+assert ext_modules[2].libraries == ["lib_y"] -+ -+######## libx.pxd ######## -+# distutils: libraries = lib_x -+ -+######## liby.pxd ######## -+# distutils: libraries = lib_y -+ -+######## a.pyx ######## -+cimport libx -+cimport liby -+ -+######## x.pyx ######## -+cimport libx -+ -+######## y.pyx ######## -+cimport liby diff --git a/build/pkgs/cython/spkg-check b/build/pkgs/cython/spkg-check new file mode 100755 index 00000000000..872ecb7a1a4 --- /dev/null +++ b/build/pkgs/cython/spkg-check @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && $MAKE test diff --git a/build/pkgs/d3js/SPKG.txt b/build/pkgs/d3js/SPKG.txt index 2f536b8e3b3..d7a80a3bc91 100644 --- a/build/pkgs/d3js/SPKG.txt +++ b/build/pkgs/d3js/SPKG.txt @@ -9,10 +9,6 @@ The file d3.min.js will be placed into the ${SAGE_SHARE}/d3js/ directory. BSD 3-Clause License -== SPKG Maintainers == - -* Thierry Monteil - == Upstream Contact == Author: Mike Bostock (http://bost.ocks.org/mike/) diff --git a/build/pkgs/d3js/dependencies b/build/pkgs/d3js/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/d3js/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_cremona_ellcurve/SPKG.txt b/build/pkgs/database_cremona_ellcurve/SPKG.txt index 264265594cf..19232641f17 100644 --- a/build/pkgs/database_cremona_ellcurve/SPKG.txt +++ b/build/pkgs/database_cremona_ellcurve/SPKG.txt @@ -10,10 +10,6 @@ This is an optional package, not included by default. == License == Public Domain -== SPKG Maintainers == - * R. Andrew Ohana - * John Cremona - == Dependencies == None diff --git a/build/pkgs/database_cremona_ellcurve/dependencies b/build/pkgs/database_cremona_ellcurve/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_cremona_ellcurve/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_gap/SPKG.txt b/build/pkgs/database_gap/SPKG.txt index 39e1aa5c31b..e06e4b5054b 100644 --- a/build/pkgs/database_gap/SPKG.txt +++ b/build/pkgs/database_gap/SPKG.txt @@ -4,13 +4,6 @@ The databases of GAP: finite groups and tables of marks -== SPKG Maintainers == - - * William Stein - * Robert Miller - * David Joyner - * Dmitrii Pasechnik - == Upstream Contact == Dmitrii Pasechnik, dimpase@gmail.com diff --git a/build/pkgs/database_jones_numfield/SPKG.txt b/build/pkgs/database_jones_numfield/SPKG.txt new file mode 100644 index 00000000000..93d41c86cd3 --- /dev/null +++ b/build/pkgs/database_jones_numfield/SPKG.txt @@ -0,0 +1,21 @@ += database_jones_numfield = + +== Description == + +This is a table of number fields with bounded ramification and degree ≤6. + +== License == + +GPLv2+ + +== Upstream Contact == + +sage-devel@googlegroups.com + +== Dependencies == + +None + +== Special Update/Build Instructions == + +Created by taking the original old-style spkg and removing crud from it. diff --git a/build/pkgs/database_jones_numfield/checksums.ini b/build/pkgs/database_jones_numfield/checksums.ini new file mode 100644 index 00000000000..69db10a25a8 --- /dev/null +++ b/build/pkgs/database_jones_numfield/checksums.ini @@ -0,0 +1,4 @@ +tarball=database_jones_numfield-VERSION.tar.gz +sha1=f7b1c4c330ff6be47ade4ec7fc14df9cebe8e2a8 +md5=a8da4f207235a1de980a23a06e1e6d76 +cksum=1843797635 diff --git a/build/pkgs/database_jones_numfield/dependencies b/build/pkgs/database_jones_numfield/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_jones_numfield/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_jones_numfield/package-version.txt b/build/pkgs/database_jones_numfield/package-version.txt new file mode 100644 index 00000000000..b8626c4cff2 --- /dev/null +++ b/build/pkgs/database_jones_numfield/package-version.txt @@ -0,0 +1 @@ +4 diff --git a/build/pkgs/database_jones_numfield/spkg-install b/build/pkgs/database_jones_numfield/spkg-install new file mode 100755 index 00000000000..06dbabd8400 --- /dev/null +++ b/build/pkgs/database_jones_numfield/spkg-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_SHARE" ]; then + echo >&2 "SAGE_SHARE undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +TARGET="${SAGE_SHARE}/jones" +if [ ! -d "${TARGET}" ]; then + mkdir "${TARGET}" +fi + +cp 'src/jones.sobj' "${TARGET}/" diff --git a/build/pkgs/database_jones_numfield/type b/build/pkgs/database_jones_numfield/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/database_jones_numfield/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/database_pari/SPKG.txt b/build/pkgs/database_pari/SPKG.txt index fe264e9ca9c..48f0bf8a214 100644 --- a/build/pkgs/database_pari/SPKG.txt +++ b/build/pkgs/database_pari/SPKG.txt @@ -10,10 +10,6 @@ See http://pari.math.u-bordeaux.fr/packages.html GNU General Public License (GPL version 2 or any later version). -== SPKG Maintainers == - -* Jeroen Demeyer - == Upstream Contact == http://pari.math.u-bordeaux.fr/ diff --git a/build/pkgs/database_stein_watkins/SPKG.txt b/build/pkgs/database_stein_watkins/SPKG.txt index 549da475211..ee6ecb4349f 100644 --- a/build/pkgs/database_stein_watkins/SPKG.txt +++ b/build/pkgs/database_stein_watkins/SPKG.txt @@ -10,10 +10,6 @@ This is an optional (huge) package, not included by default. == License == Public Domain -== SPKG Maintainers == - * William Stein - * John Cremona - == Dependencies == None diff --git a/build/pkgs/database_stein_watkins/dependencies b/build/pkgs/database_stein_watkins/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_stein_watkins_mini/SPKG.txt b/build/pkgs/database_stein_watkins_mini/SPKG.txt index 20dadc78328..41c575c9a9e 100644 --- a/build/pkgs/database_stein_watkins_mini/SPKG.txt +++ b/build/pkgs/database_stein_watkins_mini/SPKG.txt @@ -10,10 +10,6 @@ This is an optional package, not included by default. == License == Public Domain -== SPKG Maintainers == - * William Stein - * John Cremona - == Dependencies == None diff --git a/build/pkgs/database_stein_watkins_mini/dependencies b/build/pkgs/database_stein_watkins_mini/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins_mini/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_symbolic_data/SPKG.txt b/build/pkgs/database_symbolic_data/SPKG.txt index c164235db7d..e09e6c47d99 100644 --- a/build/pkgs/database_symbolic_data/SPKG.txt +++ b/build/pkgs/database_symbolic_data/SPKG.txt @@ -22,10 +22,6 @@ Tools and data are designed to be used both GNU General Public License -== SPKG Maintainers == - -* Martin Albrecht - == Upstream Contact == * Andreas Nareike diff --git a/build/pkgs/database_symbolic_data/dependencies b/build/pkgs/database_symbolic_data/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_symbolic_data/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/dateutil/SPKG.txt b/build/pkgs/dateutil/SPKG.txt index 361ce6e6abb..c8b3d5d8779 100644 --- a/build/pkgs/dateutil/SPKG.txt +++ b/build/pkgs/dateutil/SPKG.txt @@ -9,10 +9,6 @@ datetime module. Simplified BSD License -== SPKG Maintainers == - -John H. Palmieri - == Upstream Contact == Author: Gustavo Niemeyer diff --git a/build/pkgs/decorator/SPKG.txt b/build/pkgs/decorator/SPKG.txt new file mode 100644 index 00000000000..52aa0271d0a --- /dev/null +++ b/build/pkgs/decorator/SPKG.txt @@ -0,0 +1,5 @@ += decorator = + +== Description == + +Better living through Python with decorators diff --git a/build/pkgs/decorator/checksums.ini b/build/pkgs/decorator/checksums.ini new file mode 100644 index 00000000000..02e755cf1bd --- /dev/null +++ b/build/pkgs/decorator/checksums.ini @@ -0,0 +1,4 @@ +tarball=decorator-VERSION.tar.gz +sha1=9ca491f88a5b5456d0f0eb8d26cf6ebd780e2934 +md5=033c9563af492c4ce2680ee6ca481fa7 +cksum=3724271103 diff --git a/build/pkgs/decorator/dependencies b/build/pkgs/decorator/dependencies new file mode 100644 index 00000000000..05fd9f23776 --- /dev/null +++ b/build/pkgs/decorator/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(SETUPTOOLS) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/decorator/package-version.txt b/build/pkgs/decorator/package-version.txt new file mode 100644 index 00000000000..4d54daddb61 --- /dev/null +++ b/build/pkgs/decorator/package-version.txt @@ -0,0 +1 @@ +4.0.2 diff --git a/build/pkgs/decorator/spkg-install b/build/pkgs/decorator/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/decorator/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/decorator/type b/build/pkgs/decorator/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/decorator/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/docutils/SPKG.txt b/build/pkgs/docutils/SPKG.txt index 58e6b5c3876..9b6eaa57634 100644 --- a/build/pkgs/docutils/SPKG.txt +++ b/build/pkgs/docutils/SPKG.txt @@ -11,11 +11,6 @@ plaintext markup syntax. Modified BSD -== SPKG Maintainers == - -Mike Hansen -Pablo De Napoli - == Upstream Contact == Author: David Goodger diff --git a/build/pkgs/dot2tex/SPKG.txt b/build/pkgs/dot2tex/SPKG.txt index bd99f848347..6aa00f80e36 100644 --- a/build/pkgs/dot2tex/SPKG.txt +++ b/build/pkgs/dot2tex/SPKG.txt @@ -13,9 +13,6 @@ or http://dot2tex.googlecode.com/ == License == * MIT -== SPKG Maintainers == - * Nicolas M. Thiery - == Upstream Contact == * Kjell Magne Fauske, km@fauskes.net diff --git a/build/pkgs/ecl/SPKG.txt b/build/pkgs/ecl/SPKG.txt index ab2ffe82175..ee1f1253843 100644 --- a/build/pkgs/ecl/SPKG.txt +++ b/build/pkgs/ecl/SPKG.txt @@ -34,6 +34,8 @@ Website: http://ecls.sourceforge.net/ * boehm_gc == Special Update/Build Instructions == + * As autotools need to be run after most of the patches are applied, + we do all the patching in spkg-source. * Deleting the following directories saves space: without doing this, the tarball can grow from under 3 megabytes to more than 7 megabytes. Deleting these files is done automatically by the diff --git a/build/pkgs/ecl/checksums.ini b/build/pkgs/ecl/checksums.ini index f0e6ca9cad0..7b0e8c978e8 100644 --- a/build/pkgs/ecl/checksums.ini +++ b/build/pkgs/ecl/checksums.ini @@ -1,4 +1,4 @@ tarball=ecl-VERSION.tar.bz2 -sha1=f9ee0699433be837e90d04f6f038acf9209cf5f6 -md5=db91e48a9f140bd0c571c9f82bc74eb2 -cksum=2161474303 +sha1=d5b9f2f19847697f3cbe54e69daf609d1ea1b9ca +md5=ba1d8acd05b2921c556a488191ff4b6b +cksum=1247067343 diff --git a/build/pkgs/ecl/package-version.txt b/build/pkgs/ecl/package-version.txt index 34603f369af..74a10628100 100644 --- a/build/pkgs/ecl/package-version.txt +++ b/build/pkgs/ecl/package-version.txt @@ -1 +1 @@ -13.5.1.p0 +15.3.7p0 diff --git a/build/pkgs/ecl/patches/gmp.patch b/build/pkgs/ecl/patches/gmp.patch index 9ac008af0ab..50a5a7966ba 100644 --- a/build/pkgs/ecl/patches/gmp.patch +++ b/build/pkgs/ecl/patches/gmp.patch @@ -1,33 +1,13 @@ -diff -druN src.orig/src/configure.in src/src/configure.in ---- src.orig/src/configure.in 2012-12-07 22:01:02.000000000 +0100 -+++ src/src/configure.in 2014-04-09 15:17:50.282780900 +0200 -@@ -11,7 +11,7 @@ - AC_INIT([ecl],[13.5.1],[]) +diff --git b/src/configure.ac a/src/configure.ac +index bc8d84b..e076e09 100644 +--- b/src/configure.ac ++++ a/src/configure.ac +@@ -11,7 +11,7 @@ dnl + AC_INIT([ecl],[15.3.7],[]) AC_REVISION([$Revision$]) AC_CONFIG_SRCDIR([bare.lsp.in]) -AC_CONFIG_AUX_DIR([gmp]) +AC_CONFIG_AUX_DIR([.]) - AC_PREREQ(2.59) + AC_PREREQ(2.69) dnl ----------------------------------------------------------------------- -diff -druN src.orig/src/configure src/src/configure ---- src.orig/src/configure 2012-12-07 22:01:02.000000000 +0100 -+++ src/src/configure 2014-04-09 15:20:21.360905900 +0200 -@@ -2546,7 +2546,7 @@ - - - ac_aux_dir= --for ac_dir in gmp "$srcdir"/gmp; do -+for ac_dir in . "$srcdir"/.; do - if test -f "$ac_dir/install-sh"; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/install-sh -c" -@@ -2562,7 +2562,7 @@ - fi - done - if test -z "$ac_aux_dir"; then -- as_fn_error $? "cannot find install-sh, install.sh, or shtool in gmp \"$srcdir\"/gmp" "$LINENO" 5 -+ as_fn_error $? "cannot find install-sh, install.sh, or shtool in . \"$srcdir\"/." "$LINENO" 5 - fi - - # These three variables are undocumented and unsupported, diff --git a/build/pkgs/ecl/patches/implib.patch b/build/pkgs/ecl/patches/implib.patch index 818f53737ba..ab11e189ab7 100644 --- a/build/pkgs/ecl/patches/implib.patch +++ b/build/pkgs/ecl/patches/implib.patch @@ -1,71 +1,8 @@ -diff -durN src.orig/src/configure.in src/src/configure.in ---- src.orig/src/configure.in 2012-12-17 11:08:10.000000000 +0100 -+++ src/src/configure.in 2013-01-18 11:34:30.012132746 +0100 -@@ -582,6 +582,19 @@ - AC_SUBST(SONAME) - AC_SUBST(SONAME_LDFLAGS) - -+dnl ---------------------------------------------------------------------- -+dnl IMPLIB_NAME is only active when IMPLIB_NAME is non nil -+dnl -+AC_MSG_CHECKING(for import name) -+if test "${enable_soname}" != yes; then -+ IMPLIB_NAME='' -+ AC_MSG_RESULT([none]) -+else -+ AC_MSG_RESULT([${IMPLIB_NAME}]) -+fi -+AC_SUBST(IMPLIB_NAME) -+AC_SUBST(IMPLIB_LDFLAGS) -+ - dnl Related to that, the package version number - ECL_VERSION_NUMBER=$(($PACKAGE_MAJOR * 10000 + $PACKAGE_MINOR * 100 + $PACKAGE_LEAST)) - AC_SUBST(ECL_VERSION_NUMBER) -diff -durN src.orig/src/Makefile.in src/src/Makefile.in ---- src.orig/src/Makefile.in 2012-12-17 11:08:06.000000000 +0100 -+++ src/src/Makefile.in 2013-01-18 11:34:30.012132746 +0100 -@@ -174,10 +174,14 @@ - if test -s $$i ; then \ - if echo $$i | grep dll; then \ - $(INSTALL_LIBRARY) $$i $(DESTDIR)$(bindir); \ -- fi; \ -- $(INSTALL_LIBRARY) $$i $(DESTDIR)$(libdir); \ -+ else \ -+ $(INSTALL_LIBRARY) $$i $(DESTDIR)$(libdir); \ -+ fi \ - fi \ - done -+ if [ "x@IMPLIB_NAME@" != "x" -a -f "@IMPLIB_NAME@" ]; then \ -+ $(INSTALL_LIBRARY) @IMPLIB_NAME@ $(DESTDIR)$(libdir); \ -+ fi - if [ "x@SONAME@" != "x" -a -f "@SONAME@" ]; then \ - ( cd $(DESTDIR)$(libdir) && $(RM) -f @SONAME3@ @SONAME2@ @SONAME1@ && \ - mv @SONAME@ @SONAME3@ && \ -diff -durN src.orig/src/compile.lsp.in src/src/compile.lsp.in ---- src.orig/src/compile.lsp.in 2012-12-17 11:08:05.000000000 +0100 -+++ src/src/compile.lsp.in 2013-01-18 11:34:30.012132746 +0100 -@@ -58,7 +58,7 @@ - ;;; - ;;; * Add include path to not yet installed headers, and remove include flag - ;;; (-I) to installed directory, and Notice that we must explicitely mention --;;; libecl.so/ecl.dll instead of using -lecl. This is to avoid interference -+;;; libecl.so/cygecl.dll instead of using -lecl. This is to avoid interference - ;;; with an already installed copy of ECL. - ;;; - (setq c::*cc-flags* -@@ -140,7 +140,7 @@ - ;;; - ;;; We do not need the -rpath flag for the library, nor -lecl. - ;;; --(let* ((c::*ld-shared-flags* #-msvc "@SHARED_LDFLAGS@ @LDFLAGS@ @SONAME_LDFLAGS@ @CORE_LIBS@ @FASL_LIBS@ @LIBS@" -+(let* ((c::*ld-shared-flags* #-msvc " @IMPLIB_LDFLAGS@ @SHARED_LDFLAGS@ @LDFLAGS@ @SONAME_LDFLAGS@ @CORE_LIBS@ @FASL_LIBS@ @LIBS@" - #+msvc "@SHARED_LDFLAGS@ @LDFLAGS@ @STATICLIBS@ @CLIBS@") - (c::*cc-flags* (concatenate 'string "-DECL_API -I@true_builddir@/c " c::*cc-flags*)) - (extra-args nil)) -diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 ---- src.orig/src/aclocal.m4 2012-12-17 11:08:05.000000000 +0100 -+++ src/src/aclocal.m4 2013-01-18 11:34:30.012132746 +0100 -@@ -232,6 +232,8 @@ +diff --git a/src/aclocal.m4 b/src/aclocal.m4 +index 63d8997..79ff24d 100644 +--- a/src/aclocal.m4 ++++ b/src/aclocal.m4 +@@ -233,6 +233,8 @@ AC_SUBST(LIBPREFIX)dnl Name components of a statically linked library AC_SUBST(LIBEXT) AC_SUBST(SHAREDEXT)dnl Name components of a dynamically linked library AC_SUBST(SHAREDPREFIX) @@ -74,7 +11,7 @@ diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 AC_SUBST(OBJEXT)dnl These are set by autoconf AC_SUBST(EXEEXT) AC_SUBST(INSTALL_TARGET)dnl Which type of installation: flat directory or unix like. -@@ -241,6 +243,8 @@ +@@ -242,6 +244,8 @@ ECL_GC_DIR=gc-unstable ECL_LDRPATH='' SHAREDEXT='so' SHAREDPREFIX='lib' @@ -83,7 +20,7 @@ diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 LIBPREFIX='lib' LIBEXT='a' PICFLAG='-fPIC' -@@ -252,6 +256,8 @@ +@@ -253,6 +257,8 @@ THREAD_OBJ="$THREAD_OBJ threads/process threads/queue threads/mutex threads/cond clibs='' SONAME='' SONAME_LDFLAGS='' @@ -92,7 +29,7 @@ diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 case "${host_os}" in # libdir may have a dollar expression inside linux*) -@@ -354,10 +360,14 @@ +@@ -355,10 +361,14 @@ case "${host_os}" in shared='yes' THREAD_CFLAGS='-D_THREAD_SAFE' THREAD_LIBS='-lpthread' @@ -108,9 +45,9 @@ diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 + IMPLIB_NAME="${IMPLIB_PREFIX}ecl.${IMPLIB_EXT}" + IMPLIB_LDFLAGS="-Wl,--out-implib,${IMPLIB_NAME}" PICFLAG='' - ;; - mingw*) -@@ -367,10 +377,14 @@ + if test "x$host_cpu" = "xx86_64" ; then + # Our GMP library is too old and does not support +@@ -373,10 +383,14 @@ case "${host_os}" in enable_threads='yes' THREAD_CFLAGS='-D_THREAD_SAFE' THREAD_GC_FLAGS='--enable-threads=win32' @@ -127,102 +64,71 @@ diff -durN src.orig/src/aclocal.m4 src/src/aclocal.m4 PICFLAG='' INSTALL_TARGET='flatinstall' TCPLIBS='-lws2_32' -diff -durN src.orig/src/configure src/src/configure ---- src.orig/src/configure 2012-12-17 11:08:11.000000000 +0100 -+++ src/src/configure 2013-01-18 11:35:15.231702758 +0100 -@@ -643,6 +643,8 @@ - CL_FIXNUM_TYPE - XMKMF - ECL_VERSION_NUMBER -+IMPLIB_LDFLAGS -+IMPLIB_NAME - SONAME_LDFLAGS - SONAME - SONAME1 -@@ -659,6 +661,8 @@ - ECL_GC_DIR - thehost - INSTALL_TARGET -+IMPLIB_PREFIX -+IMPLIB_EXT - SHAREDPREFIX - SHAREDEXT - LIBEXT -@@ -4855,10 +4859,13 @@ - - - -+ - ECL_GC_DIR=gc-unstable - ECL_LDRPATH='' - SHAREDEXT='so' - SHAREDPREFIX='lib' -+IMPLIB_EXT='' -+IMPLIB_PREFIX='' - LIBPREFIX='lib' - LIBEXT='a' - PICFLAG='-fPIC' -@@ -4870,6 +4877,8 @@ - clibs='' - SONAME='' - SONAME_LDFLAGS='' -+IMPLIB_NAME='' -+IMPLIB_LDFLAGS='' - case "${host_os}" in - # libdir may have a dollar expression inside - linux*) -@@ -4972,10 +4981,14 @@ - shared='yes' - THREAD_CFLAGS='-D_THREAD_SAFE' - THREAD_LIBS='-lpthread' -- SHARED_LDFLAGS="-shared ${LDFLAGS}" -- BUNDLE_LDFLAGS="-shared ${LDFLAGS}" -- SHAREDPREFIX='' -+ SHARED_LDFLAGS="-shared -Wl,--enable-auto-image-base ${LDFLAGS}" -+ BUNDLE_LDFLAGS="-shared -Wl,--enable-auto-image-base ${LDFLAGS}" -+ SHAREDPREFIX='cyg' - SHAREDEXT='dll' -+ IMPLIB_PREFIX='lib' -+ IMPLIB_EXT='dll.a' -+ IMPLIB_NAME="${IMPLIB_PREFIX}ecl.${IMPLIB_EXT}" -+ IMPLIB_LDFLAGS="-Wl,--out-implib,${IMPLIB_NAME}" - PICFLAG='' - ;; - mingw*) -@@ -4985,10 +4998,14 @@ - enable_threads='yes' - THREAD_CFLAGS='-D_THREAD_SAFE' - THREAD_GC_FLAGS='--enable-threads=win32' -- SHARED_LDFLAGS='' -- BUNDLE_LDFLAGS='' -+ SHARED_LDFLAGS="-shared -Wl,--enable-auto-image-base ${LDFLAGS}" -+ BUNDLE_LDFLAGS="-shared -Wl,--enable-auto-image-base ${LDFLAGS}" - SHAREDPREFIX='' - SHAREDEXT='dll' -+ IMPLIB_PREFIX='lib' -+ IMPLIB_EXT='dll.a' -+ IMPLIB_NAME="${IMPLIB_PREFIX}ecl.${IMPLIB_EXT}" -+ IMPLIB_LDFLAGS="-Wl,--out-implib,${IMPLIB_NAME}" - PICFLAG='' - INSTALL_TARGET='flatinstall' - TCPLIBS='-lws2_32' -@@ -6131,6 +6148,19 @@ - - +diff --git a/src/compile.lsp.in b/src/compile.lsp.in +index 773fe99..697c115 100755 +--- a/src/compile.lsp.in ++++ b/src/compile.lsp.in +@@ -58,7 +58,7 @@ + ;;; + ;;; * Add include path to not yet installed headers, and remove include flag + ;;; (-I) to installed directory, and Notice that we must explicitely mention +-;;; libecl.so/ecl.dll instead of using -lecl. This is to avoid interference ++;;; libecl.so/cygecl.dll instead of using -lecl. This is to avoid interference + ;;; with an already installed copy of ECL. + ;;; + (setq c::*cc-flags* +@@ -140,7 +140,7 @@ + ;;; + ;;; We do not need the -rpath flag for the library, nor -lecl. + ;;; +-(let* ((c::*ld-shared-flags* #-msvc "@SHARED_LDFLAGS@ @LDFLAGS@ @SONAME_LDFLAGS@ @CORE_LIBS@ @FASL_LIBS@ @LIBS@" ++(let* ((c::*ld-shared-flags* #-msvc " @IMPLIB_LDFLAGS@ @SHARED_LDFLAGS@ @LDFLAGS@ @SONAME_LDFLAGS@ @CORE_LIBS@ @FASL_LIBS@ @LIBS@" + #+msvc "@SHARED_LDFLAGS@ @LDFLAGS@ @STATICLIBS@ @CLIBS@") + (c::*cc-flags* (concatenate 'string "-DECL_API -I@true_builddir@/c " c::*cc-flags*)) + (extra-args nil)) +diff --git a/src/configure.ac b/src/configure.ac +index e076e09..15d307a 100644 +--- a/src/configure.ac ++++ b/src/configure.ac +@@ -588,6 +588,20 @@ AC_SUBST(SONAME1) + AC_SUBST(SONAME) + AC_SUBST(SONAME_LDFLAGS) -+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for import name" >&5 -+$as_echo_n "checking for import name... " >&6; } ++dnl ---------------------------------------------------------------------- ++dnl IMPLIB_NAME is only active when IMPLIB_NAME is non nil ++dnl ++AC_MSG_CHECKING(for import name) +if test "${enable_soname}" != yes; then + IMPLIB_NAME='' -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 -+$as_echo "none" >&6; } ++ AC_MSG_RESULT([none]) +else -+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${IMPLIB_NAME}" >&5 -+$as_echo "${IMPLIB_NAME}" >&6; } ++ AC_MSG_RESULT([${IMPLIB_NAME}]) +fi ++AC_SUBST(IMPLIB_NAME) ++AC_SUBST(IMPLIB_LDFLAGS) + + -+ + dnl Related to that, the package version number ECL_VERSION_NUMBER=$(($PACKAGE_MAJOR * 10000 + $PACKAGE_MINOR * 100 + $PACKAGE_LEAST)) - - + AC_SUBST(ECL_VERSION_NUMBER) +diff --git a/src/Makefile.in b/src/Makefile.in +index 12e9f05..74bc216 100644 +--- a/src/Makefile.in ++++ b/src/Makefile.in +@@ -174,10 +174,14 @@ install: + if test -s $$i ; then \ + if echo $$i | grep dll; then \ + $(INSTALL_LIBRARY) $$i $(DESTDIR)$(bindir); \ +- fi; \ +- $(INSTALL_LIBRARY) $$i $(DESTDIR)$(libdir); \ ++ else \ ++ $(INSTALL_LIBRARY) $$i $(DESTDIR)$(libdir); \ ++ fi \ + fi \ + done ++ if [ "x@IMPLIB_NAME@" != "x" -a -f "@IMPLIB_NAME@" ]; then \ ++ $(INSTALL_LIBRARY) @IMPLIB_NAME@ $(DESTDIR)$(libdir); \ ++ fi + if [ "x@SONAME@" != "x" -a -f "@SONAME@" ]; then \ + ( cd $(DESTDIR)$(libdir) && $(RM) -f @SONAME3@ @SONAME2@ @SONAME1@ && \ + mv @SONAME@ @SONAME3@ && \ diff --git a/build/pkgs/ecl/patches/write_error.patch b/build/pkgs/ecl/patches/write_error.patch index 8ddcf68e072..658b864a606 100644 --- a/build/pkgs/ecl/patches/write_error.patch +++ b/build/pkgs/ecl/patches/write_error.patch @@ -1,13 +1,15 @@ -diff -ru src/src/c/file.d b/src/c/file.d ---- src/src/c/file.d 2012-12-07 22:01:02.000000000 +0100 -+++ b/src/c/file.d 2013-04-10 09:07:24.537513659 +0200 -@@ -3335,7 +3335,8 @@ +diff --git b/src/c/file.d a/src/c/file.d +index de7ba7b..c1f8c1e 100755 +--- b/src/c/file.d ++++ a/src/c/file.d +@@ -3341,7 +3341,9 @@ output_stream_write_byte8(cl_object strm, unsigned char *c, cl_index n) ecl_disable_interrupts(); do { out = fwrite(c, sizeof(char), n, IO_STREAM_FILE(strm)); - } while (out < n && restartable_io_error(strm, "fwrite")); -+ /* Ignore write errors to stderr to avoid an infinite loop */ -+ } while (out < n && (IO_STREAM_FILE(strm) != stderr) && restartable_io_error(strm, "fwrite")); ++ /* Ignore write errors to stderr to avoid an infinite loop */ ++ } while (out < n && (IO_STREAM_FILE(strm) != stderr) && restartable_io_error(strm, "fwrite")); ++ ecl_enable_interrupts(); return out; } diff --git a/build/pkgs/ecl/spkg-install b/build/pkgs/ecl/spkg-install index b5056c3e456..f478c45c365 100755 --- a/build/pkgs/ecl/spkg-install +++ b/build/pkgs/ecl/spkg-install @@ -7,16 +7,6 @@ if [ -z "$SAGE_LOCAL" ] ; then fi cd src -# For some of the patches, Cygwin also has upstream fixes that are -# closely related, keep track. See Trac 11119, for example. -for patch in ../patches/*.patch; do - [ -f "$patch" ] || continue - patch -p1 <"$patch" - if [ $? -ne 0 ]; then - echo >&2 "Error applying '$patch'" - exit 1 - fi -done if [ -z "$CFLAG64" ] ; then CFLAG64=-m64 @@ -60,6 +50,8 @@ echo "Using CPPFLAGS=$CPPFLAGS" echo "Using LDFLAGS=$LDFLAGS" echo "configure scripts and/or makefiles might override these later" echo "" +echo "Note that the patches were applied by spkg-source" +echo "" # export everything. Probably not necessary in most cases. export CFLAGS diff --git a/build/pkgs/ecl/spkg-src b/build/pkgs/ecl/spkg-src index fe8a0757a9d..587331c6f72 100755 --- a/build/pkgs/ecl/spkg-src +++ b/build/pkgs/ecl/spkg-src @@ -6,11 +6,15 @@ # and its subdirectories! # # HOW TO MAKE THE TARBALL: -# 1) copy upstream ecl-$ECLVERSION.tgz to this directory +# 1) copy upstream the tarball from gitlab; it will be named by the commit +# hash, e.g. ecl-a014bd2c23a9ba863ecdd28c1c48d67de04d3620.tar.gz +# Untar ECL tarball; the root dir will be named ecl.git/ # 2) ./spkg-src -# 3) compress ecl-$ECLVERSION into a .tar.bz2 file +# +# needs autotools and sage in your PATH. # # AUTHOR: Jeroen Demeyer (November 2011) +# Dima Pasechnik (July 2015) # Sanity check: must be run from current directory if ! [ -f spkg-src ]; then @@ -21,12 +25,40 @@ fi # Exit on failure set -e -# Untar ECL tarball -ECLVERSION=13.5.1 -tar xf ecl-"$ECLVERSION".tgz +# now we automate the task: +ECLVERSION=`cat package-version.txt` +ECLTARBALL=ecl-"$ECLVERSION".tar + +mv ecl.git ecl-"$ECLVERSION" cd ecl-"$ECLVERSION" +# the patches are applied here, as it has to be done before +# running autoconf. +cd src +echo "applying patches in ecl-$ECLVERSION/src" +# For some of the patches, Cygwin also has upstream fixes that are +# closely related, keep track. See Trac 11119, for example. +for patch in ../../patches/*.patch; do + patch --verbose -p2 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + + +cd .. # Remove unneeded files to save space -rm -rf msvc +rm -rf msvc/ cd src -rm -rf gc-unstable gmp libffi +rm -rf gc-unstable/ gmp/ libffi/ +libtoolize # to generate configure script +autoreconf -ivf + +cd ../../ +rm -f "$ECLTARBALL".* "$ECLTARBALL" +tar cf "$ECLTARBALL" ecl-"$ECLVERSION"/ +bzip2 "$ECLTARBALL" +mv -f "$ECLTARBALL".bz2 ../../../upstream/ +sage -sh sage-fix-pkg-checksums +rm -rf ecl-"$ECLVERSION"/ diff --git a/build/pkgs/eclib/SPKG.txt b/build/pkgs/eclib/SPKG.txt index 831b5747e57..1de401d1e87 100644 --- a/build/pkgs/eclib/SPKG.txt +++ b/build/pkgs/eclib/SPKG.txt @@ -10,11 +10,6 @@ over 25 years of hard work. eclib is licensed GPL v2+. -== Maintainers == - - * William Stein - * John Cremona - == Upstream Contact == * Author: John Cremona diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index 9c73a2743fc..6fe7b5683e1 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,4 +1,4 @@ tarball=eclib-VERSION.tar.bz2 -sha1=fbcf56cc1e9c881c2a00140958a1e76b622022b6 -md5=b9b788df44c0a18e344472a453f57b1a -cksum=958952944 +sha1=77f404be91fd605f6220a1411912f578c8947c50 +md5=99f99c1b49d3354f3e467b32a684cbe0 +cksum=3819176829 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index d03d642736c..52e8c6d2310 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20150510 +20150827 diff --git a/build/pkgs/ecm/SPKG.txt b/build/pkgs/ecm/SPKG.txt index ae8cf15d62f..9d3efd62a19 100644 --- a/build/pkgs/ecm/SPKG.txt +++ b/build/pkgs/ecm/SPKG.txt @@ -10,10 +10,6 @@ Sources can be obtained from http://gforge.inria.fr/projects/ecm/ LGPL V3+ -== SPKG Maintainers == - - * Jeroen Demeyer - == Upstream Contact == * ecm-discuss@lists.gforge.inria.fr (requires subscription) @@ -48,140 +44,7 @@ LGPL V3+ files for later versions of Microsoft Visual C get added.) === Patches === - * Currently none - -== Changelog == - -=== ecm-6.4.4 (Jeroen Demeyer, 11 April 2013) === - * Trac #14151: upgrade to GMP-ECM 6.4.4, remove all Sage patches - since they are upstream now. - -=== ecm-6.3.p8 (Jeroen Demeyer, 28 May 2012) === - * Trac #12751: remove the gcc-4.7.0 workaround for ia64 since this bug - has been fixed in gcc-4.7.1 and we will not support building Sage - with gcc-4.7.0 on Itanium. - * Remove unused variable SAGE_CONF_OPTS. - * Rename ECM_EXTRA_OPTS to ECM_CONFIGURE for consistency with MPIR. - * Don't override user-set CFLAGS. - -=== ecm-6.3.p7 (Leif Leonhardy, April 19th 2012) === - * #12830: Add `-m[no-]power*` to the processor-specific options recognized; - these are used to enable/disable specific instruction set extensions of the - POWER and PowerPC (PPC) CPUs. - -=== ecm-6.3.p6 (Leif Leonhardy, April 16th 2012) === - * #12830: Add a work-around for GCC 4.7.x on ia64 (Itanium), since GMP-ECM - currently won't build with that and anything but `-O0` on that platform. - * Use `\{1,\}` instead of `\+` in `sed` patterns, which is more portable. - * Also support newer system-wide MPIR installations for printing their - settings. - * Use `patch` to apply patches. Since the pre-patched `configure` in - `patches/` was created with a newer version of autotools (or, rather, the - original `configure` was created with an outdated version), the patch would - have been almost as large as the patched `configure` file itself. Hence - I `autoreconf`ed the source tree with a patched `configure.in` (and almost - the latest versions of autotools), then created a patch to `configure` - from the resulting file(s). Note that therefore `src/` isn't really vanilla - any more, although just the auto-generated files differ (which are still - made from vanilla upstream sources, including `configure.in`). - Add a "Patches" subsection and update "Special Update/Build Instructions". - Remove files in `patches/` from `.hgignore` (and also remove pre-patched - files from that directory); the patch to `configure` and the diff of - `configure.in` are [now] under revision control, which IMHO makes sense. - * Beautify (and simplify) the output with respect to options passed to - `configure`; print the settings of a few more environment variables that - GMP-ECM uses (in case they're set); add some messages, also mention - `--enable-assert` etc. if `SAGE_DEBUG=yes`. - * Remove unused test for GCC. - -=== ecm-6.3.p5 (Leif Leonhardy, April 11th 2012) === - * #12830: Don't add `-march=native` if the assembler doesn't understand the - instructions the compiler emits with that. (E.g. the Apple/XCode assembler - on Darwin doesn't yet support AVX.) - * Fix extraction of `__GMP_CC` and `__GMP_CFLAGS` (from `gmp.h`) for newer - versions of MPIR, which define these to preprocessor macros rather than - strings. - * Redirect warnings and error messages to `stderr`; add some messages. - * Correct `SPKG.txt` w.r.t. applied patches. - -=== ecm-6.3.p4 (Jeroen Demeyer, 13 February 2012) === - * #12501: Do not patch configure.in in spkg-install (to prevent - automake from running). - * Restore upstream sources. - -=== ecm-6.3.p3 (Simon King, December 11th, 2011) === - * #12131: Use --libdir, to make the package work on openSUSE. - -=== ecm-6.3.p2 (Leif Leonhardy, November 25th, 2010) === - * #5847: Apply another patch from upstream to 'configure.in' to fix com- - pilation on 32-bit x86 processors supporting SSE2. (Also #10252.) - (There's only a single, cumulative patch file since both patches are to - 'configure.in'.) - * Work around linker bug on MacOS X 10.5 PPC (see Special Update/Build - Instructions above, and #5847 comment 35 ff.). - * Allow passing extra arguments to 'configure' through ECM_EXTRA_OPTS. - * Add "-march=native" to CFLAGS on platforms that support it, or use - processor-specific CFLAGS from GMP's / MPIR's 'gmp.h' if available, - but only if CFLAGS do not already contain similar (i.e., don't over- - ride a user's choice). [Subject to further improvement.] - * Print settings of CC, CFLAGS etc. and how we configure. - * Print settings of CC and CFLAGS found in 'SAGE_LOCAL/include/gmp.h' - and eventually a system-wide 'gmp.h', although the latter aren't (yet) - used at all, and only processor-specific parts of the former. - * Add '-fPIC' conditionally, i.e. not if we're also building a shared - library (or '--with-pic' was given). - * Don't delete previous installations unless the build succeeded. - * Further clean-up. - -=== ecm-6.3.p1 (Jeroen Demeyer, November 10th, 2010) === - * #5847: Apply a patch from upstream to configure.in to fix compilation - on 32-bit PowerPC processors. - -=== ecm-6.3.p0 (Leif Leonhardy, November 4th, 2010) === - * #5847: Reviewer patch applied to Mike's spkg (upgrade to 6.3) - * There are no patches, but to avoid confusion with the previous - one, it's now 'p0'. - * Added sanity checks to spkg-install and spkg-check. - * Add debug symbols by default, disable optimization if SAGE_DEBUG=yes, - enable optimization (-O3) otherwise. - * Remove also the manual page of previous installations. - * Typo: 'rm -r' -> 'rm -f' (header file) - * Removed setting of CXXFLAGS, since we don't have C++ code. - * Don't overwrite CFLAGS if SAGE64=yes (instead, append). - Removed -O2 -g in that case. Make use of CFLAG64 if set. - * Quote $SAGE_LOCAL in the parameters to configure, too. - * Use $MAKE instead of 'make' in spkg-check, too. - * Some messages changed (e.g. all failures now starting with "Error"), - some added. - * A few comments/notes added (SPKG.txt, spkg-install). - -=== ecm-6.3 (Mike Hansen, August 16th, 2010) === - * #5847: Update GMP-ECM to 6.3 - -=== ecm-6.2.1.p2 (Jaap Spies, Jan 25th, 2010) === - * Made SAGE64="yes" work also for Open Solaris 64 bit - * Removed Michael Mabshoff as maintainer - -=== ecm-6.2.1.p1 (Franois Bissey, October 26th, 2009) === - * The removal of the old version of ecm in ecm-6.2.1_p0.spkg is - broken because of the typo "SAGE_LCOAL"; notice that it should be - "SAGE_LOCAL". - -=== ecm-6.2.1.p0 (Michael Abshoff, January 19th, 2008) === - * bump version to force recompile due to gmp -> MPIR switch - -=== ecm-6.2.1 (Michael Abshoff, December 23rd, 2008) === - * upgrade to latest upstream release (#3237) - * clean up old static library, headers and binary - * improve SPKG.txt - -=== ecm-6.1.3.p0 (Michael Abshoff, May 18th, 2008) === - * add 64 bit OSX support - -=== ecm-6.1.3 (Michael Abshoff, Sept. 24th, 2007) === - * update to ecm 6.1.3 - -* Initial version - date unknown: - -I obtained the package from http://gforge.inria.fr/projects/ecm/ - + * asm-align.patch: on OS X using Xcode 7, ".align 64" in the files + "x86_64/mulredc*.asm" causes an error, and similarly with ".align + 32,,16". These need to be changed to ".p2align 6" and ".p2align + 5,,4", respectively diff --git a/build/pkgs/ecm/patches/asm-align.patch b/build/pkgs/ecm/patches/asm-align.patch new file mode 100644 index 00000000000..88e7a8f4f80 --- /dev/null +++ b/build/pkgs/ecm/patches/asm-align.patch @@ -0,0 +1,627 @@ +diff -r -u ecm-6.4.4-orig/x86_64/mulredc10.asm ecm-6.4.4/x86_64/mulredc10.asm +--- ecm-6.4.4-orig/x86_64/mulredc10.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc10.asm 2015-09-23 21:02:14.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc10 + TYPE(GSYM_PREFIX`'mulredc`'10,`function') + +@@ -535,7 +535,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc11.asm ecm-6.4.4/x86_64/mulredc11.asm +--- ecm-6.4.4-orig/x86_64/mulredc11.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc11.asm 2015-09-23 21:05:49.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc11 + TYPE(GSYM_PREFIX`'mulredc`'11,`function') + +@@ -579,7 +579,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc12.asm ecm-6.4.4/x86_64/mulredc12.asm +--- ecm-6.4.4-orig/x86_64/mulredc12.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc12.asm 2015-09-23 21:05:55.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc12 + TYPE(GSYM_PREFIX`'mulredc`'12,`function') + +@@ -623,7 +623,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc13.asm ecm-6.4.4/x86_64/mulredc13.asm +--- ecm-6.4.4-orig/x86_64/mulredc13.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc13.asm 2015-09-23 21:05:57.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc13 + TYPE(GSYM_PREFIX`'mulredc`'13,`function') + +@@ -667,7 +667,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc14.asm ecm-6.4.4/x86_64/mulredc14.asm +--- ecm-6.4.4-orig/x86_64/mulredc14.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc14.asm 2015-09-23 21:05:59.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc14 + TYPE(GSYM_PREFIX`'mulredc`'14,`function') + +@@ -711,7 +711,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc15.asm ecm-6.4.4/x86_64/mulredc15.asm +--- ecm-6.4.4-orig/x86_64/mulredc15.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc15.asm 2015-09-23 21:06:02.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc15 + TYPE(GSYM_PREFIX`'mulredc`'15,`function') + +@@ -755,7 +755,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc16.asm ecm-6.4.4/x86_64/mulredc16.asm +--- ecm-6.4.4-orig/x86_64/mulredc16.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc16.asm 2015-09-23 21:06:03.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc16 + TYPE(GSYM_PREFIX`'mulredc`'16,`function') + +@@ -799,7 +799,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc17.asm ecm-6.4.4/x86_64/mulredc17.asm +--- ecm-6.4.4-orig/x86_64/mulredc17.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc17.asm 2015-09-23 21:06:05.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc17 + TYPE(GSYM_PREFIX`'mulredc`'17,`function') + +@@ -843,7 +843,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc18.asm ecm-6.4.4/x86_64/mulredc18.asm +--- ecm-6.4.4-orig/x86_64/mulredc18.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc18.asm 2015-09-23 21:06:07.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc18 + TYPE(GSYM_PREFIX`'mulredc`'18,`function') + +@@ -887,7 +887,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc19.asm ecm-6.4.4/x86_64/mulredc19.asm +--- ecm-6.4.4-orig/x86_64/mulredc19.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc19.asm 2015-09-23 21:06:10.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc19 + TYPE(GSYM_PREFIX`'mulredc`'19,`function') + +@@ -931,7 +931,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_10.asm ecm-6.4.4/x86_64/mulredc1_10.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_10.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_10.asm 2015-09-23 20:52:08.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 bytes long + GLOBL GSYM_PREFIX`'mulredc1_10 + TYPE(GSYM_PREFIX`'mulredc1_`'10,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_11.asm ecm-6.4.4/x86_64/mulredc1_11.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_11.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_11.asm 2015-09-23 21:03:24.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_11 + TYPE(GSYM_PREFIX`'mulredc1_`'11,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_12.asm ecm-6.4.4/x86_64/mulredc1_12.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_12.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_12.asm 2015-09-23 21:03:25.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_12 + TYPE(GSYM_PREFIX`'mulredc1_`'12,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_13.asm ecm-6.4.4/x86_64/mulredc1_13.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_13.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_13.asm 2015-09-23 21:03:25.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_13 + TYPE(GSYM_PREFIX`'mulredc1_`'13,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_14.asm ecm-6.4.4/x86_64/mulredc1_14.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_14.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_14.asm 2015-09-23 21:03:25.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_14 + TYPE(GSYM_PREFIX`'mulredc1_`'14,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_15.asm ecm-6.4.4/x86_64/mulredc1_15.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_15.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_15.asm 2015-09-23 21:03:25.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_15 + TYPE(GSYM_PREFIX`'mulredc1_`'15,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_16.asm ecm-6.4.4/x86_64/mulredc1_16.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_16.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_16.asm 2015-09-23 21:03:26.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_16 + TYPE(GSYM_PREFIX`'mulredc1_`'16,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_17.asm ecm-6.4.4/x86_64/mulredc1_17.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_17.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_17.asm 2015-09-23 21:03:26.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_17 + TYPE(GSYM_PREFIX`'mulredc1_`'17,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_18.asm ecm-6.4.4/x86_64/mulredc1_18.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_18.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_18.asm 2015-09-23 21:03:26.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_18 + TYPE(GSYM_PREFIX`'mulredc1_`'18,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_19.asm ecm-6.4.4/x86_64/mulredc1_19.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_19.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_19.asm 2015-09-23 21:03:26.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_19 + TYPE(GSYM_PREFIX`'mulredc1_`'19,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_2.asm ecm-6.4.4/x86_64/mulredc1_2.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_2.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_2.asm 2015-09-23 21:03:26.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_2 + TYPE(GSYM_PREFIX`'mulredc1_`'2,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_20.asm ecm-6.4.4/x86_64/mulredc1_20.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_20.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_20.asm 2015-09-23 21:03:27.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_20 + TYPE(GSYM_PREFIX`'mulredc1_`'20,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_3.asm ecm-6.4.4/x86_64/mulredc1_3.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_3.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_3.asm 2015-09-23 21:03:27.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_3 + TYPE(GSYM_PREFIX`'mulredc1_`'3,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_4.asm ecm-6.4.4/x86_64/mulredc1_4.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_4.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_4.asm 2015-09-23 21:03:28.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_4 + TYPE(GSYM_PREFIX`'mulredc1_`'4,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_5.asm ecm-6.4.4/x86_64/mulredc1_5.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_5.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_5.asm 2015-09-23 21:03:28.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_5 + TYPE(GSYM_PREFIX`'mulredc1_`'5,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_6.asm ecm-6.4.4/x86_64/mulredc1_6.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_6.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_6.asm 2015-09-23 21:03:28.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_6 + TYPE(GSYM_PREFIX`'mulredc1_`'6,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_7.asm ecm-6.4.4/x86_64/mulredc1_7.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_7.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_7.asm 2015-09-23 21:03:28.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_7 + TYPE(GSYM_PREFIX`'mulredc1_`'7,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_8.asm ecm-6.4.4/x86_64/mulredc1_8.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_8.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_8.asm 2015-09-23 21:03:28.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_8 + TYPE(GSYM_PREFIX`'mulredc1_`'8,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc1_9.asm ecm-6.4.4/x86_64/mulredc1_9.asm +--- ecm-6.4.4-orig/x86_64/mulredc1_9.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc1_9.asm 2015-09-23 21:03:29.000000000 -0700 +@@ -18,7 +18,7 @@ + define(`INVM_PARAM',`%r8')dnl' + )dnl + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc1_9 + TYPE(GSYM_PREFIX`'mulredc1_`'9,`function') + +diff -r -u ecm-6.4.4-orig/x86_64/mulredc2.asm ecm-6.4.4/x86_64/mulredc2.asm +--- ecm-6.4.4-orig/x86_64/mulredc2.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc2.asm 2015-09-23 21:04:57.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc2 + TYPE(GSYM_PREFIX`'mulredc`'2,`function') + +@@ -183,7 +183,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc20.asm ecm-6.4.4/x86_64/mulredc20.asm +--- ecm-6.4.4-orig/x86_64/mulredc20.asm 2013-02-27 07:17:53.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc20.asm 2015-09-23 21:05:17.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc20 + TYPE(GSYM_PREFIX`'mulredc`'20,`function') + +@@ -975,7 +975,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc3.asm ecm-6.4.4/x86_64/mulredc3.asm +--- ecm-6.4.4-orig/x86_64/mulredc3.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc3.asm 2015-09-23 21:06:14.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc3 + TYPE(GSYM_PREFIX`'mulredc`'3,`function') + +@@ -227,7 +227,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc4.asm ecm-6.4.4/x86_64/mulredc4.asm +--- ecm-6.4.4-orig/x86_64/mulredc4.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc4.asm 2015-09-23 21:06:18.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc4 + TYPE(GSYM_PREFIX`'mulredc`'4,`function') + +@@ -271,7 +271,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc5.asm ecm-6.4.4/x86_64/mulredc5.asm +--- ecm-6.4.4-orig/x86_64/mulredc5.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc5.asm 2015-09-23 21:06:23.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc5 + TYPE(GSYM_PREFIX`'mulredc`'5,`function') + +@@ -315,7 +315,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc6.asm ecm-6.4.4/x86_64/mulredc6.asm +--- ecm-6.4.4-orig/x86_64/mulredc6.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc6.asm 2015-09-23 21:06:27.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc6 + TYPE(GSYM_PREFIX`'mulredc`'6,`function') + +@@ -359,7 +359,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc7.asm ecm-6.4.4/x86_64/mulredc7.asm +--- ecm-6.4.4-orig/x86_64/mulredc7.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc7.asm 2015-09-23 21:06:31.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc7 + TYPE(GSYM_PREFIX`'mulredc`'7,`function') + +@@ -403,7 +403,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc8.asm ecm-6.4.4/x86_64/mulredc8.asm +--- ecm-6.4.4-orig/x86_64/mulredc8.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc8.asm 2015-09-23 21:06:34.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc8 + TYPE(GSYM_PREFIX`'mulredc`'8,`function') + +@@ -447,7 +447,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m +diff -r -u ecm-6.4.4-orig/x86_64/mulredc9.asm ecm-6.4.4/x86_64/mulredc9.asm +--- ecm-6.4.4-orig/x86_64/mulredc9.asm 2013-02-27 07:17:52.000000000 -0800 ++++ ecm-6.4.4/x86_64/mulredc9.asm 2015-09-23 21:06:38.000000000 -0700 +@@ -23,7 +23,7 @@ + include(`config.m4') + + TEXT +-.align 64 # Opteron L1 code cache line is 64 bytes long ++.p2align 6 # Opteron L1 code cache line is 64 5,,4 + GLOBL GSYM_PREFIX`'mulredc9 + TYPE(GSYM_PREFIX`'mulredc`'9,`function') + +@@ -491,7 +491,7 @@ + # i > 0 passes + ######################################################################### + +-.align 32,,16 ++.p2align 5,,4 + LABEL_SUFFIX(1) + + # register values at loop entry: %TP = tmp, %I = i, %YP = y, %MP = m diff --git a/build/pkgs/elliptic_curves/SPKG.txt b/build/pkgs/elliptic_curves/SPKG.txt index 27b316e85f9..0d4efa33b0d 100644 --- a/build/pkgs/elliptic_curves/SPKG.txt +++ b/build/pkgs/elliptic_curves/SPKG.txt @@ -9,10 +9,6 @@ Includes two databases: http://sage.math.washington.edu/cremona/INDEX.html * William Stein's database of interesting curves -== SPKG Maintainers == - - * R. Andrew Ohana - == Upstream Contact == === cremona_mini === diff --git a/build/pkgs/fflas_ffpack/SPKG.txt b/build/pkgs/fflas_ffpack/SPKG.txt index 8e18912a400..585e244d2da 100644 --- a/build/pkgs/fflas_ffpack/SPKG.txt +++ b/build/pkgs/fflas_ffpack/SPKG.txt @@ -11,10 +11,6 @@ http://linalg.org/projects/fflas-ffpack LGPL V2.1 or later -== Maintainers == - - * Martin Albrecht - == SPKG Repository == https://bitbucket.org/malb/fflas-ffpack-spkg diff --git a/build/pkgs/flint/SPKG.txt b/build/pkgs/flint/SPKG.txt index 92bb8f957a1..935850e3888 100644 --- a/build/pkgs/flint/SPKG.txt +++ b/build/pkgs/flint/SPKG.txt @@ -10,11 +10,6 @@ Website: www.flintlib.org FLINT is licensed GPL v2+. -== SPKG Maintainers == - - * Fredrik Johansson - * Jean-Pierre Flori - == Upstream Contact == * flint-devel Gougle Group (http://groups.google.co.uk/group/flint-devel) @@ -25,9 +20,3 @@ FLINT is licensed GPL v2+. * MPIR * MPFR * NTL - -== Special Update/Build Instructions == - -=== Patches === - * test_empty_string.patch: fix test for empty string in Makefile.in. - * ntl62.patch: do not include multiple times NTL headers. diff --git a/build/pkgs/flint/checksums.ini b/build/pkgs/flint/checksums.ini index ac09ea6497a..06e7bec084c 100644 --- a/build/pkgs/flint/checksums.ini +++ b/build/pkgs/flint/checksums.ini @@ -1,4 +1,4 @@ tarball=flint-VERSION.tar.gz -sha1=d6349906644a09b4b2a43532012fbdd59da02780 -md5=6504b9deabeafb9313e57153a1730b33 -cksum=817805972 +sha1=67c747ec92dda0dbc57ab88e2c501b11537299a5 +md5=cda885309362150196aed66a5e0f0383 +cksum=4232495945 diff --git a/build/pkgs/flint/package-version.txt b/build/pkgs/flint/package-version.txt index 59aa62c1fa4..f225a78adf0 100644 --- a/build/pkgs/flint/package-version.txt +++ b/build/pkgs/flint/package-version.txt @@ -1 +1 @@ -2.4.5 +2.5.2 diff --git a/build/pkgs/flint/patches/flint-2.4.5-test.patch b/build/pkgs/flint/patches/flint-2.4.5-test.patch deleted file mode 100644 index b48235fcba9..00000000000 --- a/build/pkgs/flint/patches/flint-2.4.5-test.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -Naur flint-2.4.5.orig/Makefile.in flint-2.4.5/Makefile.in ---- flint-2.4.5.orig/Makefile.in 2015-05-15 19:05:59.034143372 +1200 -+++ flint-2.4.5/Makefile.in 2015-05-15 19:06:26.204204225 +1200 -@@ -212,7 +212,7 @@ - $(QUIET_CXX) $(CXX) $(CFLAGS) $(INCS) -c $< -o $@; - - build/interfaces/test/t-NTL-interface$(EXEEXT): interfaces/test/t-NTL-interface.cpp -- $(QUIET_CXX) $(CXX) $(CFLAGS) $(INCS) $< build/interfaces/NTL-interface.o -o $@ $(LIBS); -+ $(QUIET_CXX) $(CXX) $(CFLAGS) $(INCS) $< build/interfaces/NTL-interface.lo -o $@ $(LIBS); - - print-%: - @echo '$*=$($*)' diff --git a/build/pkgs/flint/patches/ntl62.patch b/build/pkgs/flint/patches/ntl62.patch deleted file mode 100644 index e08fa774e29..00000000000 --- a/build/pkgs/flint/patches/ntl62.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff -druN flint-2.5.orig/interfaces/NTL-interface.cpp flint-2.5/interfaces/NTL-interface.cpp ---- flint-2.5.orig/interfaces/NTL-interface.cpp 2014-07-16 07:50:40.000000000 -0700 -+++ flint-2.5/interfaces/NTL-interface.cpp 2014-08-26 07:51:55.585884615 -0700 -@@ -32,9 +32,7 @@ - #include - #include - #include --#include - #include --#include - #include - - #include "flint.h" diff --git a/build/pkgs/flint/patches/test_empty_string.patch b/build/pkgs/flint/patches/test_empty_string.patch deleted file mode 100644 index b4073926919..00000000000 --- a/build/pkgs/flint/patches/test_empty_string.patch +++ /dev/null @@ -1,19 +0,0 @@ -commit 81c820b73dc0208f107629d7658f4f0642ca64e8 -Author: Jean-Pierre Flori -Date: Mon Feb 3 15:02:22 2014 +0100 - - Fix test for empty string in install target. - -diff --git a/Makefile.in b/Makefile.in -index 0fa717b..19caed2 100644 ---- a/Makefile.in -+++ b/Makefile.in -@@ -178,7 +178,7 @@ install: library - cp libflint.a $(DESTDIR)$(PREFIX)/lib; \ - fi - cp $(HEADERS) $(DESTDIR)$(PREFIX)/include/flint -- $(AT)if [ ! -z $(EXT_HEADERS) ]; then \ -+ $(AT)if [ ! -z "$(EXT_HEADERS)" ]; then \ - cp $(EXT_HEADERS) $(DESTDIR)$(PREFIX)/include/flint; \ - fi - mkdir -p $(DESTDIR)$(FLINT_CPIMPORT_DIR) diff --git a/build/pkgs/flint/spkg-install b/build/pkgs/flint/spkg-install index 67bb3d3cc5f..c8bb0653a9b 100755 --- a/build/pkgs/flint/spkg-install +++ b/build/pkgs/flint/spkg-install @@ -26,15 +26,15 @@ fi cd src -echo "Patching FLINT." -for patch in ../patches/*.patch; do - [ -f "$patch" ] || continue - patch -p1 <"$patch" - if [ $? -ne 0 ]; then - echo >&2 "Error applying '$patch'" - exit 1 - fi -done +#echo "Patching FLINT." +#for patch in ../patches/*.patch; do +# [ -f "$patch" ] || continue +# patch -p1 <"$patch" +# if [ $? -ne 0 ]; then +# echo >&2 "Error applying '$patch'" +# exit 1 +# fi +#done echo "Configuring FLINT." ./configure \ diff --git a/build/pkgs/freetype/SPKG.txt b/build/pkgs/freetype/SPKG.txt index 6c70a5ecb90..5b74f50b7f7 100644 --- a/build/pkgs/freetype/SPKG.txt +++ b/build/pkgs/freetype/SPKG.txt @@ -1,6 +1,3 @@ -MAINTAINER: - * William Stein - == License == * FreeType License diff --git a/build/pkgs/freetype/spkg-install b/build/pkgs/freetype/spkg-install index c29237b484a..304d78b6104 100755 --- a/build/pkgs/freetype/spkg-install +++ b/build/pkgs/freetype/spkg-install @@ -13,7 +13,8 @@ fi cd src -GNUMAKE=${MAKE} ./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" +# Disabling harfbuzz until upstream properly check for suitable version +GNUMAKE=${MAKE} ./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" --with-harfbuzz=no if [ $? -ne 0 ]; then exit 1; diff --git a/build/pkgs/gambit/SPKG.txt b/build/pkgs/gambit/SPKG.txt index fadfb9b88af..01710e57d06 100644 --- a/build/pkgs/gambit/SPKG.txt +++ b/build/pkgs/gambit/SPKG.txt @@ -10,11 +10,6 @@ Richard McKelvey at the California Institute of Technology. GPL v2+ -== SPKG Maintainers == - -* James Campbell -* Vince Knight - == Upstream Contact == * Website: http://www.gambit-project.org/ diff --git a/build/pkgs/gap/SPKG.txt b/build/pkgs/gap/SPKG.txt index ce8e3a79b3c..021c1578fb1 100644 --- a/build/pkgs/gap/SPKG.txt +++ b/build/pkgs/gap/SPKG.txt @@ -16,15 +16,6 @@ extend it for your special use. This is a stripped-down version of GAP. The databases, which are architecture-independent, are in a separate package. -== SPKG Maintainers == - - * William Stein - * Robert Miller - * David Joyner - * Dmitrii Pasechnik - * Martin Albrecht - * Volker Braun - == Upstream Contact == David Joyner, wdjoyner@gmail.com (on the GAP team, but diff --git a/build/pkgs/gap_packages/SPKG.txt b/build/pkgs/gap_packages/SPKG.txt index 546bc0b76da..44cf1067486 100644 --- a/build/pkgs/gap_packages/SPKG.txt +++ b/build/pkgs/gap_packages/SPKG.txt @@ -5,13 +5,6 @@ Several "official" and "undeposited" GAP packages available from http://www.gap-system.org/Packages/packages.html -== SPKG Maintainers == - - * William Stein - * Robert Miller - * David Joyner - * Dmitrii Pasechnik - == Upstream Contact == * Dmitrii Pasechnik, dimpase@gmail.com diff --git a/build/pkgs/gcc/patches/bug-66509.patch b/build/pkgs/gcc/patches/bug-66509.patch new file mode 100644 index 00000000000..c9e8c6c20de --- /dev/null +++ b/build/pkgs/gcc/patches/bug-66509.patch @@ -0,0 +1,48 @@ +Upstream fix for incompatibility with the new clang assembler + +Taken from homebrew: +https://github.com/Homebrew/homebrew/commit/ebd659ab80c1585ac7f48230a2c955eb9b1e5e0a + + +diff --git a/gcc/configure b/gcc/configure +index 9523773..52b0bf7 100755 +--- a/gcc/configure ++++ b/gcc/configure +@@ -24884,7 +24884,7 @@ if test "${gcc_cv_as_ix86_filds+set}" = set; then : + else + gcc_cv_as_ix86_filds=no + if test x$gcc_cv_as != x; then +- $as_echo 'filds mem; fists mem' > conftest.s ++ $as_echo 'filds (%ebp); fists (%ebp)' > conftest.s + if { ac_try='$gcc_cv_as $gcc_cv_as_flags -o conftest.o conftest.s >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 +@@ -24915,7 +24915,7 @@ if test "${gcc_cv_as_ix86_fildq+set}" = set; then : + else + gcc_cv_as_ix86_fildq=no + if test x$gcc_cv_as != x; then +- $as_echo 'fildq mem; fistpq mem' > conftest.s ++ $as_echo 'fildq (%ebp); fistpq (%ebp)' > conftest.s + if { ac_try='$gcc_cv_as $gcc_cv_as_flags -o conftest.o conftest.s >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 +diff --git a/gcc/configure.ac b/gcc/configure.ac +index 68b0ee8..bd53978 100644 +--- a/gcc/configure.ac ++++ b/gcc/configure.ac +@@ -3869,13 +3869,13 @@ foo: nop + + gcc_GAS_CHECK_FEATURE([filds and fists mnemonics], + gcc_cv_as_ix86_filds,,, +- [filds mem; fists mem],, ++ [filds (%ebp); fists (%ebp)],, + [AC_DEFINE(HAVE_AS_IX86_FILDS, 1, + [Define if your assembler uses filds and fists mnemonics.])]) + + gcc_GAS_CHECK_FEATURE([fildq and fistpq mnemonics], + gcc_cv_as_ix86_fildq,,, +- [fildq mem; fistpq mem],, ++ [fildq (%ebp); fistpq (%ebp)],, + [AC_DEFINE(HAVE_AS_IX86_FILDQ, 1, + [Define if your assembler uses fildq and fistq mnemonics.])]) + diff --git a/build/pkgs/gcc/spkg-install b/build/pkgs/gcc/spkg-install index a93411334ad..f9c7fe690e9 100755 --- a/build/pkgs/gcc/spkg-install +++ b/build/pkgs/gcc/spkg-install @@ -137,3 +137,7 @@ $MAKE install # Force re-installation of mpir, mpfr and mpc with the GCC we just built. cd "$SAGE_SPKG_INST" rm -f mpir-* mpfr-* mpc-* + +# Force re-configuration: the next time that "make" is run, we don't +# want GCC to be built again, see Trac #19324 +touch "$SAGE_ROOT/configure" diff --git a/build/pkgs/gdb/SPKG.txt b/build/pkgs/gdb/SPKG.txt index ceb78ca35b4..6ec63e386f8 100644 --- a/build/pkgs/gdb/SPKG.txt +++ b/build/pkgs/gdb/SPKG.txt @@ -10,10 +10,6 @@ was doing at the moment it crashed. GPL v3+ -== SPKG Maintainers == - -* Volker Braun - == Upstream Contact == http://www.gnu.org/software/gdb/ diff --git a/build/pkgs/gf2x/spkg-install b/build/pkgs/gf2x/spkg-install index ee7e6a07c63..31798a59b90 100755 --- a/build/pkgs/gf2x/spkg-install +++ b/build/pkgs/gf2x/spkg-install @@ -26,8 +26,8 @@ touch aclocal.m4 configure Makefile.in gf2x/gf2x-config.h.in if [ "$SAGE_DEBUG" = "yes" ]; then echo "Building a debug version of gf2x." export CFLAGS="-O0 -g $CFLAGS" -elif $CC --version 2>/dev/null |grep 'gcc.* 5[.]1' >/dev/null; then - echo "Using compiler flags to work around problems with GCC 5.1 (Trac #18580)" +elif $CC --version 2>/dev/null |grep 'gcc.* 5[.][12]' >/dev/null; then + echo "Using compiler flags to work around problems with GCC 5.1/5.2 (Trac #18580,#18978)" export CFLAGS="-O2 -fno-forward-propagate -g $CFLAGS" else export CFLAGS="-O2 -g $CFLAGS" diff --git a/build/pkgs/giac/SPKG.txt b/build/pkgs/giac/SPKG.txt index b0be08215d7..478906de737 100644 --- a/build/pkgs/giac/SPKG.txt +++ b/build/pkgs/giac/SPKG.txt @@ -19,10 +19,6 @@ GPLv3+ except the french html documentation which is freely redistributable for non commercial only purposes. This doc has been removed in the spkg) -== SPKG Maintener == - - * Han Frederic: frederic.han@imj-prg.fr - == Upstream Contact == * Bernard Parisse: http://www-fourier.ujf-grenoble.fr/~parisse/giac.html diff --git a/build/pkgs/giacpy/SPKG.txt b/build/pkgs/giacpy/SPKG.txt index 563a07264bb..cdcf223302a 100644 --- a/build/pkgs/giacpy/SPKG.txt +++ b/build/pkgs/giacpy/SPKG.txt @@ -8,10 +8,6 @@ GPLv2 or above -== SPKG Maintener == - - * Han Frederic - == Upstream Contact == * Han Frederic: frederic.han@imj-prg.fr diff --git a/build/pkgs/git/SPKG.txt b/build/pkgs/git/SPKG.txt index 9727ea03fb3..51e88426e20 100644 --- a/build/pkgs/git/SPKG.txt +++ b/build/pkgs/git/SPKG.txt @@ -8,10 +8,6 @@ -- `man git` -== SPKG Maintainers == - - * Keshav Kini - == Upstream Contact == * Maintainer: Junio C. Hamano diff --git a/build/pkgs/git/checksums.ini b/build/pkgs/git/checksums.ini index 88657651061..3a6e9681738 100644 --- a/build/pkgs/git/checksums.ini +++ b/build/pkgs/git/checksums.ini @@ -1,4 +1,4 @@ tarball=git-VERSION.tar.gz -sha1=150efeb9c016cb8d3e768a408f3f407d18d69661 -md5=edf994cf34cd3354dadcdfa6b4292335 -cksum=995320395 +sha1=ff32a94936309ca3f0e3d56e479e7510a3c1c925 +md5=da293290da69f45a86a311ad3cd43dc8 +cksum=2025246710 diff --git a/build/pkgs/git/package-version.txt b/build/pkgs/git/package-version.txt index 276cbf9e285..097a15a2af3 100644 --- a/build/pkgs/git/package-version.txt +++ b/build/pkgs/git/package-version.txt @@ -1 +1 @@ -2.3.0 +2.6.2 diff --git a/build/pkgs/git/spkg-install b/build/pkgs/git/spkg-install index 6331ae29f5f..f9ba3204f16 100755 --- a/build/pkgs/git/spkg-install +++ b/build/pkgs/git/spkg-install @@ -49,13 +49,10 @@ done export NO_FINK=1 export NO_DARWIN_PORTS=1 -# Darwin 8 (Tiger) does not support common crypto -if { uname -sr | grep 'Darwin 8' ;} &>/dev/null; then - export NO_APPLE_COMMON_CRYPTO=1 -fi - # OSX Git with FSF GCC is broken, disable completely for now. See #17091 -export NO_APPLE_COMMON_CRYPTO=1 +if [ "$UNAME" = "Darwin" ]; then + export NO_OPENSSL=1 +fi # First make GIT-VERSION-FILE (we patched Makefile such that configure # no longer depends on this, so it's safer to explicitly build this). diff --git a/build/pkgs/git_trac/SPKG.txt b/build/pkgs/git_trac/SPKG.txt index d5d73c5084c..08c3b2b7683 100644 --- a/build/pkgs/git_trac/SPKG.txt +++ b/build/pkgs/git_trac/SPKG.txt @@ -9,10 +9,6 @@ interfaces with trac over XMLRPC. GPLv3+ -== SPKG Maintainers == - -* Volker Braun - == Upstream Contact == https://github.com/sagemath/git-trac-command diff --git a/build/pkgs/givaro/SPKG.txt b/build/pkgs/givaro/SPKG.txt index b99ef07db71..9186cbce3f2 100644 --- a/build/pkgs/givaro/SPKG.txt +++ b/build/pkgs/givaro/SPKG.txt @@ -29,11 +29,6 @@ SPKG Repository: https://bitbucket.org/malb/givaro-spkg * GNU patch * GMP/MPIR -== Maintainers == - - * Clement Pernet - * Martin Albrecht - == Changelog == === givaro-3.7.1 (Jean-Pierre Flori, 9 August 2012) === diff --git a/build/pkgs/gp2c/SPKG.txt b/build/pkgs/gp2c/SPKG.txt new file mode 100644 index 00000000000..984b63c7865 --- /dev/null +++ b/build/pkgs/gp2c/SPKG.txt @@ -0,0 +1,18 @@ += gp2c = + +== Description == + +The gp2c compiler is a package for translating GP routines into the C +programming language, so that they can be compiled and used with the PARI +system or the GP calculator. + +== License == + +GPL version 2+ + +== Upstream Contact == + * http://pari.math.u-bordeaux.fr/ + +== Dependencies == + * PARI + * Perl diff --git a/build/pkgs/gp2c/checksums.ini b/build/pkgs/gp2c/checksums.ini new file mode 100644 index 00000000000..0f114ad0f51 --- /dev/null +++ b/build/pkgs/gp2c/checksums.ini @@ -0,0 +1,4 @@ +tarball=gp2c-VERSION.tar.gz +sha1=5acb1a13e1ed8ee877ffb34baa3b817e720f3e50 +md5=cb263990e399153aca6a2540930b4600 +cksum=1931194041 diff --git a/build/pkgs/gp2c/dependencies b/build/pkgs/gp2c/dependencies new file mode 100644 index 00000000000..58cfd0bc484 --- /dev/null +++ b/build/pkgs/gp2c/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PARI) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. diff --git a/build/pkgs/gp2c/package-version.txt b/build/pkgs/gp2c/package-version.txt new file mode 100644 index 00000000000..6eb15535643 --- /dev/null +++ b/build/pkgs/gp2c/package-version.txt @@ -0,0 +1 @@ +0.0.9pl3 diff --git a/build/pkgs/gp2c/patches/20150326.patch b/build/pkgs/gp2c/patches/20150326.patch new file mode 100644 index 00000000000..3f7651f8162 --- /dev/null +++ b/build/pkgs/gp2c/patches/20150326.patch @@ -0,0 +1,218 @@ +Various fixes between released version 0.0.9pl3 and gp2c git repo + +diff -ru gp2c-0.0.9pl3/gp2c gp2c/gp2c +--- gp2c-0.0.9pl3/gp2c 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c 2015-07-09 14:50:05.600098572 +0200 +@@ -14,4 +14,4 @@ + EOF + exit 1; + fi +-GP2C_FUNC_DSC=desc/func.dsc src/gp2c $* ++GP2C_FUNC_DSC=desc/func.dsc exec src/gp2c "$@" +diff -ru gp2c-0.0.9pl3/gp2c-dbg gp2c/gp2c-dbg +--- gp2c-0.0.9pl3/gp2c-dbg 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-dbg 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-dbg $* ++GP2C=./gp2c exec scripts/gp2c-dbg "$@" +diff -ru gp2c-0.0.9pl3/gp2c-run gp2c/gp2c-run +--- gp2c-0.0.9pl3/gp2c-run 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-run 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-run $* ++GP2C=./gp2c exec scripts/gp2c-run "$@" +diff -ru gp2c-0.0.9pl3/src/funcdesc.c gp2c/src/funcdesc.c +--- gp2c-0.0.9pl3/src/funcdesc.c 2014-11-17 14:30:55.000000000 +0100 ++++ gp2c/src/funcdesc.c 2015-07-09 14:50:05.603098609 +0200 +@@ -575,7 +575,8 @@ + gpfunc *gp = lfunc + findfunction(entryname(arg[0])); + if (gp->spec==GPuser && gp->user->wrapper>=0) + { +- if (funcmode(*gp)&(1<user->wrapper; ++ if ((funcmode(*gp)&(1<fout, nb-1,arg+1,FC_tovecprec,d->nerr); + else + { +diff -ru gp2c-0.0.9pl3/src/funcspec.c gp2c/src/funcspec.c +--- gp2c-0.0.9pl3/src/funcspec.c 2014-11-22 22:28:59.000000000 +0100 ++++ gp2c/src/funcspec.c 2015-07-09 14:50:05.604098621 +0200 +@@ -432,8 +432,11 @@ + arg[0] = newleaf(pred); + } + genblock(arg[0],n); +- arg[1]=geninsertvar(arg[1],ret); +- makesubblock(arg[1]); ++ if (arg[1]!=GNOARG) ++ { ++ arg[1]=geninsertvar(arg[1],ret); ++ makesubblock(arg[1]); ++ } + if(nb==3) + { + arg[2]=geninsertvar(arg[2],ret==-1?-1:newleaf(ret)); +diff -ru gp2c-0.0.9pl3/src/genfunc.c gp2c/src/genfunc.c +--- gp2c-0.0.9pl3/src/genfunc.c 2014-12-16 10:56:21.000000000 +0100 ++++ gp2c/src/genfunc.c 2015-07-09 14:50:05.605098633 +0200 +@@ -524,13 +524,15 @@ + } + if (name) + { +- if (i>=nb) +- die(err_func,"too few arguments in lambda"); + fputs(" ",fout); + if (c=='p') + fprintf(fout,"prec"); + else ++ { ++ if (i>=nb) ++ die(err_func,"too few arguments in lambda"); + gencode(fout, name[i++]); ++ } + } + } + if (name && i=nb) +- die(err_func,"too few arguments in lambda"); + if (c=='p') +- fprintf(fout,"prec"); ++ { ++ if (mode&(1<=nb) ++ die(err_func,"too few arguments in lambda"); ++ if (!firstarg) fprintf(fout,", "); ++ firstarg=0; ++ ot = tree[name[i]].t; ++ tree[name[i]].t = t; ++ gencast(fout, name[i], ot); ++ tree[name[i]].t = ot; ++ i++; ++ } + } + if (name && iuser->defnode; + int savc=s_ctx.n; +@@ -650,7 +667,6 @@ + int res; + ctxvar *vres; + context *fc=block+gp->user->bl; +- gpfunc *wr=lfunc+wrap; + gpdescarg *rule; + if (!wr->proto.code) + die(wr->node,"Wrapper not defined"); +@@ -688,7 +704,7 @@ + fprintf(fout," = "); + } + fprintf(fout,"%s(",gp->proto.cname); +- firstarg=genwrapargs(fout, wrap, nb, stack); ++ firstarg=genwrapargs(fout, wrap, nb, stack, m); + for (i=0,d=1;is.n;i++) + { + ctxvar *v=fc->c+i; +diff -ru gp2c-0.0.9pl3/src/printnode.c gp2c/src/printnode.c +--- gp2c-0.0.9pl3/src/printnode.c 2013-10-12 20:32:23.000000000 +0200 ++++ gp2c/src/printnode.c 2015-07-09 14:50:05.606098645 +0200 +@@ -231,6 +231,10 @@ + fprintf(fout,"&"); + printnode(fout,x); + break; ++ case Fvararg: ++ printnode(fout,x); ++ fprintf(fout,"[..]"); ++ break; + case Fcall: + printnode(fout,x); + fprintf(fout,"("); +diff -ru gp2c-0.0.9pl3/src/topfunc.c gp2c/src/topfunc.c +--- gp2c-0.0.9pl3/src/topfunc.c 2014-12-16 10:53:09.000000000 +0100 ++++ gp2c/src/topfunc.c 2015-07-09 14:50:05.606098645 +0200 +@@ -109,7 +109,7 @@ + int nn = newanon(); + int nf = newnode(Fdeffunc,newnode(Ffunction,nn,x),y); + int seq = newnode(Fentry,nn,-1); +- topfunc(n,p,fun,pfun,nf,wr); ++ topfunc(nf,p,fun,pfun,nf,wr); + if (fun>=0) tree[n] = tree[seq]; + } + +diff -ru gp2c-0.0.9pl3/test2/gp/apply.gp gp2c/test2/gp/apply.gp +--- gp2c-0.0.9pl3/test2/gp/apply.gp 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/gp/apply.gp 2015-07-09 14:50:05.609098682 +0200 +@@ -3,6 +3,7 @@ + f3(f,v)=apply(f,v) + g(x)=x^2+1 + f4(v)=apply(g,v) ++f5(z:small)=apply(n:small->(n+z)!,[1,2,3,4,5]) + + g1(v,z)=select(x->x>z,v) + g2(v)=select(isprime,v) +diff -ru gp2c-0.0.9pl3/test2/input/apply gp2c/test2/input/apply +--- gp2c-0.0.9pl3/test2/input/apply 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/input/apply 2015-07-09 14:50:05.609098682 +0200 +@@ -2,6 +2,7 @@ + f2([1,2,3,4]) + f3(sqr,[1,2,3,4]) + f4([1,2,3,4]) ++f5(-1) + + g1([1,2,3,4],2) + g2([1,2,3,4]) +diff -ru gp2c-0.0.9pl3/test2/res/apply.res gp2c/test2/res/apply.res +--- gp2c-0.0.9pl3/test2/res/apply.res 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/res/apply.res 2015-07-09 14:50:05.610098694 +0200 +@@ -2,6 +2,7 @@ + [1, 4, 9, 16] + [1, 4, 9, 16] + [2, 5, 10, 17] ++[1, 1, 2, 6, 24] + [3, 4] + [2, 3] + [2, 3] diff --git a/build/pkgs/gp2c/patches/global.patch b/build/pkgs/gp2c/patches/global.patch new file mode 100644 index 00000000000..dd0c24d0472 --- /dev/null +++ b/build/pkgs/gp2c/patches/global.patch @@ -0,0 +1,126 @@ +Patch taken from upstream to fix global() + +diff --git a/src/genblock.c b/src/genblock.c +index a3582d2..e626e0f 100644 +--- a/src/genblock.c ++++ b/src/genblock.c +@@ -251,7 +251,11 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + /*Make sure GEN objects are gerepilable.*/ + val = newnode(Ftag, newnode(Ftag, newsmall(0), Ggen), tv); + if (decl==global) +- fillvar(var,flag,tv,val); ++ { ++ int f=fillvar(var,flag,tv,-1); ++ if (autogc) ++ affectval(f,val,seq); ++ } + else + pushvar(var,flag,tv,val); + break; +@@ -270,10 +274,12 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + val=newcall("_const_quote",newstringnode(entryname(stack[i]),-1)); + affectval(fillvar(stack[i],flag,mint,-1),val,seq); + } +- else if (autogc) ++ else + { ++ int f = fillvar(stack[i],flag,mint,-1); + /*Make sure (implicitly GEN) objects are gerepilable.*/ +- fillvar(stack[i],flag,mint, newsmall(0)); ++ if (autogc) ++ affectval(f, newsmall(0), seq); + } + break; + case function: +diff --git a/src/topfunc.c b/src/topfunc.c +index 872830e..bff787f 100644 +--- a/src/topfunc.c ++++ b/src/topfunc.c +@@ -154,13 +154,12 @@ static int topfuncproto(int n, int fun, int pfun, int nf) + break; + case 'I': + case 'E': +- if (wr>=-1) +- { +- case 'J': +- seq = newnode(Flambda,var,newleaf(a)); +- tree[a] = tree[seq]; +- topfunclambda(a, n, fun, pfun, wr); +- } ++ if (wr<-1) ++ break; ++ case 'J': /* Fall through */ ++ seq = newnode(Flambda,var,newleaf(a)); ++ tree[a] = tree[seq]; ++ topfunclambda(a, n, fun, pfun, wr); + break; + } + break; +diff --git a/src/toplevel.c b/src/toplevel.c +index f5999c4..23c92a8 100644 +--- a/src/toplevel.c ++++ b/src/toplevel.c +@@ -120,7 +120,7 @@ void gentoplevel(int n) + } + else + gentoplevel(x); +- if (y>=0 && tree[y].f!=Fdeffunc) ++ if (y>=0 && tree[y].f!=Fdeffunc && tree[y].f!=Fseq) + { + int dy = detag(y); + if (isfunc(dy,"global")) +diff --git a/test/gp/initfunc.gp b/test/gp/initfunc.gp +index 03c1dd3..ed0b87d 100644 +--- a/test/gp/initfunc.gp ++++ b/test/gp/initfunc.gp +@@ -1,4 +1,4 @@ +-global(y:var,n:small=0,m:small,k=2,L:vec,T2) ++global(y:var='y,n:small=0,m:small,k=2,L:vec,T2) + T=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + T2=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + L=[]~; +@@ -15,3 +15,10 @@ aff()=print(n," ",m); + M=[1,2;3,4] + print(x^4+k); + pow_M(k)=M^k ++global(x1:int,x2:int); ++global(y1,y2):int; ++global(z1,z2); ++f1(x)=my(z=x^2+1);if(z==1,x1=x,x2=x);z ++f2(x)=my(z=x^2+1);if(z==1,y1=x,y2=x);z ++f3(x)=my(z=x^2+1);if(z==1,z1=x,z2=x);z ++global(t1);t1=0 +diff --git a/test/input/initfunc b/test/input/initfunc +index 8506d08..46a126d 100644 +--- a/test/input/initfunc ++++ b/test/input/initfunc +@@ -2,3 +2,9 @@ init_initfunc(); print(pow_M(2)) + vector(100,i,mybfffo(i)) + vector(31,i,mybfffo(1<<(i-1))) + for(i=1,5,count();aff()) ++init_initfunc();f1(0) ++init_initfunc();f1(1) ++init_initfunc();f2(0) ++init_initfunc();f2(1) ++init_initfunc();f3(0) ++init_initfunc();f3(1) +diff --git a/test/res/initfunc.res b/test/res/initfunc.res +index 391d09d..a1e538c 100644 +--- a/test/res/initfunc.res ++++ b/test/res/initfunc.res +@@ -13,3 +13,15 @@ x^4 + 2 + 3 30 + 4 30 + 5 29 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 diff --git a/build/pkgs/gp2c/spkg-check b/build/pkgs/gp2c/spkg-check new file mode 100755 index 00000000000..9adcda86f90 --- /dev/null +++ b/build/pkgs/gp2c/spkg-check @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && $MAKE check diff --git a/build/pkgs/gp2c/spkg-install b/build/pkgs/gp2c/spkg-install new file mode 100755 index 00000000000..0346d362ada --- /dev/null +++ b/build/pkgs/gp2c/spkg-install @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo "SAGE_LOCAL undefined ... exiting" + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +set -e + +./configure --prefix="$SAGE_LOCAL" --with-paricfg="$SAGE_LOCAL/lib/pari/pari.cfg" + +$MAKE install diff --git a/build/pkgs/gp2c/type b/build/pkgs/gp2c/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/gp2c/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/graphs/SPKG.txt b/build/pkgs/graphs/SPKG.txt index e959d94a449..b9076656dc3 100644 --- a/build/pkgs/graphs/SPKG.txt +++ b/build/pkgs/graphs/SPKG.txt @@ -5,14 +5,20 @@ A database of graphs. Created by Emily Kirkman based on the work of Jason Grout. Since April 2012 it also contains the ISGCI graph database. -== SPKG Maintainers == +== Upstream Contact == - * R. Andrew Ohana - * Nathann Cohen (part-time since march 2012) + * For ISGCI: + + H.N. de Ridder (hnridder@graphclasses.org) -== Upstream Contact == + * For Andries Brouwer's database: - * For ISGCI : H.N. de Ridder (hnridder@graphclasses.org) + The data is taken from from Andries E. Brouwer's website + (http://www.win.tue.nl/~aeb/). Anything related to the data should be + reported to him directly (aeb@cwi.nl) + + The code used to parse the data and create the .json file is available at + https://github.com/nathanncohen/strongly_regular_graphs_database. == Dependencies == @@ -20,6 +26,9 @@ N/A == Changelog == +== graphs-20150724.p6 (Nathann Cohen, 2013-07-24 == + * Added the database on strongly regular graphs + == graphs-20130920.p5 (Nathann Cohen, 2013-09-20) == * #14396: Update of isgci_sage.xml, added smallgraphs.txt diff --git a/build/pkgs/graphs/checksums.ini b/build/pkgs/graphs/checksums.ini index 8978e68f0ee..f4b488d0bb9 100644 --- a/build/pkgs/graphs/checksums.ini +++ b/build/pkgs/graphs/checksums.ini @@ -1,4 +1,4 @@ tarball=graphs-VERSION.tar.bz2 -sha1=f78fa61daf3baafa088921f5e578ea19f556f963 -md5=2621b6cfd61797ab0be179653d7bcf3e -cksum=2747417664 +sha1=252564f5ef2b5ba020dc981523dda725046df72a +md5=261a521fcdcab90a52992b166efc9293 +cksum=668305216 diff --git a/build/pkgs/graphs/dependencies b/build/pkgs/graphs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/graphs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/graphs/package-version.txt b/build/pkgs/graphs/package-version.txt index fbae6e7f7f5..a917e053749 100644 --- a/build/pkgs/graphs/package-version.txt +++ b/build/pkgs/graphs/package-version.txt @@ -1 +1 @@ -20130920.p5 +20150724.p6 diff --git a/build/pkgs/gsl/SPKG.txt b/build/pkgs/gsl/SPKG.txt index f67bff2867d..db2aab7b5fd 100644 --- a/build/pkgs/gsl/SPKG.txt +++ b/build/pkgs/gsl/SPKG.txt @@ -18,10 +18,6 @@ is run. * GPL V3 -== SPKG Maintainers == - - * William Stein - == Upstream Contact == * http://www.gnu.org/software/gsl/ diff --git a/build/pkgs/guppy/type b/build/pkgs/guppy/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/guppy/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/iconv/SPKG.txt b/build/pkgs/iconv/SPKG.txt index 9e1aa0f634e..3ae0547a38a 100644 --- a/build/pkgs/iconv/SPKG.txt +++ b/build/pkgs/iconv/SPKG.txt @@ -7,9 +7,6 @@ languages, with different characters to be handled properly. == License == * GPL 3 and LGPL 3. So we can safely link against the library in Sage. -== SPKG Maintainers == - * David Kirkby - == Upstream Contact == * http://www.gnu.org/software/libiconv/ * Bug reports to bug-gnu-libiconv@gnu.org diff --git a/build/pkgs/iconv/dependencies b/build/pkgs/iconv/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/iconv/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/igraph/SPKG.txt b/build/pkgs/igraph/SPKG.txt new file mode 100644 index 00000000000..e6ea91c464a --- /dev/null +++ b/build/pkgs/igraph/SPKG.txt @@ -0,0 +1,23 @@ += igraph = + +== Description == + +igraph is a library for creating and manipulating graphs. +It is intended to be as powerful (ie. fast) as possible to enable the +analysis of large graphs. + +== License == + +GPL version 2 + +== Upstream Contact == + +http://igraph.org/c/ + +== Dependencies == + +* python +* readline +* gcc + +== Special Update/Build Instructions == diff --git a/build/pkgs/igraph/checksums.ini b/build/pkgs/igraph/checksums.ini new file mode 100644 index 00000000000..f826586c8c6 --- /dev/null +++ b/build/pkgs/igraph/checksums.ini @@ -0,0 +1,4 @@ +tarball=igraph-VERSION.tar.gz +sha1=2cf3528a60c52810a3d5ed9f117692f8f639aac1 +md5=4f6e7c16b45fce8ed423516a9786e4e8 +cksum=2372626349 diff --git a/build/pkgs/igraph/package-version.txt b/build/pkgs/igraph/package-version.txt new file mode 100644 index 00000000000..7deb86fee42 --- /dev/null +++ b/build/pkgs/igraph/package-version.txt @@ -0,0 +1 @@ +0.7.1 \ No newline at end of file diff --git a/build/pkgs/igraph/spkg-check b/build/pkgs/igraph/spkg-check new file mode 100644 index 00000000000..35f04fc8862 --- /dev/null +++ b/build/pkgs/igraph/spkg-check @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd src +$MAKE check diff --git a/build/pkgs/igraph/spkg-install b/build/pkgs/igraph/spkg-install new file mode 100755 index 00000000000..5d0ae627922 --- /dev/null +++ b/build/pkgs/igraph/spkg-install @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +if gcc -lxml2 2>&1 | grep -q 'cannot find -lxml2'; then + echo " +You need libxml2 to run igraph. On Ubuntu and Debian Linux, installing the build-essential and the libxml2-dev packages is sufficient." + exit 1 +fi + +cd src + +./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" +if [ $? -ne 0 ]; then + echo >&2 "Error configuring igraph." + exit 1 +fi + +$MAKE +if [ $? -ne 0 ]; then + echo >&2 "Error building igraph." + exit 1 +fi + +$MAKE -j1 install +if [ $? -ne 0 ]; then + echo >&2 "Error installing igraph." + exit 1 +fi diff --git a/build/pkgs/igraph/type b/build/pkgs/igraph/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/igraph/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/ipykernel/SPKG.txt b/build/pkgs/ipykernel/SPKG.txt new file mode 100644 index 00000000000..de67f28d0b2 --- /dev/null +++ b/build/pkgs/ipykernel/SPKG.txt @@ -0,0 +1,7 @@ += ipykernel = + +== Description == + +IPython Kernel for Jupyter + +This package provides the IPython kernel for Jupyter. diff --git a/build/pkgs/ipykernel/checksums.ini b/build/pkgs/ipykernel/checksums.ini new file mode 100644 index 00000000000..7c68b0f4ba3 --- /dev/null +++ b/build/pkgs/ipykernel/checksums.ini @@ -0,0 +1,4 @@ +tarball=ipykernel-VERSION.tar.gz +sha1=53dc62e7cfd7582df4dc0a7d0da2463fcf294329 +md5=86b6a392e5ca727284220ea0034ec4d5 +cksum=55668233 diff --git a/build/pkgs/ipykernel/dependencies b/build/pkgs/ipykernel/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/ipykernel/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ipykernel/package-version.txt b/build/pkgs/ipykernel/package-version.txt new file mode 100644 index 00000000000..8539bce652f --- /dev/null +++ b/build/pkgs/ipykernel/package-version.txt @@ -0,0 +1 @@ +4.0.3.p0 diff --git a/build/pkgs/ipykernel/patches/sigint.patch b/build/pkgs/ipykernel/patches/sigint.patch new file mode 100644 index 00000000000..6b6b670d753 --- /dev/null +++ b/build/pkgs/ipykernel/patches/sigint.patch @@ -0,0 +1,45 @@ +From 443f56f982fb678041c0a9dfb85348e6d646053b Mon Sep 17 00:00:00 2001 +From: Jeroen Demeyer +Date: Fri, 4 Sep 2015 16:24:28 +0200 +Subject: [PATCH] Abstract signal handler changes in hooks + +--- + ipykernel/kernelbase.py | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py +index a7869f4..07546e6 100644 +--- a/ipykernel/kernelbase.py ++++ b/ipykernel/kernelbase.py +@@ -206,20 +206,28 @@ def dispatch_shell(self, stream, msg): + if handler is None: + self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type) + else: +- # ensure default_int_handler during handler call +- sig = signal(SIGINT, default_int_handler) + self.log.debug("%s: %s", msg_type, msg) ++ self.pre_handler_hook() + try: + handler(stream, idents, msg) + except Exception: + self.log.error("Exception in message handler:", exc_info=True) + finally: +- signal(SIGINT, sig) ++ self.post_handler_hook() + + sys.stdout.flush() + sys.stderr.flush() + self._publish_status(u'idle') + ++ def pre_handler_hook(self): ++ """Hook to execute before calling message handler""" ++ # ensure default_int_handler during handler call ++ self.saved_sigint_handler = signal(SIGINT, default_int_handler) ++ ++ def post_handler_hook(self): ++ """Hook to execute after calling message handler""" ++ signal(SIGINT, self.saved_sigint_handler) ++ + def enter_eventloop(self): + """enter eventloop""" + self.log.info("entering eventloop %s", self.eventloop) diff --git a/build/pkgs/ipykernel/spkg-install b/build/pkgs/ipykernel/spkg-install new file mode 100755 index 00000000000..1836e0e425b --- /dev/null +++ b/build/pkgs/ipykernel/spkg-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +python setup.py install diff --git a/build/pkgs/ipykernel/type b/build/pkgs/ipykernel/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/ipykernel/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/ipython/SPKG.txt b/build/pkgs/ipython/SPKG.txt index 39d5b16f375..c342a637a18 100644 --- a/build/pkgs/ipython/SPKG.txt +++ b/build/pkgs/ipython/SPKG.txt @@ -19,11 +19,6 @@ IPython is a multiplatform, Free Software project (BSD licensed) that offers: BSD -== SPKG Maintainers == - - * Jason Grout - * William Stein - == Upstream Contact == http://ipython.scipy.org/ @@ -31,12 +26,3 @@ http://ipython.scipy.org/ ipython-dev@scipy.org ipython-user@scipy.org - -== Dependencies == - - * Python - -== Patches == - -* github_pr_5874.patch: https://github.com/ipython/ipython/pull/5874 - diff --git a/build/pkgs/ipython/checksums.ini b/build/pkgs/ipython/checksums.ini index 7f9117902ef..785d210c674 100644 --- a/build/pkgs/ipython/checksums.ini +++ b/build/pkgs/ipython/checksums.ini @@ -1,4 +1,4 @@ tarball=ipython-VERSION.tar.gz -sha1=a77731d25519a0f76cd9aab2dcadaa555cbcc04c -md5=41aa9b34f39484861e77c46ffb29b699 -cksum=3783479410 +sha1=26fa227053cf1b0387459a72b53b1818140831f7 +md5=c2fecbcf1c0fbdc82625c77a50733dd6 +cksum=2156781632 diff --git a/build/pkgs/ipython/dependencies b/build/pkgs/ipython/dependencies index 1a644804181..7591607254f 100644 --- a/build/pkgs/ipython/dependencies +++ b/build/pkgs/ipython/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) $(INST)/$(PYZMQ) +$(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) $(INST)/$(PYZMQ) $(INST)/$(PICKLESHARE) $(INST)/$(SIMPLEGENERIC) $(INST)/$(TRAITLETS) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/ipython/package-version.txt b/build/pkgs/ipython/package-version.txt index 944880fa15e..fcdb2e109f6 100644 --- a/build/pkgs/ipython/package-version.txt +++ b/build/pkgs/ipython/package-version.txt @@ -1 +1 @@ -3.2.0 +4.0.0 diff --git a/build/pkgs/ipython/patches/catch_termios_error.patch b/build/pkgs/ipython/patches/catch_termios_error.patch deleted file mode 100644 index 475beed4118..00000000000 --- a/build/pkgs/ipython/patches/catch_termios_error.patch +++ /dev/null @@ -1,23 +0,0 @@ -See https://github.com/ipython/ipython/pull/8208 - - Catch termios.error in pager - - This error is raised by termios.tcgetattr on really old Linux versions - (here: Ubuntu 8.04.4) in doctests, that is, without a real stdout. - -diff --git a/IPython/core/page.py b/IPython/core/page.py -index 2a0305e..067b540 100644 ---- a/IPython/core/page.py -+++ b/IPython/core/page.py -@@ -100,7 +100,10 @@ def _detect_screen_size(screen_lines_def): - # flags each time), we just save the initial terminal state and - # unconditionally reset it every time. It's cheaper than making - # the checks. -- term_flags = termios.tcgetattr(sys.stdout) -+ try: -+ term_flags = termios.tcgetattr(sys.stdout) -+ except termios.error as err: -+ raise TypeError - - # Curses modifies the stdout buffer size by default, which messes - # up Python's normal stdout buffering. This would manifest itself diff --git a/build/pkgs/ipython/spkg-install b/build/pkgs/ipython/spkg-install index 17777345ab2..bdf8bc0750b 100755 --- a/build/pkgs/ipython/spkg-install +++ b/build/pkgs/ipython/spkg-install @@ -46,10 +46,3 @@ if python -c 'import sys; sys.exit(sys.version_info.major != 3)'; then rm -f ipython ln -s ipython3 ipython fi - -# let ipython use mathjax -cd "$SAGE_LOCAL/lib/python/site-packages/IPython/html/static" -rm -rf mathjax -ln -s ../../../../../../share/mathjax/ mathjax - - diff --git a/build/pkgs/ipython_genutils/SPKG.txt b/build/pkgs/ipython_genutils/SPKG.txt new file mode 100644 index 00000000000..e6b7acbe301 --- /dev/null +++ b/build/pkgs/ipython_genutils/SPKG.txt @@ -0,0 +1,5 @@ += ipython_genutils = + +== Description == + +Vestigial utilities from IPython diff --git a/build/pkgs/ipython_genutils/checksums.ini b/build/pkgs/ipython_genutils/checksums.ini new file mode 100644 index 00000000000..bbfc37f1d9c --- /dev/null +++ b/build/pkgs/ipython_genutils/checksums.ini @@ -0,0 +1,4 @@ +tarball=ipython_genutils-VERSION.tar.gz +sha1=bbabe9a5935e7b7cb6d57d4279763756420ed09b +md5=9a8afbe0978adbcbfcb3b35b2d015a56 +cksum=3663270374 diff --git a/build/pkgs/ipython_genutils/dependencies b/build/pkgs/ipython_genutils/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/ipython_genutils/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ipython_genutils/package-version.txt b/build/pkgs/ipython_genutils/package-version.txt new file mode 100644 index 00000000000..6e8bf73aa55 --- /dev/null +++ b/build/pkgs/ipython_genutils/package-version.txt @@ -0,0 +1 @@ +0.1.0 diff --git a/build/pkgs/ipython_genutils/spkg-install b/build/pkgs/ipython_genutils/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/ipython_genutils/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/ipython_genutils/type b/build/pkgs/ipython_genutils/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/ipython_genutils/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/ipywidgets/SPKG.txt b/build/pkgs/ipywidgets/SPKG.txt new file mode 100644 index 00000000000..7fd95c0eddd --- /dev/null +++ b/build/pkgs/ipywidgets/SPKG.txt @@ -0,0 +1,5 @@ += ipywidgets = + +== Description == + +Interactive HTML widgets for Jupyter notebooks and the IPython kernel. diff --git a/build/pkgs/ipywidgets/checksums.ini b/build/pkgs/ipywidgets/checksums.ini new file mode 100644 index 00000000000..adc4eb03de7 --- /dev/null +++ b/build/pkgs/ipywidgets/checksums.ini @@ -0,0 +1,4 @@ +tarball=ipywidgets-VERSION.tar.gz +sha1=3e9f86e670fd41feb7a597088ab023471e668962 +md5=8c03ee965b91a8f626d5505b41af8031 +cksum=3467449425 diff --git a/build/pkgs/ipywidgets/dependencies b/build/pkgs/ipywidgets/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/ipywidgets/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ipywidgets/package-version.txt b/build/pkgs/ipywidgets/package-version.txt new file mode 100644 index 00000000000..4d54daddb61 --- /dev/null +++ b/build/pkgs/ipywidgets/package-version.txt @@ -0,0 +1 @@ +4.0.2 diff --git a/build/pkgs/ipywidgets/spkg-install b/build/pkgs/ipywidgets/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/ipywidgets/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/ipywidgets/type b/build/pkgs/ipywidgets/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/ipywidgets/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/jinja2/SPKG.txt b/build/pkgs/jinja2/SPKG.txt index 8771248ecfa..96b65425ac3 100644 --- a/build/pkgs/jinja2/SPKG.txt +++ b/build/pkgs/jinja2/SPKG.txt @@ -15,12 +15,6 @@ environments. Modified BSD License -== SPKG Maintainers == - - * Tim Dumol - * John Palmieri - * Mike Hansen - == Upstream Contact == Author: Pocoo Team diff --git a/build/pkgs/jmol/SPKG.txt b/build/pkgs/jmol/SPKG.txt index 1b0030c1fb2..14e4a49328d 100644 --- a/build/pkgs/jmol/SPKG.txt +++ b/build/pkgs/jmol/SPKG.txt @@ -11,10 +11,6 @@ library jmol_lib.js or changes to Notebook or Sage code. GPLv2+ -== SPKG Maintainers == - - * Jonathan Gutow - == Upstream Contact == * Jonathan Gutow diff --git a/build/pkgs/jupyter_client/SPKG.txt b/build/pkgs/jupyter_client/SPKG.txt new file mode 100644 index 00000000000..3fedb1e4efd --- /dev/null +++ b/build/pkgs/jupyter_client/SPKG.txt @@ -0,0 +1,12 @@ += jupyter_client = + +== Description == + +Jupyter protocol implementation and client libraries + +jupyter_client contains the reference implementation of the Jupyter +protocol. It also provides client and kernel management APIs for working +with kernels. + +It also provides the jupyter kernelspec entrypoint for installing +kernelspecs for use with Jupyter frontends. diff --git a/build/pkgs/jupyter_client/checksums.ini b/build/pkgs/jupyter_client/checksums.ini new file mode 100644 index 00000000000..a11dab18918 --- /dev/null +++ b/build/pkgs/jupyter_client/checksums.ini @@ -0,0 +1,4 @@ +tarball=jupyter_client-VERSION.tar.gz +sha1=ac279b6aea424b793e2a13c59c37b21dc05b535c +md5=11ec05c5356c86ea3fe6b6008732874c +cksum=145697732 diff --git a/build/pkgs/jupyter_client/dependencies b/build/pkgs/jupyter_client/dependencies new file mode 100644 index 00000000000..e48e827d895 --- /dev/null +++ b/build/pkgs/jupyter_client/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) $(INST)/$(JUPYTER_CORE) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/jupyter_client/package-version.txt b/build/pkgs/jupyter_client/package-version.txt new file mode 100644 index 00000000000..fcdb2e109f6 --- /dev/null +++ b/build/pkgs/jupyter_client/package-version.txt @@ -0,0 +1 @@ +4.0.0 diff --git a/build/pkgs/jupyter_client/spkg-install b/build/pkgs/jupyter_client/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/jupyter_client/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/jupyter_client/type b/build/pkgs/jupyter_client/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/jupyter_client/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/jupyter_core/SPKG.txt b/build/pkgs/jupyter_core/SPKG.txt new file mode 100644 index 00000000000..dbed147e7af --- /dev/null +++ b/build/pkgs/jupyter_core/SPKG.txt @@ -0,0 +1,5 @@ += jupyter_core = + +== Description == + +Jupyter core package. A base package on which Jupyter projects rely. diff --git a/build/pkgs/jupyter_core/checksums.ini b/build/pkgs/jupyter_core/checksums.ini new file mode 100644 index 00000000000..5a8c1115dff --- /dev/null +++ b/build/pkgs/jupyter_core/checksums.ini @@ -0,0 +1,4 @@ +tarball=jupyter_core-VERSION.tar.gz +sha1=cfa33cb28883c3547133949c69af532b02866d28 +md5=1d24d79414b8ec18216bf490e101313a +cksum=1145384470 diff --git a/build/pkgs/jupyter_core/dependencies b/build/pkgs/jupyter_core/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/jupyter_core/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/jupyter_core/package-version.txt b/build/pkgs/jupyter_core/package-version.txt new file mode 100644 index 00000000000..c5106e6d139 --- /dev/null +++ b/build/pkgs/jupyter_core/package-version.txt @@ -0,0 +1 @@ +4.0.4 diff --git a/build/pkgs/jupyter_core/spkg-install b/build/pkgs/jupyter_core/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/jupyter_core/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/jupyter_core/type b/build/pkgs/jupyter_core/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/jupyter_core/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/lcalc/SPKG.txt b/build/pkgs/lcalc/SPKG.txt index 8a4635066d4..2958ec6d12c 100644 --- a/build/pkgs/lcalc/SPKG.txt +++ b/build/pkgs/lcalc/SPKG.txt @@ -8,10 +8,6 @@ Michael Rubinstein's L-function calculator. * LGPL V2+ -== SPKG Maintainers == - - * Rishikesh - == Upstream contact == Michael Rubinstein diff --git a/build/pkgs/lcalc/spkg-install b/build/pkgs/lcalc/spkg-install index d1ff0c5c5e4..50bd1f97752 100755 --- a/build/pkgs/lcalc/spkg-install +++ b/build/pkgs/lcalc/spkg-install @@ -78,4 +78,9 @@ success "$MAKE" echo "Now installing lcalc binary, library and header files..." rm -fr "$SAGE_LOCAL"/include/libLfunction $MAKE install + +if [ "$UNAME" = "Darwin" ]; then + install_name_tool -id ${SAGE_LOCAL}/lib/libLfunction.dylib "${SAGE_LOCAL}"/lib/libLfunction.dylib +fi + success "$MAKE install" diff --git a/build/pkgs/libfplll/SPKG.txt b/build/pkgs/libfplll/SPKG.txt index 7e3a89ac2ab..7b44e38e189 100644 --- a/build/pkgs/libfplll/SPKG.txt +++ b/build/pkgs/libfplll/SPKG.txt @@ -10,9 +10,6 @@ Website: http://perso.ens-lyon.fr/damien.stehle/fplll.html == License == * LGPL V2.1+ -== SPKG Maintainers == - * Martin Albrecht - == Upstream Contact == * Damien Stehlé (damien.stehle@ens-lyon.fr) diff --git a/build/pkgs/libfplll/package-version.txt b/build/pkgs/libfplll/package-version.txt index c5106e6d139..4e1ad0564c8 100644 --- a/build/pkgs/libfplll/package-version.txt +++ b/build/pkgs/libfplll/package-version.txt @@ -1 +1 @@ -4.0.4 +4.0.4.p0 diff --git a/build/pkgs/libfplll/spkg-install b/build/pkgs/libfplll/spkg-install index 12eb5ab03a0..06ce10cc57e 100755 --- a/build/pkgs/libfplll/spkg-install +++ b/build/pkgs/libfplll/spkg-install @@ -58,7 +58,7 @@ export CC export CXX echo "Now configuring libfplll..." -./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" --includedir="$SAGE_LOCAL/include/fplll" +./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" if [ $? -ne 0 ]; then echo "Error configuring libfplll" exit 1 diff --git a/build/pkgs/libgap/SPKG.txt b/build/pkgs/libgap/SPKG.txt index 584e9696e15..fd95cb5d3d5 100644 --- a/build/pkgs/libgap/SPKG.txt +++ b/build/pkgs/libgap/SPKG.txt @@ -8,10 +8,6 @@ This is the GPL v3+ -== SPKG Maintainers == - -* Volker Braun - == Upstream Contact == Volker Braun diff --git a/build/pkgs/libogg/SPKG.txt b/build/pkgs/libogg/SPKG.txt index 3db5ad12872..7f0011fa9d5 100644 --- a/build/pkgs/libogg/SPKG.txt +++ b/build/pkgs/libogg/SPKG.txt @@ -39,10 +39,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -== SPKG Maintainers == - - * Wilfried Huss - == Upstream Contact == The Xiph.org mailing lists - see http://lists.xiph.org/mailman/listinfo diff --git a/build/pkgs/libogg/dependencies b/build/pkgs/libogg/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/libogg/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libpng/SPKG.txt b/build/pkgs/libpng/SPKG.txt index 9ff08b3f211..9ef5facecad 100644 --- a/build/pkgs/libpng/SPKG.txt +++ b/build/pkgs/libpng/SPKG.txt @@ -15,10 +15,6 @@ Website: http://www.libpng.org/pub/png/libpng.html The libpng license - see http://www.libpng.org/pub/png/src/libpng-LICENSE.txt -== SPKG Maintainers == - - * William Stein - == Upstream Contact == The png mailing lists - see http://www.libpng.org/pub/png/pngmisc.html#lists diff --git a/build/pkgs/libtheora/SPKG.txt b/build/pkgs/libtheora/SPKG.txt index bcfb137b74f..9526cf4fb1d 100644 --- a/build/pkgs/libtheora/SPKG.txt +++ b/build/pkgs/libtheora/SPKG.txt @@ -38,10 +38,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -== SPKG Maintainers == - - * Wilfried Huss - == Upstream Contact == The Xiph.org mailing lists - see http://lists.xiph.org/mailman/listinfo diff --git a/build/pkgs/lidia/SPKG.txt b/build/pkgs/lidia/SPKG.txt index 97d82122652..54d712d6998 100644 --- a/build/pkgs/lidia/SPKG.txt +++ b/build/pkgs/lidia/SPKG.txt @@ -16,10 +16,6 @@ lidia is released under the GPL, or so it is claimed. See https://groups.google.com/forum/#!msg/sage-devel/kTxgPSqrbUM/5Txj3_IKhlQJ and https://lists.debian.org/debian-legal/2007/07/msg00120.html -== SPKG Maintainers == - -Matthias Köppe - == Upstream Contact == Matthias Köppe, UC Davis, CA, USA diff --git a/build/pkgs/lie/SPKG.txt b/build/pkgs/lie/SPKG.txt new file mode 100644 index 00000000000..3d77299997b --- /dev/null +++ b/build/pkgs/lie/SPKG.txt @@ -0,0 +1,48 @@ += LiE = + +== Description == + +LiE is the name of a software package that enables mathematicians and +physicists to perform computations of a Lie group theoretic nature. It +focuses on the representation theory of complex semisimple (reductive) +Lie groups and algebras, and on the structure of their Weyl groups and +root systems. + +LiE does not compute directly with elements of the Lie groups and +algebras themselves; it rather computes with weights, roots, characters +and similar objects. Some specialities of LiE are: tensor product +decompositions, branching to subgroups, Weyl group orbits, reduced +elements in Weyl groups, distinguished coset representatives and much +more. These operations have been compiled into the program which results +in fast execution: typically one or two orders of magnitude faster than +similar programs written in a general purpose program. + +The LiE programming language makes it possible to customise and extend +the package with more mathematical functions. A user manual is provided +containing many examples. + +LiE establishes an interactive environment from which commands can be +given that involve basic programming primitives and powerful built-in +functions. These commands are read by an interpreter built into the +package and passed to the core of the system. This core consists of +programs representing some 100 mathematical functions. The interpreter +offers on-line facilities which explain operations and functions, and +which give background information about Lie group theoretical concepts +and about currently valid definitions and values. + +(from http://www-math.univ-poitiers.fr/~maavl/LiE/description.html ) + +== License == + +GNU Lesser General Public License (LGPL), version unspecified + +== Upstream Contact == + + * Marc van Leeuwen, http://www-math.univ-poitiers.fr/~maavl/ + +== Dependencies == + + * readline + * ncurses + * bison (not included in this package or in Sage!) + diff --git a/build/pkgs/lie/checksums.ini b/build/pkgs/lie/checksums.ini new file mode 100644 index 00000000000..83fede0fa5f --- /dev/null +++ b/build/pkgs/lie/checksums.ini @@ -0,0 +1,4 @@ +tarball=lie-VERSION.tar.gz +sha1=bf8d2303c02738cfd2c899332d736b5364322a5d +md5=2e98f252364d43557a322ea7eb677944 +cksum=3838739617 diff --git a/build/pkgs/lie/dependencies b/build/pkgs/lie/dependencies new file mode 100644 index 00000000000..313ba28c58a --- /dev/null +++ b/build/pkgs/lie/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(READLINE) $(INST)/$(NCURSES) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/lie/package-version.txt b/build/pkgs/lie/package-version.txt new file mode 100644 index 00000000000..b1b25a5ffae --- /dev/null +++ b/build/pkgs/lie/package-version.txt @@ -0,0 +1 @@ +2.2.2 diff --git a/build/pkgs/lie/patches/00-string.h.patch b/build/pkgs/lie/patches/00-string.h.patch new file mode 100644 index 00000000000..ce9417ca7e9 --- /dev/null +++ b/build/pkgs/lie/patches/00-string.h.patch @@ -0,0 +1,23 @@ +diff -ur a/box/closure.c b/box/closure.c +--- a/box/closure.c 2005-03-29 01:48:27.000000000 -0800 ++++ b/box/closure.c 2007-01-30 12:10:54.000000000 -0800 +@@ -1,6 +1,6 @@ + #include "lie.h" + #define local static +-#include ++/* #include */ + #define two_lengths(type) (strchr("BCFG",type)!=NULL) + #define grp_less(x,y) \ + ((x)->lietype<(y)->lietype \ +diff -ur a/lie.h b/lie.h +--- a/lie.h 2005-03-29 01:48:27.000000000 -0800 ++++ b/lie.h 2007-01-30 12:10:40.000000000 -0800 +@@ -31,7 +31,7 @@ + + #include + #include +-#include ++/* #include */ + #include + #include + #include diff --git a/build/pkgs/lie/patches/01-libs.patch b/build/pkgs/lie/patches/01-libs.patch new file mode 100644 index 00000000000..9ed20b6bf64 --- /dev/null +++ b/build/pkgs/lie/patches/01-libs.patch @@ -0,0 +1,12 @@ +diff -ur a/Makefile b/Makefile +--- a/Makefile 2000-04-07 00:46:28.000000000 -0700 ++++ b/Makefile 2007-05-20 23:10:43.000000000 -0700 +@@ -88,7 +88,7 @@ + $(CC) -ansi -c -o gapdate.o date.c + + Lie.exe: date.o +- $(CC) -o Lie.exe $(objects) date.o static/*.o box/*.o -lreadline ++ $(CC) -o Lie.exe $(objects) date.o static/*.o box/*.o -lreadline -lncurses + chmod g+w Lie.exe + Liegap.exe: gapdate.o + $(CC) -o Liegap.exe $(GAP_objects) gapdate.o static/*.o box/*.o diff --git a/build/pkgs/lie/patches/02-hashbang.patch b/build/pkgs/lie/patches/02-hashbang.patch new file mode 100644 index 00000000000..19d7c181f51 --- /dev/null +++ b/build/pkgs/lie/patches/02-hashbang.patch @@ -0,0 +1,8 @@ +diff -ur a/lie_script b/lie_script +--- a/lie_script 2012-05-24 15:48:05.763203064 -0700 ++++ b/lie_script 2012-05-24 15:48:24.339275579 -0700 +@@ -1,2 +1,4 @@ ++#!/bin/sh ++ + LD=actual directory gets substituted here + exec $LD/Lie.exe initfile $LD diff --git a/build/pkgs/lie/patches/03-solaris.patch b/build/pkgs/lie/patches/03-solaris.patch new file mode 100644 index 00000000000..7dc48957d72 --- /dev/null +++ b/build/pkgs/lie/patches/03-solaris.patch @@ -0,0 +1,12 @@ +diff -ur a/Makefile b/Makefile +--- a/Makefile 2000-04-07 00:46:28.000000000 -0700 ++++ b/Makefile 2007-05-20 23:10:43.000000000 -0700 +@@ -88,7 +88,7 @@ + $(CC) -ansi -c -o gapdate.o date.c + + Lie.exe: date.o +- $(CC) -o Lie.exe $(objects) date.o static/*.o box/*.o -lreadline -lncurses ++ $(CC) -o Lie.exe $(objects) date.o static/*.o box/*.o -lreadline -lcurses + chmod g+w Lie.exe + Liegap.exe: gapdate.o + $(CC) -o Liegap.exe $(GAP_objects) gapdate.o static/*.o box/*.o diff --git a/build/pkgs/lie/spkg-install b/build/pkgs/lie/spkg-install new file mode 100755 index 00000000000..6653a832852 --- /dev/null +++ b/build/pkgs/lie/spkg-install @@ -0,0 +1,35 @@ +#!/bin/sh + +die () { + echo "$@" + exit 1 +} + +# patching +cd LiE/ +chmod -R a+rX . +patch -p1 <../patches/00-string.h.patch && +patch -p1 <../patches/01-libs.patch && +patch -p1 <../patches/02-hashbang.patch || + die "Error patching LiE." +if [ $UNAME = "SunOS" ]; then + patch -p1 <../patches/03-solaris.patch || + die "Error patching LiE." +fi + +# Building LiE in parallel is broken +export MAKE="$MAKE -j1" + +# building +$MAKE CC="$CC" || die "Error building LiE. Did you install bison?" + +# "install" the LiE package by moving over the complete build +# directory to $SAGE_LOCAL/lib/lie +cd .. +sed -e "s'$PWD/LiE'$SAGE_LOCAL/lib/LiE'" LiE/lie > LiE/lie~ +mv LiE/lie~ LiE/lie +chmod +x LiE/lie +rm -rf "$SAGE_LOCAL"/lib/lie # clean up old versions +rm -rf "$SAGE_LOCAL"/bin/lie "$SAGE_LOCAL"/lib/LiE +mv LiE/lie "$SAGE_LOCAL"/bin/ +mv LiE/ "$SAGE_LOCAL"/lib/LiE diff --git a/build/pkgs/lie/type b/build/pkgs/lie/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/lie/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/linbox/SPKG.txt b/build/pkgs/linbox/SPKG.txt index 17ff331c93d..ef08537f536 100644 --- a/build/pkgs/linbox/SPKG.txt +++ b/build/pkgs/linbox/SPKG.txt @@ -10,11 +10,6 @@ structured matrices over the integers and over finite fields. LGPL V2 or later -== Maintainers == - - * Clement Pernet - * William Stein - == Upstream Contact == * diff --git a/build/pkgs/lrcalc/SPKG.txt b/build/pkgs/lrcalc/SPKG.txt index 328549280df..bc101b1847d 100644 --- a/build/pkgs/lrcalc/SPKG.txt +++ b/build/pkgs/lrcalc/SPKG.txt @@ -10,11 +10,6 @@ http://math.rutgers.edu/~asbuch/lrcalc/ GNU General Public License V2+ -== SPKG Maintainers == - - * Mike Hansen - * Nicolas M. Thiéry - == Upstream Contact == Anders S. Buch (asbuch@math.rutgers.edu) diff --git a/build/pkgs/lrcalc/dependencies b/build/pkgs/lrcalc/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/lrcalc/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/lrslib/SPKG.txt b/build/pkgs/lrslib/SPKG.txt index b0713de9a13..ad485cf3da3 100644 --- a/build/pkgs/lrslib/SPKG.txt +++ b/build/pkgs/lrslib/SPKG.txt @@ -9,12 +9,6 @@ See the homepage (http://cgm.cs.mcgill.ca/~avis/C/lrs.html) for details. == License == lrslib is released under a GPL v2+ license. -== SPKG Maintainers == - - * Marshall Hampton (lrs old-style spkg) - * Michael Abshoff (lrs old-style spkg) - * Matthias Koeppe (lrslib new-style spkg) - == Upstream Contact == David Avis, avis at cs dot mcgill dot edu. diff --git a/build/pkgs/m4ri/SPKG.txt b/build/pkgs/m4ri/SPKG.txt index 25b14cc9ec6..3eefac0c992 100644 --- a/build/pkgs/m4ri/SPKG.txt +++ b/build/pkgs/m4ri/SPKG.txt @@ -9,10 +9,6 @@ GF(2). (See also m4ri/README for a brief overview.) * GNU General Public License Version 2 or later (see src/COPYING) -== Maintainers == - - * Martin Albrecht - == Upstream Contact == * Authors: Martin Albrecht et al. diff --git a/build/pkgs/m4rie/SPKG.txt b/build/pkgs/m4rie/SPKG.txt index bdd18e1afcb..0f5d1a49f06 100644 --- a/build/pkgs/m4rie/SPKG.txt +++ b/build/pkgs/m4rie/SPKG.txt @@ -9,12 +9,6 @@ GF(2^k) for 2 <= k <= 10. * GNU General Public License Version 2 or later (see src/COPYING) -== Maintainers == - - * Martin Albrecht - - SPKG Repository: https://bitbucket.org/malb/m4ri-spkg - == Upstream Contact == * Authors: Martin Albrecht diff --git a/build/pkgs/m4rie/checksums.ini b/build/pkgs/m4rie/checksums.ini index e4c5a9c3bde..faa02bff414 100644 --- a/build/pkgs/m4rie/checksums.ini +++ b/build/pkgs/m4rie/checksums.ini @@ -1,4 +1,4 @@ tarball=m4rie-VERSION.tar.gz -sha1=bdeca0d6b4058d27f2f72887a58c608bd3810ef2 -md5=10e9fd98efb72568ee64c6510f4cc0de -cksum=3493596118 +sha1=d0c5407046131184fc34056d478c8b4e9e22bb1a +md5=c2c04cbfcc5d56ffdeb5133109272b8c +cksum=1547829740 diff --git a/build/pkgs/m4rie/package-version.txt b/build/pkgs/m4rie/package-version.txt index a82c931ce04..ac47466489a 100644 --- a/build/pkgs/m4rie/package-version.txt +++ b/build/pkgs/m4rie/package-version.txt @@ -1 +1 @@ -20140914 +20150908 diff --git a/build/pkgs/maxima/SPKG.txt b/build/pkgs/maxima/SPKG.txt index f74b2daed8a..9c8827a4dc0 100644 --- a/build/pkgs/maxima/SPKG.txt +++ b/build/pkgs/maxima/SPKG.txt @@ -21,10 +21,6 @@ Maxima is distributed under the GNU General Public License, with some export restrictions from the U.S. Department of Energy. See the file COPYING. -== SPKG Maintainers == - - * TBD - == Upstream Contact == * The Maxima mailing list - see http://maxima.sourceforge.net/maximalist.html diff --git a/build/pkgs/mcqd/SPKG.txt b/build/pkgs/mcqd/SPKG.txt index e6379e83828..8292187b491 100644 --- a/build/pkgs/mcqd/SPKG.txt +++ b/build/pkgs/mcqd/SPKG.txt @@ -9,11 +9,6 @@ undirected graph. GPL 3 -== SPKG Maintainers == - -Jernej Azarija (azi.stdout@gmail.com) -Nathann Cohen (nathann.cohen@gmail.com) - == Upstream Contact == MCQD is currently being maintained by Janez Konc. diff --git a/build/pkgs/mercurial/type b/build/pkgs/mercurial/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/mercurial/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/modular_decomposition/SPKG.txt b/build/pkgs/modular_decomposition/SPKG.txt index 277eeded0c5..929836f4336 100644 --- a/build/pkgs/modular_decomposition/SPKG.txt +++ b/build/pkgs/modular_decomposition/SPKG.txt @@ -10,10 +10,6 @@ http://www.liafa.jussieu.fr/~fm/ (in french) GPL -== SPKG Maintainers == - -Nathann Cohen (nathann.cohen@gmail.com) - == Upstream Contact == Fabien de Montgolfier diff --git a/build/pkgs/mpc/SPKG.txt b/build/pkgs/mpc/SPKG.txt index 7ac0fa10baa..13dcd59e94c 100644 --- a/build/pkgs/mpc/SPKG.txt +++ b/build/pkgs/mpc/SPKG.txt @@ -15,10 +15,6 @@ high precision is a major design goal. LGPLv3+ for the code and GFDLv1.3+ (with no invariant sections) for the documentation. -== SPKG Maintainers == - - * Jeroen Demeyer - == Upstream Contact == The MPC website is located at http://www.multiprecision.org/mpc . diff --git a/build/pkgs/mpfi/SPKG.txt b/build/pkgs/mpfi/SPKG.txt index 48bd51d9a07..d360a94c372 100644 --- a/build/pkgs/mpfi/SPKG.txt +++ b/build/pkgs/mpfi/SPKG.txt @@ -24,10 +24,6 @@ License. It is permitted to link MPFI to non-free programs, as long as when distributing them the MPFI source code and a means to re-link with a modified MPFI is provided. -== SPKG Maintainers == - - * ??? - == Upstream Contact == The MPFI website is located at http://mpfi.gforge.inria.fr/ diff --git a/build/pkgs/mpfr/SPKG.txt b/build/pkgs/mpfr/SPKG.txt index 693c31f3acf..36e8fd4295f 100644 --- a/build/pkgs/mpfr/SPKG.txt +++ b/build/pkgs/mpfr/SPKG.txt @@ -26,10 +26,6 @@ Public License, the Lesser GPL enables developers of non-free programs to use MPFR in their programs. If you have written a new function for MPFR or improved an existing one, please share your work! -== SPKG Maintainers == - - * David Kirkby - == Upstream Contact == The MPFR website is located at http://mpfr.org/ diff --git a/build/pkgs/mpi4py/type b/build/pkgs/mpi4py/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/mpi4py/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/mpir/SPKG.txt b/build/pkgs/mpir/SPKG.txt index 47384145420..b776da0c49f 100644 --- a/build/pkgs/mpir/SPKG.txt +++ b/build/pkgs/mpir/SPKG.txt @@ -11,9 +11,6 @@ See http://www.mpir.org == License == * LGPL V3+ -== SPKG Maintainers == - * William Stein - == Upstream Contact == * The Google group mpir-devel * thempirteam@googlemail.com diff --git a/build/pkgs/mpir/package-version.txt b/build/pkgs/mpir/package-version.txt index 24ba9a38de6..9c8d6791e82 100644 --- a/build/pkgs/mpir/package-version.txt +++ b/build/pkgs/mpir/package-version.txt @@ -1 +1 @@ -2.7.0 +2.7.0.p1 diff --git a/build/pkgs/mpir/patches/fix-19280.patch b/build/pkgs/mpir/patches/fix-19280.patch new file mode 100644 index 00000000000..ced078c66ee --- /dev/null +++ b/build/pkgs/mpir/patches/fix-19280.patch @@ -0,0 +1,22 @@ +Apply the fix proposed in http://trac.sagemath.org/ticket/18546#comment:23 +to the bug reported in http://trac.sagemath.org/ticket/19280 . + +diff -ru mpir-2.7.0/gmp-impl.h mpir-2.7.0-fixed/gmp-impl.h +Index: gmp-impl.h +=================================================================== +--- mpir-2.7.0/gmp-impl.h 2015-06-10 23:02:35.000000000 +0200 ++++ mpir-2.7.0-fixed/gmp-impl.h 2015-09-29 17:04:17.746919331 +0200 +@@ -2040,11 +2040,11 @@ + #endif + + #ifndef SB_DIVAPPR_Q_SMALL_THRESHOLD +-#define SB_DIVAPPR_Q_SMALL_THRESHOLD 11 ++#define SB_DIVAPPR_Q_SMALL_THRESHOLD 0 + #endif + + #ifndef SB_DIV_QR_SMALL_THRESHOLD +-#define SB_DIV_QR_SMALL_THRESHOLD 11 ++#define SB_DIV_QR_SMALL_THRESHOLD 0 + #endif + + #ifndef DC_DIVAPPR_Q_THRESHOLD diff --git a/build/pkgs/mpir/patches/test-19280.patch b/build/pkgs/mpir/patches/test-19280.patch new file mode 100644 index 00000000000..cef193c8bbf --- /dev/null +++ b/build/pkgs/mpir/patches/test-19280.patch @@ -0,0 +1,161 @@ +Include a test for the bug reported in http://trac.sagemath.org/ticket/19280 . +diff -ruN mpir-2.7.0/tests/mpz/Makefile.am mpir-2.7.0-patched/tests/mpz/Makefile.am +--- mpir-2.7.0/tests/mpz/Makefile.am 2015-06-09 19:18:27.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.am 2015-09-24 18:39:33.093974089 +0200 +@@ -26,7 +26,7 @@ + AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/tests + LDADD = $(top_builddir)/tests/libtests.la $(top_builddir)/libmpir.la + +-check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division ++check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division t-19280 + + if ENABLE_STATIC + if ENABLE_SHARED +diff -ruN mpir-2.7.0/tests/mpz/Makefile.in mpir-2.7.0-patched/tests/mpz/Makefile.in +--- mpir-2.7.0/tests/mpz/Makefile.in 2015-06-16 12:40:00.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.in 2015-09-24 18:42:37.485967609 +0200 +@@ -121,7 +121,7 @@ + t-set_si$(EXEEXT) t-set_str$(EXEEXT) t-set_sx$(EXEEXT) \ + t-set_ux$(EXEEXT) t-sizeinbase$(EXEEXT) t-sqrtrem$(EXEEXT) \ + t-tdiv$(EXEEXT) t-tdiv_ui$(EXEEXT) t-trial_division$(EXEEXT) \ +- $(am__EXEEXT_1) ++ t-19280$(EXEEXT) $(am__EXEEXT_1) + @ENABLE_SHARED_TRUE@@ENABLE_STATIC_TRUE@am__append_1 = st_hamdist st_popcount + subdir = tests/mpz + DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ +@@ -197,6 +197,11 @@ + st_popcount_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(st_popcount_LDFLAGS) $(LDFLAGS) -o $@ ++t_19280_SOURCES = t-19280.c ++t_19280_OBJECTS = t-19280.$(OBJEXT) ++t_19280_LDADD = $(LDADD) ++t_19280_DEPENDENCIES = $(top_builddir)/tests/libtests.la \ ++ $(top_builddir)/libmpir.la + t_addsub_SOURCES = t-addsub.c + t_addsub_OBJECTS = t-addsub.$(OBJEXT) + t_addsub_LDADD = $(LDADD) +@@ -526,21 +531,7 @@ + am__v_CCLD_0 = @echo " CCLD " $@; + am__v_CCLD_1 = + SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-addsub.c \ +- t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c t-cmp_si.c \ +- t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c t-divis_2exp.c \ +- t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c t-fib_ui.c t-fits.c \ +- t-gcd.c t-gcd_ui.c t-get_d.c t-get_d_2exp.c t-get_si.c \ +- t-get_sx.c t-get_ux.c t-hamdist.c t-import.c t-inp_str.c \ +- t-io_raw.c t-jac.c t-lcm.c t-likely_prime_p.c t-lucnum_ui.c \ +- t-mfac_uiui.c t-mul.c t-mul_i.c t-next_prime_candidate.c \ +- t-oddeven.c t-perfpow.c t-perfsqr.c t-popcount.c t-pow.c \ +- t-powm.c t-powm_ui.c t-pprime_p.c t-primorial_ui.c t-root.c \ +- t-scan.c t-set_d.c t-set_f.c t-set_si.c t-set_str.c t-set_sx.c \ +- t-set_ux.c t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ +- t-trial_division.c +-DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-19280.c \ + t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c \ + t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c \ + t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c \ +@@ -554,6 +545,21 @@ + t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ + t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ + t-trial_division.c ++DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ ++ $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ t-19280.c t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c \ ++ t-cmp_d.c t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c \ ++ t-divis.c t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c \ ++ t-fdiv_ui.c t-fib_ui.c t-fits.c t-gcd.c t-gcd_ui.c t-get_d.c \ ++ t-get_d_2exp.c t-get_si.c t-get_sx.c t-get_ux.c t-hamdist.c \ ++ t-import.c t-inp_str.c t-io_raw.c t-jac.c t-lcm.c \ ++ t-likely_prime_p.c t-lucnum_ui.c t-mfac_uiui.c t-mul.c \ ++ t-mul_i.c t-next_prime_candidate.c t-oddeven.c t-perfpow.c \ ++ t-perfsqr.c t-popcount.c t-pow.c t-powm.c t-powm_ui.c \ ++ t-pprime_p.c t-primorial_ui.c t-root.c t-scan.c t-set_d.c \ ++ t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ ++ t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ ++ t-trial_division.c + am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ +@@ -1020,6 +1026,10 @@ + @rm -f st_popcount$(EXEEXT) + $(AM_V_CCLD)$(st_popcount_LINK) $(st_popcount_OBJECTS) $(st_popcount_LDADD) $(LIBS) + ++t-19280$(EXEEXT): $(t_19280_OBJECTS) $(t_19280_DEPENDENCIES) $(EXTRA_t_19280_DEPENDENCIES) ++ @rm -f t-19280$(EXEEXT) ++ $(AM_V_CCLD)$(LINK) $(t_19280_OBJECTS) $(t_19280_LDADD) $(LIBS) ++ + t-addsub$(EXEEXT): $(t_addsub_OBJECTS) $(t_addsub_DEPENDENCIES) $(EXTRA_t_addsub_DEPENDENCIES) + @rm -f t-addsub$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_addsub_OBJECTS) $(t_addsub_LDADD) $(LIBS) +@@ -1931,6 +1941,13 @@ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ ++ "$$tst" $(AM_TESTS_FD_REDIRECT) ++t-19280.log: t-19280$(EXEEXT) ++ @p='t-19280$(EXEEXT)'; \ ++ b='t-19280'; \ ++ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ ++ --log-file $$b.log --trs-file $$b.trs \ ++ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) + st_hamdist.log: st_hamdist$(EXEEXT) + @p='st_hamdist$(EXEEXT)'; \ +diff -ruN mpir-2.7.0/tests/mpz/t-19280.c mpir-2.7.0-patched/tests/mpz/t-19280.c +--- mpir-2.7.0/tests/mpz/t-19280.c 1970-01-01 01:00:00.000000000 +0100 ++++ mpir-2.7.0-patched/tests/mpz/t-19280.c 2015-09-24 18:57:03.005937190 +0200 +@@ -0,0 +1,50 @@ ++/* Test t-19280. ++ ++*/ ++ ++#include ++#include ++ ++#include "mpir.h" ++#include "gmp-impl.h" ++#include "tests.h" ++ ++#define printf gmp_printf ++ ++/* Check mpz_tdif_q gives a correct answer on 32-bit, ++ see http://trac.sagemath.org/ticket/19280. ++ This was wrong in sage 6.9.beta7. */ ++static void ++check_19280 (void) ++{ ++ ++ mpz_t one, x, w, correct; ++ mpz_init(one); ++ mpz_init(x); ++ mpz_init(w); ++ mpz_init(correct); ++ mpz_set_str(one, "62165404551223330269422781018352605012557018849668464680057997111644937126566671941632", 10); ++ mpz_set_str(x, "39623752663112484341451587580", 10); ++ mpz_set_str(correct, "1568892403497558507879962225846103176600476845510570267609", 10); ++ ++ mpz_tdiv_q(w, one, x); ++ if (mpz_cmp(w, correct)!=0) { ++ printf("mpz_tdiv_q returned %Zd instead of %Zd.\n", w, correct); ++ abort(); ++ } ++ mpz_clear(one); ++ mpz_clear(x); ++ mpz_clear(w); ++ mpz_clear(correct); ++} ++ ++int ++main (void) ++{ ++ tests_start (); ++ ++ check_19280 (); ++ ++ tests_end (); ++ exit (0); ++} diff --git a/build/pkgs/mpmath/SPKG.txt b/build/pkgs/mpmath/SPKG.txt index 9217b20bee9..e885c18929d 100644 --- a/build/pkgs/mpmath/SPKG.txt +++ b/build/pkgs/mpmath/SPKG.txt @@ -4,11 +4,6 @@ Mpmath is a pure-Python library for multiprecision floating-point arithmetic. It provides an extensive set of transcendental functions, unlimited exponent sizes, complex numbers, interval arithmetic, numerical integration and differentiation, root-finding, linear algebra, and much more. Almost any calculation can be performed just as well at 10-digit or 1000-digit precision, and in many cases mpmath implements asymptotically fast algorithms that scale well for extremely high precision work. If available, mpmath will (optionally) use gmpy to speed up high precision operations. -== Maintainers == - - * Mike Hansen - * Fredrik Johansson - == Upstream Contact == * Author: Fredrik Johansson diff --git a/build/pkgs/nauty/dependencies b/build/pkgs/nauty/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/nauty/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/nbconvert/SPKG.txt b/build/pkgs/nbconvert/SPKG.txt new file mode 100644 index 00000000000..362c21c9e5f --- /dev/null +++ b/build/pkgs/nbconvert/SPKG.txt @@ -0,0 +1,8 @@ += nbconvert = + +== Description == + +Converting Jupyter Notebooks + +jupyter nbconvert converts notebooks to various other formats via Jinja +templates. diff --git a/build/pkgs/nbconvert/checksums.ini b/build/pkgs/nbconvert/checksums.ini new file mode 100644 index 00000000000..7e388037867 --- /dev/null +++ b/build/pkgs/nbconvert/checksums.ini @@ -0,0 +1,4 @@ +tarball=nbconvert-VERSION.tar.gz +sha1=7b149cb25a1122763c47c5ae8327ea2ce3cea5bc +md5=223067790b8f193f587f5b59ee76f568 +cksum=418065607 diff --git a/build/pkgs/nbconvert/dependencies b/build/pkgs/nbconvert/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/nbconvert/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/nbconvert/package-version.txt b/build/pkgs/nbconvert/package-version.txt new file mode 100644 index 00000000000..fcdb2e109f6 --- /dev/null +++ b/build/pkgs/nbconvert/package-version.txt @@ -0,0 +1 @@ +4.0.0 diff --git a/build/pkgs/nbconvert/spkg-install b/build/pkgs/nbconvert/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/nbconvert/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/nbconvert/type b/build/pkgs/nbconvert/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/nbconvert/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/nbformat/SPKG.txt b/build/pkgs/nbformat/SPKG.txt new file mode 100644 index 00000000000..a6c91aa1966 --- /dev/null +++ b/build/pkgs/nbformat/SPKG.txt @@ -0,0 +1,8 @@ += nbformat = + +== Description == + +The Jupyter Notebook format + +This package contains the base implementation of the Jupyter Notebook +format, and Python APIs for working with notebooks. diff --git a/build/pkgs/nbformat/checksums.ini b/build/pkgs/nbformat/checksums.ini new file mode 100644 index 00000000000..1c6f0fe4005 --- /dev/null +++ b/build/pkgs/nbformat/checksums.ini @@ -0,0 +1,4 @@ +tarball=nbformat-VERSION.tar.gz +sha1=f8a2533ccee18596eb53c6404af6062ed2432585 +md5=ce31bb7f34c6a3554dc4a7625fc3033f +cksum=227017057 diff --git a/build/pkgs/nbformat/dependencies b/build/pkgs/nbformat/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/nbformat/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/nbformat/package-version.txt b/build/pkgs/nbformat/package-version.txt new file mode 100644 index 00000000000..fcdb2e109f6 --- /dev/null +++ b/build/pkgs/nbformat/package-version.txt @@ -0,0 +1 @@ +4.0.0 diff --git a/build/pkgs/nbformat/spkg-install b/build/pkgs/nbformat/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/nbformat/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/nbformat/type b/build/pkgs/nbformat/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/nbformat/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/ncurses/SPKG.txt b/build/pkgs/ncurses/SPKG.txt index ee995e9265b..74eb9bb759d 100644 --- a/build/pkgs/ncurses/SPKG.txt +++ b/build/pkgs/ncurses/SPKG.txt @@ -28,10 +28,6 @@ Website: http://invisible-island.net/ncurses * MIT-style -== SPKG Maintainers == - - * Volker Braun - == Upstream Contact == * bug-ncurses@gnu.org diff --git a/build/pkgs/ncurses/dependencies b/build/pkgs/ncurses/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/ncurses/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ncurses/patches/work_around_changed_output_of_GNU_cpp_5.x.patch b/build/pkgs/ncurses/patches/work_around_changed_output_of_GNU_cpp_5.x.patch index af82739cfbb..619b37c6150 100644 --- a/build/pkgs/ncurses/patches/work_around_changed_output_of_GNU_cpp_5.x.patch +++ b/build/pkgs/ncurses/patches/work_around_changed_output_of_GNU_cpp_5.x.patch @@ -1,5 +1,5 @@ -Building ncurses with GCC 5.1 (or more precisely, with its 'cpp') fails with -a syntax error, caused by earlier preprocessing. +Building ncurses with GCC 5 (or more precisely, with its 'cpp') fails with a +syntax error, caused by earlier preprocessing. (I'm not entirely sure whether it's a GCC bug or rather caused by a new feature which breaks further processing with 'awk' and 'sed'; I *think* @@ -19,7 +19,7 @@ output of 'cpp' w.r.t. line directives [1]. Anyway, the patch fixes the issue.) +# Work around "unexpected" output of GCC 5.1.0's cpp w.r.t. #line directives +# by simply suppressing them: +case `$1 -dumpversion 2>/dev/null` in -+ 5.[01].*) # assume a "broken" one ++ 5.*.*) # assume a "broken" one + preprocessor="$1 -P -DNCURSES_INTERNALS -I../include" + ;; + *) diff --git a/build/pkgs/networkx/SPKG.txt b/build/pkgs/networkx/SPKG.txt index 004a31761a4..c70a3c12cf9 100644 --- a/build/pkgs/networkx/SPKG.txt +++ b/build/pkgs/networkx/SPKG.txt @@ -8,10 +8,6 @@ NetworkX (NX) is a Python package for the creation, manipulation, and study of t BSD -== SPKG Maintainers == - * Robert Miller - * Gregory McWhirter - == Upstream Contact == See http://networkx.lanl.gov/ diff --git a/build/pkgs/networkx/checksums.ini b/build/pkgs/networkx/checksums.ini index 50122f00a66..9aaf119a3b8 100644 --- a/build/pkgs/networkx/checksums.ini +++ b/build/pkgs/networkx/checksums.ini @@ -1,4 +1,4 @@ tarball=networkx-VERSION.tar.gz -sha1=d6c1524724d3e47f7621bb2072863463924bfb99 -md5=b4a9e68ecd1b0164446ee432d2e20bd0 -cksum=3256827710 +sha1=99292e464c25be5e96de295752880bf5e5f1848a +md5=eb7a065e37250a4cc009919dacfe7a9d +cksum=2520536431 diff --git a/build/pkgs/networkx/dependencies b/build/pkgs/networkx/dependencies index edf27112135..d4db0ff5eae 100644 --- a/build/pkgs/networkx/dependencies +++ b/build/pkgs/networkx/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) +$(INST)/$(PYTHON) $(INST)/$(DECORATOR) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/networkx/package-version.txt b/build/pkgs/networkx/package-version.txt index a8fdfda1c78..c044b1a3269 100644 --- a/build/pkgs/networkx/package-version.txt +++ b/build/pkgs/networkx/package-version.txt @@ -1 +1 @@ -1.8.1 +1.10 diff --git a/build/pkgs/networkx/spkg-install b/build/pkgs/networkx/spkg-install index 6769f79adac..5cd75ee5791 100755 --- a/build/pkgs/networkx/spkg-install +++ b/build/pkgs/networkx/spkg-install @@ -15,4 +15,4 @@ rm -rf "$SAGE_LOCAL"/spkg/network* cd src -python setup.py install --home="$SAGE_LOCAL" --force +python setup.py install diff --git a/build/pkgs/nibabel/type b/build/pkgs/nibabel/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/nibabel/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/normaliz/SPKG.txt b/build/pkgs/normaliz/SPKG.txt index 507f027d39a..3d935d0fd05 100644 --- a/build/pkgs/normaliz/SPKG.txt +++ b/build/pkgs/normaliz/SPKG.txt @@ -10,11 +10,6 @@ For more details see http://www.mathematik.uni-osnabrueck.de/normaliz/ * GPL v3 -== SPKG Maintainers == - - * Dima Pasechnik - * Andrey Novoseltsev - == Upstream Contact == * Winfried Bruns diff --git a/build/pkgs/nose/SPKG.txt b/build/pkgs/nose/SPKG.txt index 8f545d38c28..884de83e9b4 100644 --- a/build/pkgs/nose/SPKG.txt +++ b/build/pkgs/nose/SPKG.txt @@ -9,11 +9,6 @@ it easier to write, find and run tests. GNU LGPL -== SPKG Maintainers == - -John H. Palmieri -Martin W.-Raum - == Upstream Contact == Author: Jason Pellerin diff --git a/build/pkgs/notebook/SPKG.txt b/build/pkgs/notebook/SPKG.txt new file mode 100644 index 00000000000..af2253acd9f --- /dev/null +++ b/build/pkgs/notebook/SPKG.txt @@ -0,0 +1,6 @@ += notebook = + +== Description == + +The Jupyter HTML notebook is a web-based notebook environment for +interactive computing. diff --git a/build/pkgs/notebook/checksums.ini b/build/pkgs/notebook/checksums.ini new file mode 100644 index 00000000000..e10dd9db4ab --- /dev/null +++ b/build/pkgs/notebook/checksums.ini @@ -0,0 +1,4 @@ +tarball=notebook-VERSION.tar.gz +sha1=b6546f0b68e60ea2efa354be24fa4ded19708aaa +md5=91d6d780832f1bec7545c66e367d34f3 +cksum=2631689251 diff --git a/build/pkgs/notebook/dependencies b/build/pkgs/notebook/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/notebook/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt new file mode 100644 index 00000000000..61d8a2af785 --- /dev/null +++ b/build/pkgs/notebook/package-version.txt @@ -0,0 +1 @@ +4.0.4.p1 diff --git a/build/pkgs/notebook/patches/jupyter_notebook_config.py b/build/pkgs/notebook/patches/jupyter_notebook_config.py new file mode 100644 index 00000000000..3bf63a1649a --- /dev/null +++ b/build/pkgs/notebook/patches/jupyter_notebook_config.py @@ -0,0 +1,7 @@ +# Configuration file for Sage's builtin Jupyter notebook server + +# Note for distributors: Sage uses mathjax, so the notebook server +# needs to have the mathjax_url set to wherever your distribution +# installs mathjax. + +c.NotebookApp.mathjax_url = '../nbextensions/mathjax/MathJax.js' diff --git a/build/pkgs/notebook/spkg-install b/build/pkgs/notebook/spkg-install new file mode 100755 index 00000000000..10759a4d973 --- /dev/null +++ b/build/pkgs/notebook/spkg-install @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cd src && python setup.py install + +# Install the Jupyter notebook configuration +ETC_JUPYTER="$SAGE_ETC"/jupyter +mkdir -p "$ETC_JUPYTER" +cp ../patches/jupyter_notebook_config.py "$ETC_JUPYTER"/ diff --git a/build/pkgs/notebook/type b/build/pkgs/notebook/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/notebook/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/numpy/SPKG.txt b/build/pkgs/numpy/SPKG.txt index cff2ffbedb8..c7da9b2fdce 100644 --- a/build/pkgs/numpy/SPKG.txt +++ b/build/pkgs/numpy/SPKG.txt @@ -5,9 +5,6 @@ This package adds numerical linear algebra and other numerical computing capabilities to python. -== Maintainers == - * Josh Kantor - == Upstream Contact == * Travis Oliphant * Fernando Perez diff --git a/build/pkgs/openssl/dependencies b/build/pkgs/openssl/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/openssl/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ore_algebra/SPKG.txt b/build/pkgs/ore_algebra/SPKG.txt new file mode 100644 index 00000000000..ba4c9df107d --- /dev/null +++ b/build/pkgs/ore_algebra/SPKG.txt @@ -0,0 +1,21 @@ += ore_algebra = + +== Description == + +A Sage implementation of Ore algebras and Ore polynomials. + +Main features for the most common algebras include basic arithmetic and actions; +gcrd and lclm; D-finite closure properties; natural transformations between +related algebras; guessing; desingularization; solvers for polynomials, rational +functions and (generalized) power series. + +== License == + +Distributed under the terms of the GNU General Public License (GPL) + +http://www.gnu.org/licenses/ + +== Dependencies == + +None. + diff --git a/build/pkgs/ore_algebra/checksums.ini b/build/pkgs/ore_algebra/checksums.ini new file mode 100644 index 00000000000..f1b36fa7269 --- /dev/null +++ b/build/pkgs/ore_algebra/checksums.ini @@ -0,0 +1,4 @@ +tarball=ore_algebra-VERSION.tar.gz +sha1=5083b94a3429f92acc79a043302adf107bd57a63 +md5=48b6a6c4613981fb033ca2188f12f802 +cksum=3887839116 diff --git a/build/pkgs/ore_algebra/package-version.txt b/build/pkgs/ore_algebra/package-version.txt new file mode 100644 index 00000000000..3b04cfb60da --- /dev/null +++ b/build/pkgs/ore_algebra/package-version.txt @@ -0,0 +1 @@ +0.2 diff --git a/build/pkgs/ore_algebra/spkg-install b/build/pkgs/ore_algebra/spkg-install new file mode 100755 index 00000000000..93676fbc539 --- /dev/null +++ b/build/pkgs/ore_algebra/spkg-install @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +cd src +export PYTHONPATH="." +echo "building ore_algebra" +python setup.py install + +if [ $? -ne 0 ]; then + echo >&2 "Error building ore_algebra" + exit 1 +fi diff --git a/build/pkgs/ore_algebra/type b/build/pkgs/ore_algebra/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/ore_algebra/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/palp/SPKG.txt b/build/pkgs/palp/SPKG.txt index 0136685c20f..20184a6a0b1 100644 --- a/build/pkgs/palp/SPKG.txt +++ b/build/pkgs/palp/SPKG.txt @@ -30,10 +30,6 @@ facet enumeration compares well with existing packages. * Therefore one can deduce the authors were happy for this to be released under GPL 2 or a later version. -== SPKG Maintainers == - - * William Stein - == Upstream Contact == * Author: Harald Skarke (skarke@maths.ox.ac.uk) diff --git a/build/pkgs/pari/SPKG.txt b/build/pkgs/pari/SPKG.txt index 1347afc3c2c..a83829520db 100644 --- a/build/pkgs/pari/SPKG.txt +++ b/build/pkgs/pari/SPKG.txt @@ -18,12 +18,6 @@ Belabas with the help of many volunteer contributors. GPL version 2+ -== SPKG Maintainers == - * Robert Bradshaw - * William Stein - * David Kirkby - * Jeroen Demeyer - == Upstream Contact == * http://pari.math.u-bordeaux.fr/ diff --git a/build/pkgs/pari/checksums.ini b/build/pkgs/pari/checksums.ini index 8870cbb86ee..c62c530a888 100644 --- a/build/pkgs/pari/checksums.ini +++ b/build/pkgs/pari/checksums.ini @@ -1,4 +1,4 @@ tarball=pari-VERSION.tar.gz -sha1=307409c3917f6df71d2e10640c119e7d31c1f2e6 -md5=41936ce2dce6bd00a662bf43a772685f -cksum=855809013 +sha1=fa23e0c8b6e38a356048d19224dc9b9658d77724 +md5=c753faaa4780de5ad8d461f0ffd70ecf +cksum=1220765312 diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 8db184f072c..2b25bd18c62 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-1637-g489005a.p1 +2.8-1813-g6157df4.p0 diff --git a/build/pkgs/pari/patches/KERNELCFLAGS.patch b/build/pkgs/pari/patches/KERNELCFLAGS.patch deleted file mode 100644 index 537dbb52d1e..00000000000 --- a/build/pkgs/pari/patches/KERNELCFLAGS.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff -ru src/config/get_cc b/config/get_cc ---- src/config/get_cc 2014-02-01 21:41:54.534348273 +0100 -+++ b/config/get_cc 2014-02-01 21:42:50.850930971 +0100 -@@ -94,7 +94,11 @@ - OPTFLAGS="$OPTFLAGS -fno-strict-aliasing" - fi - rm -f $exe $exe$exe_suff -- KERNELCFLAGS=-funroll-loops -+ if [ "$SAGE_DEBUG" = yes ]; then -+ KERNELCFLAGS=-O1 -+ else -+ KERNELCFLAGS=-funroll-loops -+ fi - - DBGFLAGS=${DBGFLAGS:-"-g $warn"} - # Specific optimisations for some architectures diff --git a/build/pkgs/pari/patches/README.txt b/build/pkgs/pari/patches/README.txt index 93ac577bd55..d9aef73659a 100644 --- a/build/pkgs/pari/patches/README.txt +++ b/build/pkgs/pari/patches/README.txt @@ -11,17 +11,7 @@ Patches to configuration files: Darwin. Submitted upstream, but upstream only applied it for PowerPC. Since this doesn't break anything and only improves performance, add the flag unconditionally. -* KERNELCFLAGS.patch (Jeroen Demeyer): when SAGE_DEBUG=yes, compile - kernel files with -O1 instead of -funroll-loops; -O0 gives a - segmentation fault on some OS X systems when doing - factor(10356613*10694706299664611221) - See #13921, also reported upstream: - - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html C files: -* det_garbage.patch (Jeroen Demeyer, #15654): When computing a - determinant(), only collect garbage once per outer loop iteration. - Better increase PARI stack size instead of collecting garbage too - often. * public_memory_functions.patch (Jeroen Demeyer, #16997): Make some of PARI's private memory functions public to improve interface with Sage. diff --git a/build/pkgs/pari/patches/det_garbage.patch b/build/pkgs/pari/patches/det_garbage.patch deleted file mode 100644 index ab7af6643af..00000000000 --- a/build/pkgs/pari/patches/det_garbage.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/src/basemath/alglin1.c b/src/basemath/alglin1.c -index cada9eb..515eba9 100644 ---- a/src/basemath/alglin1.c -+++ b/src/basemath/alglin1.c -@@ -248,6 +248,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - a = RgM_shallowcopy(a); - for (i=1; ired(E,gcoeff(a,k,i)); -@@ -272,7 +273,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = ff->add(E, gcoeff(a,j,k), ff->mul(E,m,gcoeff(a,j,i))); -- if (gc_needed(av,1)) -+ if (gc_needed(av,1) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,4, &a,&x,&q,&m); -@@ -3722,6 +3723,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - a = RgM_shallowcopy(a); - for (i=1; i nbco) return gerepilecopy(av, gcoeff(a,i,i)); - if (k != i) -@@ -3741,7 +3743,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = gsub(gcoeff(a,j,k), gmul(m,gcoeff(a,j,i))); -- if (gc_needed(av,3)) -+ if (gc_needed(av,3) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&x); -@@ -3792,6 +3794,7 @@ det_bareiss(GEN a) - { - GEN ci, ck, m; - int diveuc = (gequal1(pprec)==0); -+ int garbage = 0; /* Only gerepile() once per loop iteration */ - - p = gcoeff(a,i,i); - if (gequal0(p)) -@@ -3828,7 +3831,7 @@ det_bareiss(GEN a) - GEN p1 = gsub(gmul(p,gel(ck,j)), gmul(m,gel(ci,j))); - if (diveuc) p1 = mydiv(p1,pprec); - gel(ck,j) = gerepileupto(av2, p1); -- if (gc_needed(av,2)) -+ if (gc_needed(av,2) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&pprec); diff --git a/build/pkgs/pari/patches/perl_regex.patch b/build/pkgs/pari/patches/perl_regex.patch deleted file mode 100644 index 038f4d604e8..00000000000 --- a/build/pkgs/pari/patches/perl_regex.patch +++ /dev/null @@ -1,200 +0,0 @@ -commit 257750686ae1fe928a2b4b489844c1b57a108bd3 -Author: Karim Belabas -Date: Tue Jul 14 15:23:42 2015 +0200 - - doc_make: escape all {} in regexps [ perl-5.22 warns on these => fatal - -diff --git a/src/desc/doc_make b/src/desc/doc_make -index bb41bc9..8521a9d 100755 ---- a/src/desc/doc_make -+++ b/src/desc/doc_make -@@ -38,13 +38,13 @@ while () - $v =~ s/\[\]/[\\,]/g; - $v =~ s/(\w\w+)/\\var{$1}/g; - $v =~ s/\^([a-z])/\\hbox{\\kbd{\\pow}}$1/g; -- $v =~ s/\\var{flag}/\\fl/g; -- $v =~ s/\\var{(\d+)}/{$1}/g; -+ $v =~ s/\\var\{flag\}/\\fl/g; -+ $v =~ s/\\var\{(\d+)\}/{$1}/g; - $v =~ s/_/\\_/g; # don't merge with first subst: \var{} rule kills it - - $v = "\$($v)\$"; - } -- if ($doc !~ /\\syn\w*{/ && $sec !~ /programming\/control/) { -+ if ($doc !~ /\\syn\w*\{/ && $sec !~ /programming\/control/) { - $doc .= library_syntax($fun, $args); - } - s/_def_//; -commit 742c70e505a7e75128720f619d63e882c03e9346 -Author: Karim Belabas -Date: Tue Jul 14 13:20:07 2015 +0200 - - gphelp: escape all {} in regexps [ perl-5.22 warns on these => fatal ] - -diff --git a/doc/gphelp.in b/doc/gphelp.in -index 00ff6bd..89f2768 100755 ---- a/doc/gphelp.in -+++ b/doc/gphelp.in -@@ -298,7 +298,7 @@ sub treat { - if ($pat =~ /[a-zA-Z0-9]$/) { $pat .= '\b'; } else { $pat .= '}'; } - while () - { -- if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label){$pat/) -+ if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label)\{$pat/) - { $first = $_; last; } - } - if (eof(DOC)) -@@ -380,7 +380,7 @@ sub apropos_check { - return if ($line !~ /$help/i); - - local($_) = $current; -- s/\\b{(.)}/\\$1/; -+ s/\\b\{(.)\}/\\$1/; - s/\{\}//g; - s/\\pow/^/; - s/\\%/%/; -@@ -400,7 +400,7 @@ sub apropos { - @sentence_list = @list = ""; - while () - { -- if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter){/) -+ if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter)\{/) - { - $new = &get_match($_,'{','}'); - &apropos_check($line, $current); -@@ -748,7 +748,7 @@ sub basic_subst { - s/\\fun\s*\{([^{}]*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}/\\kbd{$1 \\key{$2}($3)}\\sidx{$2}/g; - - s/\\\\(?=[a-zA-Z])/\\bs /g; -- s/\\b{}\\b{}/\\bs\\bs /g; -+ s/\\b\{\}\\b\{\}/\\bs\\bs /g; - s/\\\\/\\bs/g; - s/(\'\'|\`\`)/"/g unless $to_pod; # (english) double quotes - # asymptotic or isomorphic (~) [beware of ties] -@@ -760,16 +760,16 @@ sub basic_subst { - s/\\(~|tilde)/~/g; - - s/\\(equiv)(?![a-zA-Z])/ = /g; -- s/\\`a/$tr{agrave}/; s/\\`{a}/$tr{agrave}/; -- s/\\"o/$tr{ouml}/; s/\\"{o}/$tr{ouml}/; -- s/\\"u/$tr{uuml}/; s/\\"{u}/$tr{uuml}/; -- s/\\'e/$tr{eacute}/; s/\\'{e}/$tr{eacute}/; -+ s/\\`a/$tr{agrave}/; s/\\`\{a\}/$tr{agrave}/; -+ s/\\"o/$tr{ouml}/; s/\\"\{o\}/$tr{ouml}/; -+ s/\\"u/$tr{uuml}/; s/\\"\{u\}/$tr{uuml}/; -+ s/\\'e/$tr{eacute}/; s/\\'\{e\}/$tr{eacute}/; - - s/(^|[^\\])%.*/$1/g; # comments - s/\\vadjust\s*\{\s*\\penalty\s*\d+\s*\}//g; - - # We do not strip %\n, thus: -- s/\\kbd{\n\s*/\\kbd{/g; -+ s/\\kbd\{\n\s*/\\kbd{/g; - s/\$\\bf(\b|(?=[\d_]))\s*([^\$]+)\$/\$$tr{startbcode}$1$tr{endbcode}\$/g; - s/\$/$tr{dollar}/g; # math mode - s/\t/ /g; s/\\,//g; s/\\[ ;]/ /g; # various spaces -@@ -779,7 +779,7 @@ sub basic_subst { - s/\\TeX\{\}/TeX/g; - s/\\TeX(\W)/TeX$1/g; - s/ *\\circ\b */ o /g; -- s/\\d?frac{\s*((?:[^{}]|\{[^{}]*\})*)}{\s*((?:[^{}]|\{[^{}]*\})*)}/($1)\/($2)/g; -+ s/\\d?frac\{\s*((?:[^{}]|\{[^{}]*\})*)\}\{\s*((?:[^{}]|\{[^{}]*\})*)\}/($1)\/($2)/g; - s(\\d?frac\s*(\d)\s*(\d))(($1/$2))g; - s[{\s*(\w)\s*\\over(?![a-zA-Z])\s*(\w)\s*}]{($1/$2)}g; - s[{\s*((?:[^{}]|\{[^{}]*\})*)\\over(?![a-zA-Z])\s*((?:[^{}]|\{[^{}]*\})*)}][($1)/($2)]g; -@@ -796,7 +796,7 @@ sub basic_subst { - - s/(\\string)?\\_/_/g; - s/\\([#\$&%|])/$1/g; -- s/\\(hat(?![a-zA-Z])|\^)({\\?\s*})?/^/g; -+ s/\\(hat(?![a-zA-Z])|\^)(\{\\?\s*\})?/^/g; - s/^(\@\[podleader\]head\d *)\\pow(?![a-zA-z])( *)/$1^$2/gm; - s/ *\\pow(?![a-zA-z]) */^/g; - -@@ -896,21 +896,21 @@ sub basic_subst { - s/\\(floor|ceil|round|binom)\{/$1\{/g; - s/\\(var|emph)\{([^\}]*)\}/$tr{startit}$2$tr{endit}/g; - s/\\fl(?![a-zA-Z])/$tr{startit}flag$tr{endit}/g; -- s/\\b{([^}]*)}/$tr{startcode}\\$1$tr{endcode}/g; -+ s/\\b\{([^}]*)\}/$tr{startcode}\\$1$tr{endcode}/g; - s/\\kbdsidx/\\sidx/g; - s/\\sidx\{[^\}]*\}//g unless $to_pod; - s/\\[a-zA-Z]*idx\{([^\}]*)\}/$1/g unless $to_pod; -- s/{\\text{(st|nd|th)}}/\\text{$1}/g; -- s/\^\\text{th}/-th/g; -- s/1\^\\text{st}/1st/g; -- s/2\^\\text{nd}/2nd/g; -+ s/\{\\text\{(st|nd|th)\}\}/\\text{$1}/g; -+ s/\^\\text\{th\}/-th/g; -+ s/1\^\\text\{st\}/1st/g; -+ s/2\^\\text\{nd\}/2nd/g; - - s/\\(text|hbox|Big)//g; - s/^([ \t]+)\{ *\\(it|sl|bf|tt)\b/S<$1>{\\$2/gm; - s/\{ *\\(it|sl) *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startit}$2$tr{endit}/g; - s/\{ *\\bf *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startbold}$1$tr{endbold}/g; - s/\{ *\\tt *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startpodcode}$1$tr{endpodcode}/g; -- $seek=1 if (s/\\emph{ */$tr{startit}/g); -+ $seek=1 if (s/\\emph\{ */$tr{startit}/g); - if ($seek) { $seek=0 if (s/\}/$tr{endit}/) } - s/\\(backslash|bs)\{(\w)\}/\\$2/g; - s/\\(backslash|bs)(?![a-zA-Z]) */\\/g; -@@ -1028,21 +1028,21 @@ sub TeXprint_topod { - # Try to guard \label/\sidx (removing possible '.') - # This somehow breaks index... - # s/(\\(?:section|subsec(?:ref|idx|op)?(unix)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -- s/(\\(?:section|subsec(?:ref|idx|op)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -+ s/(\\(?:section|subsec(?:ref|idx|op)?)\s*\{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)\})\.?\s*\\(label|sidx)/$1\n\\$2/; - - # last if /\\subsec[\\{}ref]*[\\\${]$help[}\\\$]/o; -- s/\\chapter\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -- s/\\appendix\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -- s/\\section\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; -+ s/\\chapter\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -+ s/\\appendix\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -+ s/\\section\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; - - # Try to delimit by : -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsubsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsubsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; - s/\\subsubsec\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(.*)$/"\n\n$tr{podleader}head3 " . indexify("$1") . "$3\n\n"/me; - s/\\subseckbd\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; - # Try to delimit by ' ' -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsec(?:title)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]*})+)}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:title)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]*})+)\}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; - - # This is to skip preface in refcard: - /\Q$tr{podleader}\Ehead1|\\title(?![a-zA-Z])\s*\{/o and $seen_start = 1 -@@ -1097,18 +1097,18 @@ sub TeXprint_topod { - s/\$\$(.*?)\$\$\s*/\n\nS< >$tr{startcode}$1$tr{endcode}\n\n/gs; - s/\$([^\$]+)\$/$tr{startcode}$1$tr{endcode}/g; - -- s/\\s(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">"/ge; # -- s/\\(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">$1"/ge; -+ s/\\s(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">"/ge; # -+ s/\\(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">$1"/ge; - - # Conflict between different versions of PARI and refcard: --# s/\\(?:key|li)\s*{(.*)}\s*{(.+)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; --# s/\\(?:key|li)\s*{(.*)}\s*{}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; --# s/\\(key|var)(?![a-zA-Z])\s*{(\w+)}/C<$2>/mg; -- s/\\var\s*{X<(\w+)>(\w+)}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -- s/\\var\s*{f{}lag}/$tr{startcode}flag$tr{endcode}/mg; -- -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{\s*(\w+)(?=C\<)(.*)}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{(.*)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{(.+)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{\}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; -+# s/\\(key|var)(?![a-zA-Z])\s*\{(\w+)\}/C<$2>/mg; -+ s/\\var\s*\{X<(\w+)>(\w+)\}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -+ s/\\var\s*\{f\{\}lag\}/$tr{startcode}flag$tr{endcode}/mg; -+ -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{\s*(\w+)(?=C\<)(.*)\}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{(.*)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; - s/C\<\{\}=/C\<=/g; - s/\\fl(?![a-zA-Z])/I/g; - s/\\file(?![a-zA-Z])/F/g; diff --git a/build/pkgs/pari/spkg-install b/build/pkgs/pari/spkg-install index 6a7b1ebc9d3..55a64aec9a8 100755 --- a/build/pkgs/pari/spkg-install +++ b/build/pkgs/pari/spkg-install @@ -206,7 +206,14 @@ set_environment # Set CFLAGS if [ "$SAGE_DEBUG" = yes ]; then # Disable optimisation, add debug symbols. - CFLAGS="$CFLAGS -O0 -g" + CFLAGS="-O0 -g $CFLAGS" + + # Compile kernel files with -O1 instead of -funroll-loops; -O0 gives + # a segmentation fault on some OS X systems when doing + # factor(10356613*10694706299664611221) + # See #13921, also reported upstream: + # - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html + PARI_MAKEFLAGS="KERNELCFLAGS=-O1 $PARI_MAKEFLAGS" else # Use PARI's default CFLAGS (with -g added). # PARI's Configure adds -O3 to the CFLAGS, so we don't need to add diff --git a/build/pkgs/pari_galdata/SPKG.txt b/build/pkgs/pari_galdata/SPKG.txt index ea272bcbbf7..c7100da29ba 100644 --- a/build/pkgs/pari_galdata/SPKG.txt +++ b/build/pkgs/pari_galdata/SPKG.txt @@ -9,10 +9,6 @@ degrees 8 through 11. GPL version 2+ -== SPKG Maintainers == - -* Jeroen Demeyer - == Upstream Contact == http://pari.math.u-bordeaux.fr/ diff --git a/build/pkgs/pari_galdata/dependencies b/build/pkgs/pari_galdata/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_galdata/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pari_jupyter/SPKG.txt b/build/pkgs/pari_jupyter/SPKG.txt new file mode 100644 index 00000000000..681cdc57f23 --- /dev/null +++ b/build/pkgs/pari_jupyter/SPKG.txt @@ -0,0 +1,22 @@ += pari_jupyter = + +== Description == + +A Jupyter kernel for PARI/GP + +== License == + +GPL version 3 or later + +== Upstream Contact == + +* https://github.com/jdemeyer/pari_jupyter +* Jeroen Demeyer + +== Dependencies == + +* Jupyter 4 +* Python (tested with 2.7.9) +* Cython (git master) +* PARI (git master) +* GMP or MPIR (any version which works with PARI) diff --git a/build/pkgs/pari_jupyter/checksums.ini b/build/pkgs/pari_jupyter/checksums.ini new file mode 100644 index 00000000000..d3b36f6aebb --- /dev/null +++ b/build/pkgs/pari_jupyter/checksums.ini @@ -0,0 +1,4 @@ +tarball=pari_jupyter-VERSION.tar.gz +sha1=404df06171e68056d9efe8a29804204138488885 +md5=902b290a997128e6be949c0bec44ca6e +cksum=3922118226 diff --git a/build/pkgs/pari_jupyter/dependencies b/build/pkgs/pari_jupyter/dependencies new file mode 100644 index 00000000000..5cf512f3f56 --- /dev/null +++ b/build/pkgs/pari_jupyter/dependencies @@ -0,0 +1 @@ +$(INST)/$(PARI) $(INST)/$(JUPYTER_CORE) | $(INST)/$(CYTHON) diff --git a/build/pkgs/pari_jupyter/package-version.txt b/build/pkgs/pari_jupyter/package-version.txt new file mode 100644 index 00000000000..3eefcb9dd5b --- /dev/null +++ b/build/pkgs/pari_jupyter/package-version.txt @@ -0,0 +1 @@ +1.0.0 diff --git a/build/pkgs/pari_jupyter/spkg-install b/build/pkgs/pari_jupyter/spkg-install new file mode 100755 index 00000000000..94ac1fed30e --- /dev/null +++ b/build/pkgs/pari_jupyter/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && ./setup.py install diff --git a/build/pkgs/pari_jupyter/type b/build/pkgs/pari_jupyter/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pari_jupyter/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/pari_seadata_small/SPKG.txt b/build/pkgs/pari_seadata_small/SPKG.txt index d5677307eb6..4db4fb26e5c 100644 --- a/build/pkgs/pari_seadata_small/SPKG.txt +++ b/build/pkgs/pari_seadata_small/SPKG.txt @@ -11,10 +11,6 @@ databases and computed by David R. Kohel. GPL version 2+ -== SPKG Maintainers == - -* Jeroen Demeyer - == Upstream Contact == http://pari.math.u-bordeaux.fr/ diff --git a/build/pkgs/pari_seadata_small/dependencies b/build/pkgs/pari_seadata_small/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_seadata_small/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/patch/SPKG.txt b/build/pkgs/patch/SPKG.txt index 69062cf7806..49e42dacbdb 100644 --- a/build/pkgs/patch/SPKG.txt +++ b/build/pkgs/patch/SPKG.txt @@ -17,10 +17,6 @@ it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. -== SPKG Maintainers == - -David Kirkby. david.kirkby@onetel.net - == Upstream Contact == Main web site: http://savannah.gnu.org/projects/patch/ diff --git a/build/pkgs/patch/dependencies b/build/pkgs/patch/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/patch/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/patchbot/SPKG.txt b/build/pkgs/patchbot/SPKG.txt new file mode 100644 index 00000000000..c34994b3818 --- /dev/null +++ b/build/pkgs/patchbot/SPKG.txt @@ -0,0 +1,26 @@ += SageMath patchbot = + +== Description == + +Apply branches and run tests on open Sage tickets. + +The patchbot is used to automate the testing of git branches. It has two +different aspects: a server side and a client side. + +Instructions for using the client side can be found at + +http://wiki.sagemath.org/buildbot/details + +== License == + +GPLv2+ + +== Upstream Contact == + +Robert Bradshaw +Frédéric Chapoton +https://github.com/robertwb/sage-patchbot/ + +== Dependencies == + +python, python-dateutil, sage-scripts diff --git a/build/pkgs/patchbot/checksums.ini b/build/pkgs/patchbot/checksums.ini new file mode 100644 index 00000000000..4a35077fa89 --- /dev/null +++ b/build/pkgs/patchbot/checksums.ini @@ -0,0 +1,4 @@ +tarball=patchbot-VERSION.tar.bz2 +sha1=1850ce7004fe49b669be0b53102d32e9095cc307 +md5=a84f244c2f6e6c715676a09028750b36 +cksum=1356602931 diff --git a/build/pkgs/patchbot/dependencies b/build/pkgs/patchbot/dependencies new file mode 100644 index 00000000000..4e1e0144211 --- /dev/null +++ b/build/pkgs/patchbot/dependencies @@ -0,0 +1 @@ +# No dependencies diff --git a/build/pkgs/patchbot/package-version.txt b/build/pkgs/patchbot/package-version.txt new file mode 100644 index 00000000000..f225a78adf0 --- /dev/null +++ b/build/pkgs/patchbot/package-version.txt @@ -0,0 +1 @@ +2.5.2 diff --git a/build/pkgs/patchbot/spkg-install b/build/pkgs/patchbot/spkg-install new file mode 100755 index 00000000000..5dcc96f4629 --- /dev/null +++ b/build/pkgs/patchbot/spkg-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +# Delete any currently existing patchbot +rm -rf "$SAGE_LOCAL/bin/patchbot" + +# Copy into final location. +# The sage-patchbot script knows how to call this... +cp -Rv src "$SAGE_LOCAL/bin/patchbot" diff --git a/build/pkgs/patchbot/type b/build/pkgs/patchbot/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/patchbot/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/pathpy/SPKG.txt b/build/pkgs/pathpy/SPKG.txt new file mode 100644 index 00000000000..1aec14146ab --- /dev/null +++ b/build/pkgs/pathpy/SPKG.txt @@ -0,0 +1,5 @@ += path.py = + +== Description == + +A module wrapper for os.path diff --git a/build/pkgs/pathpy/checksums.ini b/build/pkgs/pathpy/checksums.ini new file mode 100644 index 00000000000..915848d3117 --- /dev/null +++ b/build/pkgs/pathpy/checksums.ini @@ -0,0 +1,4 @@ +tarball=pathpy-VERSION.tar.bz2 +sha1=880b92213fb7dc1fb4a8dc5313af5560418e4cb3 +md5=dbae978139ec214c055fa78a54a60407 +cksum=1745717847 diff --git a/build/pkgs/pathpy/dependencies b/build/pkgs/pathpy/dependencies new file mode 100644 index 00000000000..e6f6ec07031 --- /dev/null +++ b/build/pkgs/pathpy/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(SETUPTOOLS_SCM) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pathpy/package-version.txt b/build/pkgs/pathpy/package-version.txt new file mode 100644 index 00000000000..0f0fefae5ac --- /dev/null +++ b/build/pkgs/pathpy/package-version.txt @@ -0,0 +1 @@ +7.1 diff --git a/build/pkgs/pathpy/spkg-install b/build/pkgs/pathpy/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/pathpy/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/pathpy/type b/build/pkgs/pathpy/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pathpy/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/pexpect/SPKG.txt b/build/pkgs/pexpect/SPKG.txt index e51db676922..5c0dfacd5ca 100644 --- a/build/pkgs/pexpect/SPKG.txt +++ b/build/pkgs/pexpect/SPKG.txt @@ -9,10 +9,6 @@ controlling them; and responding to expected patterns in their output. Python Software Foundation License -== SPKG Maintainers == - - * William Stein - == Upstream Contact == diff --git a/build/pkgs/pickleshare/SPKG.txt b/build/pkgs/pickleshare/SPKG.txt new file mode 100644 index 00000000000..3e67a43c105 --- /dev/null +++ b/build/pkgs/pickleshare/SPKG.txt @@ -0,0 +1,14 @@ += pickleshare = + +== Description == + +PickleShare - a small 'shelve' like datastore with concurrency support + +Like shelve, a PickleShareDB object acts like a normal dictionary. Unlike +shelve, many processes can access the database simultaneously. Changing a +value in database is immediately visible to other processes accessing the +same database. + +Concurrency is possible because the values are stored in separate files. +Hence the "database" is a directory where all files are governed by +PickleShare. diff --git a/build/pkgs/pickleshare/checksums.ini b/build/pkgs/pickleshare/checksums.ini new file mode 100644 index 00000000000..90c7efe70ec --- /dev/null +++ b/build/pkgs/pickleshare/checksums.ini @@ -0,0 +1,4 @@ +tarball=pickleshare-VERSION.tar.gz +sha1=fba6e858ca5c83caca25509cb5b8bd41c49d233a +md5=25337740507cb855ad58bfcf60f7710e +cksum=1340562053 diff --git a/build/pkgs/pickleshare/dependencies b/build/pkgs/pickleshare/dependencies new file mode 100644 index 00000000000..7c263299a84 --- /dev/null +++ b/build/pkgs/pickleshare/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(SETUPTOOLS) $(INST)/$(PATHPY) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pickleshare/package-version.txt b/build/pkgs/pickleshare/package-version.txt new file mode 100644 index 00000000000..2eb3c4fe4ee --- /dev/null +++ b/build/pkgs/pickleshare/package-version.txt @@ -0,0 +1 @@ +0.5 diff --git a/build/pkgs/pickleshare/spkg-install b/build/pkgs/pickleshare/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/pickleshare/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/pickleshare/type b/build/pkgs/pickleshare/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pickleshare/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/pkgconf/SPKG.txt b/build/pkgs/pkgconf/SPKG.txt index c6a225879dc..1ba83f5b08a 100644 --- a/build/pkgs/pkgconf/SPKG.txt +++ b/build/pkgs/pkgconf/SPKG.txt @@ -9,10 +9,6 @@ dependencies. ISC License (equivalent to Simplified BSD) -== SPKG Maintainers == - -* Volker Braun - == Upstream Contact == https://github.com/pkgconf/pkgconf diff --git a/build/pkgs/pkgconfig/SPKG.txt b/build/pkgs/pkgconfig/SPKG.txt index 434c0d2b79f..13b53ecbb6e 100644 --- a/build/pkgs/pkgconfig/SPKG.txt +++ b/build/pkgs/pkgconfig/SPKG.txt @@ -9,10 +9,6 @@ line tool. MIT License -== SPKG Maintainers == - -* Volker Braun - == Upstream Contact == https://github.com/matze/pkgconfig diff --git a/build/pkgs/planarity/SPKG.txt b/build/pkgs/planarity/SPKG.txt index 2781a2b62cf..19d3ca23acb 100644 --- a/build/pkgs/planarity/SPKG.txt +++ b/build/pkgs/planarity/SPKG.txt @@ -15,10 +15,6 @@ subgraph isolation). New BSD License -== SPKG Maintainers == - -Nathann Cohen (nathann.cohen@gmail.com) - == Upstream Contact == * https://code.google.com/p/planarity/ diff --git a/build/pkgs/planarity/dependencies b/build/pkgs/planarity/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/planarity/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/plantri/SPKG.txt b/build/pkgs/plantri/SPKG.txt index 1ce9c7005d2..24defea2632 100644 --- a/build/pkgs/plantri/SPKG.txt +++ b/build/pkgs/plantri/SPKG.txt @@ -17,9 +17,6 @@ cases outputs may be isomorphic as abstract graphs. == License == Plantri is distributed without a license. -== SPKG Maintainers == -Nico Van Cleemput (nico.vancleemput@gmail.com) - == Upstream Contact == Gunnar Brinkmann University of Ghent diff --git a/build/pkgs/plantri/dependencies b/build/pkgs/plantri/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/plantri/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/polybori/SPKG.txt b/build/pkgs/polybori/SPKG.txt deleted file mode 100644 index 47810cda6b0..00000000000 --- a/build/pkgs/polybori/SPKG.txt +++ /dev/null @@ -1,248 +0,0 @@ -= PolyBoRi = - -== Description == - -The core of PolyBoRi is a C++ library, which provides high-level data -types for Boolean polynomials and monomials, exponent vectors, as well -as for the underlying polynomial rings and subsets of the powerset of -the Boolean variables. As a unique approach, binary decision diagrams -are used as internal storage type for polynomial structures. On top of -this C++-library we provide a Python interface. This allows parsing of -complex polynomial systems, as well as sophisticated and extendable -strategies for Gröbner base computation. PolyBoRi features a powerful -reference implementation for Gröbner basis computation. - -==Maintainers == - - * Martin Albrecht - * Alexander Dreyer - * Burcin Eröcal - -== Upstream Contact == - - * Author: The PolyBoRi Team - * Email: brickenstein@mfo.de (Michael Brickenstein) - alexander.dreyer@itwm.fraunhofer.de (Alexander Dreyer) - * Website: http://polybori.sourceforge.net/ - -== Dependencies == - - * GNU patch - * Python - * Scons - * Boost - * M4RI - * png/libpng12 (accomplished because Python and M4RI depend on it) - * libz (accomplished because Python and libpng depend on it) - -== Patches == - -All previously contributed patches were merged upstream. - -== Special Files == - - * custom.py - configuration file for PolyBoRi's build system, - sets local prefix and install paths to Sage directories - -== Changelog == - -=== polybori-0.8.3 (Alexander Dreyer, 30 January 2013) === - * Updating sources to PolyBoRi's release 0.8.3 (Sage Trac #13989) - -=== polybori-0.8.2.p0 (Alexander Dreyer, 30 December 2012) === - * Changing unnecessary assertion to exception (Sage Trac #13883) - -=== polybori-0.8.2 (Alexander Dreyer, 26 June 2012) === - * Updating sources to PolyBoRi's release 0.8.2 (Sage Trac #13124) - -=== polybori-0.8.1.p2 (Jeroen Demeyer, 18 May 2012) === - * Trac #12963: Disable the Boost testing framework when - SAGE_FAT_BINARY=yes. This prevents linking against a system - -lboost_unit_test_framework library (which Sage doesn't provide). - * Move sources from src/polybori-0.8.1 to src/ - * Small cleanup of SPKG.txt and spkg-install. - -=== polybori-0.8.1.p1 (Alexander Dreyer, March 26th, 2012) === - * Rebased spkg on polybori-0.8.0.p2 (Sage Trac #12750) - * Working around broken scons on sun (Sage Trac #12655, comment:29) - * Added -Wno-long-long to CXXFLAGS (accompanying -std=c++98, comment:55) - -=== polybori-0.8.1.p0 (Alexander Dreyer, March 7th, 2012) === - * Updating sources to PolyBoRi's release 0.8.1 (Sage Trac #12655) - -=== polybori-0.8.0.p2 (Alexander Dreyer, March 26th, 2012) === - * Fix scoping/name look-up issue and support flags from the environment - -=== polybori-0.8.0.p1 (Alexander Dreyer, September 28th, 2011) === - * improved detection of libM4RI flags (backport from upcoming 0.8.1) - -=== polybori-0.8.0.p0 (Alexander Dreyer, September 8th, 2011) === - * Updating sources to PolyBoRi's release 0.8.0 - -=== polybori-0.8-rc.p3 (Alexander Dreyer, September 2nd, 2011) === - * Updating sources to rc5 and merged with preliminary polybori-0.7.1.p5.spkg - -=== polybori-0.8-rc.p2 (Alexander Dreyer, August 17th, 2011) === - * Updating sources to rc3 and merged with official polybori-0.7.1.p4.spkg - -=== polybori-0.8-rc.p1 (Alexander Dreyer, August 15th, 2011) === - * Updating sources to recent release candidate rc2 of PolyBoRi 0.8 - -=== polybori-0.8-rc.p0 (Alexander Dreyer, August 10th, 2011) === - * Updating sources to release candidate of PolyBoRi 0.8 - -=== polybori-0.8-alpha.p3 (Alexander Dreyer, August 5th, 2011) === - * Removing workaround for missing default constructors from sources - -=== polybori-0.8-alpha.p2 (Alexander Dreyer, August 4th, 2011) === - * Updating sources to PolyBoRi 0.8 alpha1 - -=== polybori-0.8-alpha.p1 (Alexander Dreyer, July 15th, 2011) === - * First working Version of the PolyBoRi 0.8 spkg - -=== polybori-0.7.1.p6 (Alexander Dreyer, 19 September, 2011) === - * #11574: add -msse2 only of __M4RI_HAVE_SSE2 is defined *and* nonzero - -=== polybori-0.7.1.p5 (Alexander Dreyer, 28 August, 2011) === - * #11574: add -msse2 option to compile flags if m4ri uses sse2 - -=== polybori-0.7.1.p4 (Alexander Dreyer, August 9th, 2011) === - * Rebased on polybori-0.7.0.p4 - -=== polybori-0.7.0.p4 (Jeroen Demeyer, 8 August 2011) === - * Trac #11664: Make all files world-readable - -=== polybori-0.7.1.p3 (Alexander Dreyer, April 27th, 2011) === - * Another backport from 0.8: use jinja2 for plotting (if available) - -=== polybori-0.7.1.p2 (Alexander Dreyer, April 27th, 2011) === - * Rebase on Sage 4.7's official spkg for PolyBoRi 0.7-0 - -=== polybori-0.7.1.p1 (Alexander Dreyer, April 27th, 2011) === - * Backporting fix for compatibility with new M4RI from PolyBoRi 0.8 - -=== polybori-0.7.1.p0 (Alexander Dreyer, April 27th, 2011) === - * Upgrading sources to PolyBoRi 0.7.1 (#12261) - -=== polybori-0.7.0.p3 (Alexander Dreyer, May 13th, 2011) === - * Fixed building on OS X 10.4 PPC G4 (#11331) - -=== polybori-0.7.0.p2 (Alexander Dreyer, March 30th, 2011) === - * Fixed building on OpenSolaris with gcc-4.6.0 (#11083) - -=== polybori-0.7.0.p1 (Alexander Dreyer, March 8th, 2011) === - * backporting a bugfix - -=== polybori-0.7.0.p0 (Alexander Dreyer, February 25th, 2011) === - * fixing SIGSEGVs - -=== polybori-0.7.0 (Martin Albrecht, February 18th, 2011) === - * New upstream release. - -=== polybori-0.6.4.p6 (undocumented, September 9th, 2010) === - -=== polybori-0.6.4.p5 (Alexander Dreyer, August 20th, 2010) === - * Backporting a fix from upcoming PolyBoRi 0.7 which allows linking - PolyBoRi as dynmaic library (#9768). - -=== polybori-0.6.4.p4 (Alexander Dreyer, August 10th, 2010) === - * Importing PolyBoRi patches from upstream mercurial to fix - interaction with external M4RI library (#9717) - - http://bitbucket.org/brickenstein/polybori/changeset/6ef7402d935b - - http://bitbucket.org/brickenstein/polybori/changeset/b692c8181e94 - -=== polybori-0.6.4.p3 (Martin Albrecht, August 10th, 2010) === - * Applying a patch by Alexander Dreyer to fix error in some crypto - example (#9717) - -=== polybori-0.6.4.p2 (Leif Leonhardy, July 10th, 2010) === - * Removed Michael Abshoff from maintainer list (see #7738) - * Deleted Boost source tree again since it was split off into a separate - spkg (see below, 0.5.rc.p7), modified spkg-install accordingly - * Deleted M4RI source tree, because it is a standard Sage package - * Little clean-up and minor fixes in patches/custom.py - - Note that CFLAGS etc. are still *overwritten* rather than modified! - * Updated "Dependencies" section above - -=== polybori-0.6.4.p1 (Mike Hansen, May 26th, 2010) === - * Added a patch to make PolyBoRi build on Cygwin (#7337) - -=== polybori-0.6.4 (Burcin Erocal and Alexander Dreyer, March 12th, 2010) === - * upgraded to current upstream release - * removed obsolete patches (SConstruct, cpu_stats.c) - * removed obsolete work arounds (delete shared libraries and touch pbori.pyx) - -=== polybori-0.6.3-r1647-20091028 (Martin Albrecht, October 29th, 2009) === - * upgraded to current mercurial tip (which did pass the PolyBoRi testsuite last night) - -=== polybori-0.6.3-20090827 (Martin Albrecht, August 27th, 2009) === - * create flags.conf if it doesnt exist - -=== polybori-0.6.3-20090825 (Martin Albrecht, August 25th, 2009) === - * fixes for solaris - -=== polybori-0.6.3-20090820 (Martin Albrecht, August 20th, 2009) === - * fixing a few performance bottlenecks - -=== polybori-0.6.3-20090728 (Martin Albrecht, July 28th, 2009) === - * new upstream release - * lots of refactoring - -=== polybori-0.5rc.p7 (Michael Abshoff, May 15th, 2009) === - * Split cropped boost from the PolyBori.spkg - -=== polybori-0.5rc.p6 (Michael Abshoff, November 30th, 2008) === - * Apply patch by Alexander Dreyer to fix manpage permission issues (#4321) - * Fix permission issues for boost includes - -=== polybori-0.5rc.p4 (Michael Abshoff, September 9th, 2008) === - * only force a rebuild of the PolyBoRi extension if the Sage library has been installed - -=== polybori-0.5rc.p3 (Michael Abshoff, September 7th, 2008) === - * Delete dynamic libs to force static linking - * touch pbori.pyx to force a rebuild of the extension - -=== polybori-0.5rc.p2 (Michael Abshoff, September 6th, 2008) === - * Backport PolyBoRi 0.3.x fixes - -=== polybori-0.5rc.p1 (Martin Albrecht, September 1st, 2008) === - * make PolyBoRi use the dynamic libm4ri - -=== polybori-0.5rc.p0 (Martin Albrecht, September 1st, 2008) === - * fixed build problems. - -=== polybori-0.5rc (Tim Abbott, Jul 10, 2008) === - * update to PolyBoRi-0.5rc release. - * Remove patches for problems fixed upstream. - -=== polybori-0.3.1.p3 (Michael Abshoff, William Stein, May 17, 2008) === - * cygwin support by patching cpu_stat.c (this should be upstreamed) - * Cygwin support by modifying SConstruct - -=== polybori-0.3.1.p2 (Michael Abshoff, April 26th, 2008) === - * remove dead link (fixes #3017) - -=== polybori-0.3.1.p1 (Michael Abshoff, April 11th, 2008) === - * Update CCuddCore.h - fixes memory leak (Alexander Dreyer - see #2822) - * Fix "Invalid read" cause by linking multiple dynamic libraries (Michael Abshoff, #2822 - * add -fPIC to the default build flags in custom.py - -=== polybori-0.3.1.p0 (Michael Abshoff, April 9th, 2008) === - * add debug info the the default build flags - * fix OSX 10.4 specific build issue (#2865) - -=== polybori-0.3.1 (Burcin Erocal) === - * update to PolyBoRi-0.3.1 release - * remove patches required for the previous version - * reset hg repository - -=== polybori-0.1-r7 (Ralf-Philipp Weinmann) === - * pulled in memleak fix in groebner/src/nf.cc from PolyBoRi repo - * comment changes on precomputed Groebner base tables pulled in from repo - these remove claims about possible patents. - -=== polybori-0.1-r6 (Michael Abshoff) === - * add ENV = os.environ to SConstruct (fixes #1553) - -=== prior releases === - * lost in the dark ages diff --git a/build/pkgs/polybori/checksums.ini b/build/pkgs/polybori/checksums.ini deleted file mode 100644 index 9f08f12d53b..00000000000 --- a/build/pkgs/polybori/checksums.ini +++ /dev/null @@ -1,4 +0,0 @@ -tarball=polybori-VERSION.tar.bz2 -sha1=74909ce4912905135f54c0ba9c3ec8c71622ee1f -md5=0022954d595f09684fd7113325d9bbfd -cksum=2035176982 diff --git a/build/pkgs/polybori/custom.py b/build/pkgs/polybori/custom.py deleted file mode 100644 index 51e9283b6fc..00000000000 --- a/build/pkgs/polybori/custom.py +++ /dev/null @@ -1,76 +0,0 @@ -import os -import sys - -CCFLAGS += ["-Wreturn-type"] - -# Note: PolyBoRi still appends DEFAULT_*FLAGS (overwrite those, if necessary) -if 'CFLAGS' in os.environ: - CFLAGS = os.environ['CFLAGS'].split(' ') - -if 'CXXFLAGS' in os.environ: - CXXFLAGS = ["-Wno-long-long"] + os.environ['CXXFLAGS'].split(' ') - -if 'CPPFLAGS' in os.environ: - CCFLAGS = os.environ['CPPFLAGS'].split(' ') - -GD_LIBS+=["png12","z"] - -# FIXME: Should we include LDFLAGS here? (see above) -if not globals().has_key("LINKFLAGS"): LINKFLAGS=[] # s.t. we can *append* below - -print "Platform: ", sys.platform - -if 'sunos' in sys.platform: - try: - SHLINKFLAGS = os.environ['SAGESOFLAGS'] - except: - SHLINKFLAGS = ["$LINKFLAGS", "-shared", "${_sonamecmd(SONAMEPREFIX, TARGET, SONAMESUFFIX, __env__)}"] - - -if sys.platform=='darwin': - FORCE_HASH_MAP=True - - -if os.environ.get('SAGE_DEBUG', "no") == "yes": - CPPDEFINES=[] - CCFLAGS=["-pg"] + CCFLAGS - CXXFLAGS=["-pg"] + CXXFLAGS - LINKFLAGS=["-pg"] + LINKFLAGS - -if os.environ.get('SAGE64', "no") == "yes": - print "Building a 64-bit version of PolyBoRi" - CCFLAGS=["-m64"] + CCFLAGS - CXXFLAGS=["-m64"] + CXXFLAGS - LINKFLAGS=["-m64"] + LINKFLAGS - -CPPPATH=[os.environ['SAGE_LOCAL']+"/include"] -LIBPATH=[os.environ['SAGE_LOCAL']+"/lib"] - -PYPREFIX=os.environ['PYTHONHOME'] -PBP=os.environ['PYTHONHOME']+'/bin/python' - -M4RIURL="" - -HAVE_DOXYGEN=False -HAVE_PYDOC=False -HAVE_L2H=False -HAVE_HEVEA=False -HAVE_TEX4HT=False -HAVE_PYTHON_EXTENSION=False -EXTERNAL_PYTHON_EXTENSION=True -# Disable the Boost testing framework on distributable binaries to -# prevent linking against a system -lboost_unit_test_framework -# library. -if os.environ.get('SAGE_FAT_BINARY', "no") == "yes": - BOOST_TEST="" - -# (CC and CXX should have been set by sage-env, but never mind...:) -try: - CC = os.environ['CC'] -except KeyError: - pass - -try: - CXX = os.environ['CXX'] -except KeyError: - pass diff --git a/build/pkgs/polybori/package-version.txt b/build/pkgs/polybori/package-version.txt deleted file mode 100644 index ee94dd834b5..00000000000 --- a/build/pkgs/polybori/package-version.txt +++ /dev/null @@ -1 +0,0 @@ -0.8.3 diff --git a/build/pkgs/polybori/patches/empty.patch b/build/pkgs/polybori/patches/empty.patch deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/build/pkgs/polybori/spkg-install b/build/pkgs/polybori/spkg-install deleted file mode 100755 index a75d15a1d3b..00000000000 --- a/build/pkgs/polybori/spkg-install +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -if [ "$SAGE_LOCAL" = "" ]; then - echo "SAGE_LOCAL undefined ... exiting"; - echo "Maybe run 'sage -sh'?" - exit 1 -fi - -WORKDIR=${PWD}/src -SCONS=scons - -prepare_polybori() -{ - if [ $UNAME = "CYGWIN" ]; then - export PROGSUFFIX=.exe - fi - - # place configuration file - cp ../custom.py . - if [ $? -ne 0 ]; then - echo >&2 "Error copying 'custom.py'" - exit 1 - fi - - # Apply patches. See SPKG.txt for information about what each patch - # does. - for patch in ../patches/*.patch; do - patch -p1 < "$patch" - if [ $? -ne 0 ]; then - echo >&2 "Error applying '$patch'" - exit 1 - fi - done -} - -build_polybori() -{ - ${SCONS} prepare-install ${MAKEOPTS} - if [ $? -ne 0 ]; then - echo "Error building PolyBoRi." - exit 1 - fi -} - -install_polybori() -{ - ${SCONS} install install-headers "PREFIX=${SAGE_LOCAL}" \ - "INSTALLDIR=${SAGE_LOCAL}/share/polybori" \ - "CONFFILE=${SAGE_LOCAL}/share/polybori/flags.conf" - if [ $? -ne 0 ]; then - echo "Error installing PolyBoRi." - exit 1 - fi - # rm -rf "${SAGE_LOCAL}/lib/python/site-packages/polybori" -} - -clean_polybori() -{ - rm -f "$SAGE_LOCAL"/lib/libpolybori* - rm -f "$SAGE_LOCAL"/lib/libpboriCudd* - rm -f "$SAGE_LOCAL"/lib/libgroebner* - rm -rf "$SAGE_LOCAL"/include/polybori* - rm -rf "$SAGE_LOCAL"/include/cudd -} - -cd ${WORKDIR} - -prepare_polybori -echo "Starting build..." - -echo "Running build_polybori..." -build_polybori -echo "Done build_polybori." - -echo "Removing old PolyBoRi install..." -clean_polybori -echo "Done removing old PolyBoRi install." - -echo "Installing PolyBoRi..." -install_polybori -echo "Done installing PolyBoRi." diff --git a/build/pkgs/polytopes_db/SPKG.txt b/build/pkgs/polytopes_db/SPKG.txt index 51b0bcb2a7a..12375de91bd 100644 --- a/build/pkgs/polytopes_db/SPKG.txt +++ b/build/pkgs/polytopes_db/SPKG.txt @@ -16,10 +16,6 @@ ReflexivePolytope and ReflexivePolytopes commands. GPL -== SPKG Maintainers == - -Andrey Novoseltsev - == Dependencies == None diff --git a/build/pkgs/polytopes_db/dependencies b/build/pkgs/polytopes_db/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/polytopes_db/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ppl/SPKG.txt b/build/pkgs/ppl/SPKG.txt index b95f2ac3095..63f964039bd 100644 --- a/build/pkgs/ppl/SPKG.txt +++ b/build/pkgs/ppl/SPKG.txt @@ -25,9 +25,6 @@ Benchmarks are included in this paper: http://arxiv.org/abs/cs/0612085 == License == GPL v3+ -== SPKG Maintainers == -* Volker Braun - == Upstream Contact == http://www.cs.unipr.it/ppl/ BUGSENG srl (http://bugseng.com) diff --git a/build/pkgs/pybtex/type b/build/pkgs/pybtex/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/pybtex/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/pycrypto/SPKG.txt b/build/pkgs/pycrypto/SPKG.txt index 6ff3ae4b50b..e9f89f5b1cb 100644 --- a/build/pkgs/pycrypto/SPKG.txt +++ b/build/pkgs/pycrypto/SPKG.txt @@ -12,10 +12,6 @@ programs that require cryptographic functions. Mostly dedicated to the public domain, with some files covered by the Python 2.2 license. -== SPKG Maintainers == - - * Minh Van Nguyen - == Upstream Contact == * Website: www.pycrypto.org diff --git a/build/pkgs/pyflakes/type b/build/pkgs/pyflakes/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/pyflakes/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/pygments/SPKG.txt b/build/pkgs/pygments/SPKG.txt index eaa02d49174..b0b7e9a56f2 100644 --- a/build/pkgs/pygments/SPKG.txt +++ b/build/pkgs/pygments/SPKG.txt @@ -12,10 +12,6 @@ to prettify source code. Modified BSD -== SPKG Maintainers == - -Mike Hansen - == Upstream Contact == Author: Georg Brandl diff --git a/build/pkgs/pynac/SPKG.txt b/build/pkgs/pynac/SPKG.txt index d59d8b73737..fe87c09f20c 100644 --- a/build/pkgs/pynac/SPKG.txt +++ b/build/pkgs/pynac/SPKG.txt @@ -8,10 +8,6 @@ A modified version of GiNaC that replaces the dependency on CLN by Python. GPL V2+ -== SPKG Maintainers == - - * Burcin Erocal - burcin spam.erocal.org - == Upstream Contact == * Burcin Erocal - burcin spam.erocal.org diff --git a/build/pkgs/pynac/checksums.ini b/build/pkgs/pynac/checksums.ini index efc93ed0ab4..37f6d1c05a2 100644 --- a/build/pkgs/pynac/checksums.ini +++ b/build/pkgs/pynac/checksums.ini @@ -1,4 +1,4 @@ tarball=pynac-VERSION.tar.bz2 -sha1=ef031834c14780d71c7d53b739e32559a0fa0434 -md5=615c0ee928ef4f9e25caaba485559929 -cksum=3373265562 +sha1=7bfef924e9622ad0756becf3c16b71eb0eb10503 +md5=8c3cd5db06a1aa2048cb6ec54406ab17 +cksum=1343245112 diff --git a/build/pkgs/pynac/package-version.txt b/build/pkgs/pynac/package-version.txt index 8ef1e6f217f..0ee8af2ba1c 100644 --- a/build/pkgs/pynac/package-version.txt +++ b/build/pkgs/pynac/package-version.txt @@ -1 +1 @@ -0.3.9.2 +0.3.9.5 diff --git a/build/pkgs/pyopenssl/type b/build/pkgs/pyopenssl/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/pyopenssl/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/pyparsing/SPKG.txt b/build/pkgs/pyparsing/SPKG.txt index 47428d5a8fd..be9bcc4075a 100644 --- a/build/pkgs/pyparsing/SPKG.txt +++ b/build/pkgs/pyparsing/SPKG.txt @@ -8,10 +8,6 @@ A Python Parsing Module MIT License -== SPKG Maintainers == - -John H. Palmieri - == Upstream Contact == Author: Paul McGuire diff --git a/build/pkgs/python2/SPKG.txt b/build/pkgs/python2/SPKG.txt index c52392a0140..8e26a025f45 100644 --- a/build/pkgs/python2/SPKG.txt +++ b/build/pkgs/python2/SPKG.txt @@ -22,12 +22,6 @@ applies to all of them. Python's licensing is GPL compatible. For more details see http://www.python.org/psf/license/ -== SPKG Maintainers == - * William Stein - * Mike Hansen - * Craig Citro - * Volker Braun - == Upstream Contact == There are a large number of community resources. For more details see diff --git a/build/pkgs/python_igraph/SPKG.txt b/build/pkgs/python_igraph/SPKG.txt new file mode 100644 index 00000000000..35f12bf1a95 --- /dev/null +++ b/build/pkgs/python_igraph/SPKG.txt @@ -0,0 +1,23 @@ += python-igraph = + +== Description == + +igraph is a library for creating and manipulating graphs. +It is intended to be as powerful (ie. fast) as possible to enable the +analysis of large graphs. + +== License == + +GPL version 2 + +== Upstream Contact == + +http://igraph.org/python/ + +== Dependencies == + +* python +* readline +* igraph + +== Special Update/Build Instructions == diff --git a/build/pkgs/python_igraph/checksums.ini b/build/pkgs/python_igraph/checksums.ini new file mode 100644 index 00000000000..e1bf34b107f --- /dev/null +++ b/build/pkgs/python_igraph/checksums.ini @@ -0,0 +1,4 @@ +tarball=python_igraph-VERSION.tar.gz +sha1=3a01e98962d96061e5f2383445c7f7a86b2759cc +md5=088e816732d02b84119183e3d82a9deb +cksum=680376365 diff --git a/build/pkgs/python_igraph/dependencies b/build/pkgs/python_igraph/dependencies new file mode 100644 index 00000000000..a23b62d7989 --- /dev/null +++ b/build/pkgs/python_igraph/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(IGRAPH) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/python_igraph/package-version.txt b/build/pkgs/python_igraph/package-version.txt new file mode 100644 index 00000000000..bcaffe19b5b --- /dev/null +++ b/build/pkgs/python_igraph/package-version.txt @@ -0,0 +1 @@ +0.7.0 \ No newline at end of file diff --git a/build/pkgs/python_igraph/spkg-check b/build/pkgs/python_igraph/spkg-check new file mode 100644 index 00000000000..4172cc71738 --- /dev/null +++ b/build/pkgs/python_igraph/spkg-check @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd src +python setup.py check diff --git a/build/pkgs/python_igraph/spkg-install b/build/pkgs/python_igraph/spkg-install new file mode 100755 index 00000000000..684a8386010 --- /dev/null +++ b/build/pkgs/python_igraph/spkg-install @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +cd src + +python setup.py build +if [ $? -ne 0 ]; then + echo >&2 "Error building python_igraph." + exit 1 +fi + +python setup.py install +if [ $? -ne 0 ]; then + echo >&2 "Error installing python_igraph." + exit 1 +fi diff --git a/build/pkgs/python_igraph/spkg-src b/build/pkgs/python_igraph/spkg-src new file mode 100755 index 00000000000..a5ff069cc11 --- /dev/null +++ b/build/pkgs/python_igraph/spkg-src @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# This script must be run from folder SAGE_ROOT. It modifies the file +# python-igraph-0.7.0.tar.gz into python_igraph-0.7.0.tar.gz, and it modifies +# the name of the top folder accordingly. + + +if [ -z "$SAGE_ROOT" -o -z "$SAGE_DISTFILES" ]; then + echo >&2 "\$SAGE_ROOT or \$SAGE_DISTFILES undefined ... exiting"; + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + +PKG_DIR="$SAGE_ROOT/build/pkgs/python_igraph/" +VERSION=$(cat "$PKG_DIR/package-version.txt") +PYTHONIGRAPH=python-igraph-$VERSION +PYTHONIGRAPHMOD=python_igraph-$VERSION + +set -e +shopt -s extglob + +if [ -e $SAGE_ROOT/upstream/$PYTHONIGRAPHMOD-$VERSION.tar.gz ] +then + echo "The modified .tar file already exists." + exit +fi + +# work in a temporary directory +cd $(mktemp -d) + +mkdir src +cd src + +tar xzf <( curl -L "igraph.org/nightly/get/python/$PYTHONIGRAPH.tar.gz" ) + +if [ -e $PYTHONIGRAPH ] +then + mv $PYTHONIGRAPH $PYTHONIGRAPHMOD + tar -zcf $PYTHONIGRAPHMOD.tar.gz $PYTHONIGRAPHMOD + mv $PYTHONIGRAPHMOD.tar.gz $SAGE_ROOT/upstream/python_igraph-$VERSION.tar.gz + echo "Correctly downloaded/modified input file." +else + echo "Cannot find file $PYTHONIGRAPH.tar.gz." +fi +cd .. +rm -rf src \ No newline at end of file diff --git a/build/pkgs/python_igraph/type b/build/pkgs/python_igraph/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/python_igraph/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/pyzmq/SPKG.txt b/build/pkgs/pyzmq/SPKG.txt index 99022d790e1..86903427d03 100644 --- a/build/pkgs/pyzmq/SPKG.txt +++ b/build/pkgs/pyzmq/SPKG.txt @@ -8,12 +8,6 @@ Python bindings for the zeromq networking library. LGPLv3+ -== SPKG Maintainers == - -* Volker Braun -* Thierry Monteil -* William Stein - == Upstream Contact == http://www.zeromq.org diff --git a/build/pkgs/pyzmq/dependencies b/build/pkgs/pyzmq/dependencies index 0e4c6ddccd9..b59502c1128 100644 --- a/build/pkgs/pyzmq/dependencies +++ b/build/pkgs/pyzmq/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) $(INST)/$(ZEROMQ) $(INST)/$(SETUPTOOLS) +$(INST)/$(CYTHON) $(INST)/$(ZEROMQ) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/qepcad/SPKG.txt b/build/pkgs/qepcad/SPKG.txt new file mode 100644 index 00000000000..bafc948fffd --- /dev/null +++ b/build/pkgs/qepcad/SPKG.txt @@ -0,0 +1,37 @@ += qepcad = + +== Description == + +Qepcad is an implementation of quantifier elimination by partial cylindrical +algebraic decomposition + +== License == + +QEPCAD B +Copyright (c) 1990, 2008, Hoon Hong & Chris Brown (contact wcbrown@usna.edu) + +Permission to use, copy, modify, and/or distribute this software, including +source files, README files, etc., for any purpose with or without fee is +hereby granted, provided that the above copyright notice and this permission +notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +== Upstream Contact == + +Website: http://www.usna.edu/CS/qepcadweb/B/QEPCAD.html + +== Dependencies == + +* readline +* saclib + +== Special Update/Build Instructions == + +One might need to set MAKE to "make -j1" fo this to be built successfully. diff --git a/build/pkgs/qepcad/checksums.ini b/build/pkgs/qepcad/checksums.ini new file mode 100644 index 00000000000..687ed2a24e2 --- /dev/null +++ b/build/pkgs/qepcad/checksums.ini @@ -0,0 +1,4 @@ +tarball=qepcad-VERSION.tar.gz +sha1=44d2a7959a27f0cf3fdca9cb651529002b6c6609 +md5=8eb38dcc186076896837c75f2f206bb8 +cksum=1380984046 diff --git a/build/pkgs/qepcad/dependencies b/build/pkgs/qepcad/dependencies new file mode 100644 index 00000000000..7ba9f9c1115 --- /dev/null +++ b/build/pkgs/qepcad/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(READLINE) $(INST)/$(SACLIB) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. diff --git a/build/pkgs/qepcad/package-version.txt b/build/pkgs/qepcad/package-version.txt new file mode 100644 index 00000000000..fa0ca92c91f --- /dev/null +++ b/build/pkgs/qepcad/package-version.txt @@ -0,0 +1 @@ +B.1.69 diff --git a/build/pkgs/qepcad/patches/makefile_no_csh.patch b/build/pkgs/qepcad/patches/makefile_no_csh.patch new file mode 100644 index 00000000000..d981372b3fd --- /dev/null +++ b/build/pkgs/qepcad/patches/makefile_no_csh.patch @@ -0,0 +1,13 @@ +diff --git a/Makefile b/Makefile +index 4ea5102..2b3385c 100644 +--- a/Makefile ++++ b/Makefile +@@ -5,7 +5,7 @@ + # This simply calls qepcad's make, and the master-make for the + # extensions. + ################################################################## +-SHELL = /bin/csh ++# SHELL = /bin/csh + MAKE = make # You must use gmake, but it may not be + # called gmake on your system, so change this + diff --git a/build/pkgs/qepcad/sage-qepcad b/build/pkgs/qepcad/sage-qepcad new file mode 100755 index 00000000000..82c2d8fb735 --- /dev/null +++ b/build/pkgs/qepcad/sage-qepcad @@ -0,0 +1,6 @@ +#!/bin/bash + +qe=$SAGE_LOCAL && export qe + +$SAGE_LOCAL/bin/qepcad + diff --git a/build/pkgs/qepcad/spkg-install b/build/pkgs/qepcad/spkg-install new file mode 100755 index 00000000000..6da4a409be9 --- /dev/null +++ b/build/pkgs/qepcad/spkg-install @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting"; + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + + +cd qesource + + +# Test if saclib is installed +if [ ! -d "$SAGE_LOCAL/lib/saclib" ] ; then + echo >&2 'You should install saclib first.' + exit 1 +fi + + +# Apply patches. +echo 'Patching qepcad' +for patch in ../patches/*.patch; do + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + + +# build qepcad +saclib="$SAGE_LOCAL/lib/saclib" +export saclib +qe=$(pwd -P) +export qe +$MAKE -j1 opt + +if [ $? -ne 0 ]; then + echo >&2 "Error building qepcad." + exit 1 +fi + + +# install binaries to the Sage tree +find . -name *.a -exec cp {} $SAGE_LOCAL/lib/ \; +cp source/qepcad $SAGE_LOCAL/bin/ +cp bin/qepcad.help $SAGE_LOCAL/bin/ +cp ../sage-qepcad $SAGE_LOCAL/bin/ + diff --git a/build/pkgs/qepcad/type b/build/pkgs/qepcad/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/qepcad/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/r/checksums.ini b/build/pkgs/r/checksums.ini index f5de34d364f..558278775fb 100644 --- a/build/pkgs/r/checksums.ini +++ b/build/pkgs/r/checksums.ini @@ -1,4 +1,4 @@ tarball=R-VERSION.tar.gz -sha1=74476c4d356f227bf0f17c8c21e63459861fbc93 -md5=c2aac8b40f84e08e7f8c9068de9239a3 -cksum=1803379956 +sha1=68c74db1c5a2f2040280a03b8396e4d28a5a7617 +md5=57cef5c2e210a5454da1979562a10e5b +cksum=1992743709 diff --git a/build/pkgs/r/package-version.txt b/build/pkgs/r/package-version.txt index 95672bff2c6..d80cf902ec3 100644 --- a/build/pkgs/r/package-version.txt +++ b/build/pkgs/r/package-version.txt @@ -1 +1 @@ -3.2.1.p0 +3.2.2.p0 diff --git a/build/pkgs/r/spkg-install b/build/pkgs/r/spkg-install index 0e5f4f2c2a6..679d39e1b82 100755 --- a/build/pkgs/r/spkg-install +++ b/build/pkgs/r/spkg-install @@ -82,15 +82,14 @@ fi if [ "$UNAME" = "Darwin" ]; then # We don't want to install R as a library framework on OSX R_CONFIGURE="--enable-R-framework=no $R_CONFIGURE" + # OS X 10.10 and/or Xcode 6.3 and over broke the R installation. See + # http://trac.sagemath.org/ticket/18254. + if [ $MACOSX_VERSION -ge 14 ]; then + echo "OS X 10.$[$MACOSX_VERSION-4] Configuring R without aqua support." + R_CONFIGURE="--with-aqua=no $R_CONFIGURE" + fi fi -# OS X 10.10 and/or Xcode 6.3 broke the R installation. See -# http://trac.sagemath.org/ticket/18254. -if { uname -sr | grep 'Darwin 14\.' ;} &>/dev/null; then - echo "OS X 10.10: Configuring R without aqua support." - R_CONFIGURE="--with-aqua=no $R_CONFIGURE" -fi - if [ "$UNAME" = "CYGWIN" ]; then # Cygwin libm does not provide "long double" functions # and we do not install Cephes on Cygwin at the moment @@ -126,6 +125,12 @@ for patch in ../patches/*.patch; do fi done +if [ "$UNAME" = "Darwin" ]; then + # Fixing install_name(s) + sed -i -e 's:\"-install_name :\"-install_name ${libdir}/R/lib/:' configure + sed -i -e "/SHLIB_EXT/s/\.so/.dylib/" configure +fi + # Don't override R_HOME_DIR in local/bin/R while building R. # See patches/R.sh.patch export SAGE_BUILDING_R=yes diff --git a/build/pkgs/ratpoints/SPKG.txt b/build/pkgs/ratpoints/SPKG.txt index b886940916c..234ef54f70c 100644 --- a/build/pkgs/ratpoints/SPKG.txt +++ b/build/pkgs/ratpoints/SPKG.txt @@ -5,10 +5,6 @@ Michael Stoll's program which searches for rational points on hyperelliptic curves. -== SPKG Maintainers == - - * Robert Miller - == Upstream Contact == * Author: Michael Stoll diff --git a/build/pkgs/rpy2/SPKG.txt b/build/pkgs/rpy2/SPKG.txt index 80d56bf0370..806f1c946cf 100644 --- a/build/pkgs/rpy2/SPKG.txt +++ b/build/pkgs/rpy2/SPKG.txt @@ -11,8 +11,6 @@ Website: http://rpy.sourceforge.net/rpy2.html * GPL 2+ * Note that we have deleted references to Mozilla PL as an option, which we are allowed to do by the full rpy2 license in order to remain GPL-compatible -== SPKG Maintainers == - == Upstream Contact == * http://rpy.sourceforge.net/maillist.html diff --git a/build/pkgs/rubiks/dependencies b/build/pkgs/rubiks/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rubiks/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/rw/SPKG.txt b/build/pkgs/rw/SPKG.txt index 0877c8e74d3..7ab5243f2d4 100644 --- a/build/pkgs/rw/SPKG.txt +++ b/build/pkgs/rw/SPKG.txt @@ -10,10 +10,6 @@ http://pholia.tdi.informatik.uni-frankfurt.de/~philipp/software/rw.shtml GPL version 2 or later -== SPKG Maintainers == - -Nathann Cohen (nathann.cohen@gmail.com) - == Upstream Contact == Philipp Klaus Krause (philipp@informatik.uni-frankfurt.de) diff --git a/build/pkgs/rw/dependencies b/build/pkgs/rw/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rw/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/saclib/SPKG.txt b/build/pkgs/saclib/SPKG.txt new file mode 100644 index 00000000000..aa6bbcd4c05 --- /dev/null +++ b/build/pkgs/saclib/SPKG.txt @@ -0,0 +1,33 @@ += saclib = + +== Description == + +Saclib is a library of C programs for computer algebra derived from the SAC2 +system. It is mainly used as a dependency of qepcad. + +== License == + +Saclib 2.2 +Copyright (c) 1993, 2008, RISC-Linz (contact wcbrown@usna.edu) + +Permission to use, copy, modify, and/or distribute this software, including +source files, README files, etc., for any purpose with or without fee is +hereby granted, provided that the above copyright notice and this permission +notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +== Upstream Contact == + +Website: http://www.usna.edu/CS/qepcadweb/B/QEPCAD.html + +== Dependencies == + +None. + diff --git a/build/pkgs/saclib/checksums.ini b/build/pkgs/saclib/checksums.ini new file mode 100644 index 00000000000..0cf914823f6 --- /dev/null +++ b/build/pkgs/saclib/checksums.ini @@ -0,0 +1,4 @@ +tarball=saclib-VERSION.tar.gz +sha1=27a4eb0c7506e6f4cea653d888cb8986f9dcb6f2 +md5=93650273b3a1202731e96ddfd1cce852 +cksum=2534931240 diff --git a/build/pkgs/saclib/dependencies b/build/pkgs/saclib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/saclib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/saclib/package-version.txt b/build/pkgs/saclib/package-version.txt new file mode 100644 index 00000000000..bda8fbec154 --- /dev/null +++ b/build/pkgs/saclib/package-version.txt @@ -0,0 +1 @@ +2.2.6 diff --git a/build/pkgs/saclib/spkg-install b/build/pkgs/saclib/spkg-install new file mode 100755 index 00000000000..d2a8ae71bd1 --- /dev/null +++ b/build/pkgs/saclib/spkg-install @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + + +if [ "$SAGE_LOCAL" = "" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting"; + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + + +# build saclib +cd saclib* +saclib=$(pwd -P) +export saclib +bin/sconf && bin/mkproto && bin/mkmake && bin/mklib all +if [ $? -ne 0 ]; then + echo >&2 "Error building saclib." + exit 1 +fi + + +# install saclib to the Sage tree +cd .. +rm -rf $SAGE_LOCAL/lib/saclib +mv saclib* $SAGE_LOCAL/lib/saclib + diff --git a/build/pkgs/saclib/type b/build/pkgs/saclib/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/saclib/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/sage_mode/SPKG.txt b/build/pkgs/sage_mode/SPKG.txt index a605f0025ad..28145c3326a 100644 --- a/build/pkgs/sage_mode/SPKG.txt +++ b/build/pkgs/sage_mode/SPKG.txt @@ -26,10 +26,6 @@ along with sage-mode; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -== SPKG Maintainers == - - * Ivan Andrus - == Upstream Contact == * Maintainer: Ivan Andrus diff --git a/build/pkgs/sagenb/SPKG.txt b/build/pkgs/sagenb/SPKG.txt index 4402326bcb2..83cf729472b 100644 --- a/build/pkgs/sagenb/SPKG.txt +++ b/build/pkgs/sagenb/SPKG.txt @@ -9,13 +9,6 @@ mathematical software. GPLv3+ -== SPKG Maintainers == - - * Keshav Kini - * Jason Grout - * William Stein - * Tim Dumol - == Upstream Contact == * Keshav Kini diff --git a/build/pkgs/sagetex/SPKG.txt b/build/pkgs/sagetex/SPKG.txt index 14a27039753..1d454520ab0 100644 --- a/build/pkgs/sagetex/SPKG.txt +++ b/build/pkgs/sagetex/SPKG.txt @@ -27,10 +27,6 @@ Project Public License Distributed from CTAN archives in directory macros/latex/base/lppl.txt"; see [[http://mirror.ctan.org/macros/latex/base/lppl.txt]]. -== SPKG Maintainers == - -Dan Drake (ddrake at member ams org) - == Upstream Contact == Author: Dan Drake. Web: [[http://www.bitbucket.org/ddrake/sagetex]] and diff --git a/build/pkgs/scipy/SPKG.txt b/build/pkgs/scipy/SPKG.txt index 85866c0757a..9754f5f9f70 100644 --- a/build/pkgs/scipy/SPKG.txt +++ b/build/pkgs/scipy/SPKG.txt @@ -14,11 +14,6 @@ depended upon by some of the world's leading scientists and engineers. SciPy's license is free for both commercial and non-commercial use, under the BSD terms. See http://www.scipy.org/License_Compatibility -== SPKG Maintainers == - * William Stein - * Josh Kantor - * Jason Grout - == Upstream Contact == http://www.scipy.org/ diff --git a/build/pkgs/scons/SPKG.txt b/build/pkgs/scons/SPKG.txt index c9ca9f58385..7b822f26f9c 100644 --- a/build/pkgs/scons/SPKG.txt +++ b/build/pkgs/scons/SPKG.txt @@ -10,9 +10,6 @@ Website: http://www.scons.org/ X/MIT -== SPKG Maintainers == - * Joel B. Mohler - == Upstream Contact == * SCons mailing lists - see http://www.scons.org/lists.php diff --git a/build/pkgs/setuptools/SPKG.txt b/build/pkgs/setuptools/SPKG.txt index fdd69f141ee..6f9783df002 100644 --- a/build/pkgs/setuptools/SPKG.txt +++ b/build/pkgs/setuptools/SPKG.txt @@ -12,10 +12,6 @@ Website: http://pypi.python.org/pypi/setuptools/ PSF or ZPL. i.e Python Software Foundation License or Zope Public License -== SPKG Maintainers == - - * Jaap Spies, (j.spies@hccnet.nl) - == Upstream Contact == * Phillip J. Eby (distutils-sig@python org) diff --git a/build/pkgs/setuptools/checksums.ini b/build/pkgs/setuptools/checksums.ini index 0e6ee877cf8..00d733500d0 100644 --- a/build/pkgs/setuptools/checksums.ini +++ b/build/pkgs/setuptools/checksums.ini @@ -1,4 +1,4 @@ tarball=setuptools-VERSION.tar.gz -sha1=427e916ad99a704af54b7aa3124bd52d4ebf04d3 -md5=0160a882900196a8923007a0fd2e46b1 -cksum=2317540873 +sha1=066319dafd105cc7eb0d1c63aa2efa49d500a414 +md5=f72e87f34fbf07f299f6cb46256a0b06 +cksum=2781334732 diff --git a/build/pkgs/setuptools/package-version.txt b/build/pkgs/setuptools/package-version.txt index 800fd35ee86..33718932a44 100644 --- a/build/pkgs/setuptools/package-version.txt +++ b/build/pkgs/setuptools/package-version.txt @@ -1 +1 @@ -12.4 +18.1 diff --git a/build/pkgs/setuptools/patches/python-eggs-permissions.patch b/build/pkgs/setuptools/patches/python-eggs-permissions.patch deleted file mode 100644 index 83f5b71ae46..00000000000 --- a/build/pkgs/setuptools/patches/python-eggs-permissions.patch +++ /dev/null @@ -1,18 +0,0 @@ -Default to 755 permissions since checks later on test for non-group-writability. - -Sage trac ticket: http://trac.sagemath.org/ticket/17875 -Upstream: https://bitbucket.org/pypa/setuptools/issue/254/the-default-python-eggs-mode-doesnt - -diff -ur old/pkg_resources/__init__.py src/pkg_resources/__init__.py ---- old/pkg_resources/__init__.py 2015-02-16 14:57:32.000000000 +0100 -+++ src/pkg_resources/__init__.py 2015-03-04 22:36:05.661039379 +0100 -@@ -2955,7 +2955,7 @@ - os.makedirs(dirname) - - --def _bypass_ensure_directory(path, mode=0o777): -+def _bypass_ensure_directory(path, mode=0o755): - """Sandbox-bypassing version of ensure_directory()""" - if not WRITE_SUPPORT: - raise IOError('"os.mkdir" not supported on this platform.') - diff --git a/build/pkgs/setuptools_scm/SPKG.txt b/build/pkgs/setuptools_scm/SPKG.txt new file mode 100644 index 00000000000..23567beee69 --- /dev/null +++ b/build/pkgs/setuptools_scm/SPKG.txt @@ -0,0 +1,5 @@ += setuptools_scm = + +== Description == + +the blessed package to manage your versions by scm tags diff --git a/build/pkgs/setuptools_scm/checksums.ini b/build/pkgs/setuptools_scm/checksums.ini new file mode 100644 index 00000000000..bee26a695cc --- /dev/null +++ b/build/pkgs/setuptools_scm/checksums.ini @@ -0,0 +1,4 @@ +tarball=setuptools_scm-VERSION.tar.gz +sha1=c6829e48b2371ca48f7f61c10c6a82fbfd750755 +md5=d0423feeabda9c6a869d963cdc397d64 +cksum=2293631060 diff --git a/build/pkgs/setuptools_scm/dependencies b/build/pkgs/setuptools_scm/dependencies new file mode 100644 index 00000000000..05fd9f23776 --- /dev/null +++ b/build/pkgs/setuptools_scm/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(SETUPTOOLS) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/setuptools_scm/package-version.txt b/build/pkgs/setuptools_scm/package-version.txt new file mode 100644 index 00000000000..bd8bf882d06 --- /dev/null +++ b/build/pkgs/setuptools_scm/package-version.txt @@ -0,0 +1 @@ +1.7.0 diff --git a/build/pkgs/setuptools_scm/spkg-install b/build/pkgs/setuptools_scm/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/setuptools_scm/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/setuptools_scm/type b/build/pkgs/setuptools_scm/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/setuptools_scm/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/simplegeneric/SPKG.txt b/build/pkgs/simplegeneric/SPKG.txt new file mode 100644 index 00000000000..0fa7cf0a667 --- /dev/null +++ b/build/pkgs/simplegeneric/SPKG.txt @@ -0,0 +1,25 @@ += simplegeneric = + +== Description == + +Simple generic functions (similar to Python's own len(), pickle.dump(), etc.) + +The simplegeneric module lets you define simple single-dispatch generic +functions, akin to Python's built-in generic functions like len() +iter() and so on. However, instead of using specially-named methods, +these generic functions use simple lookup tables, akin to those used by +e.g. pickle.dump() and other generic functions found in the Python +standard library. + +As you can see from the above examples, generic functions are actually +quite common in Python already, but there is no standard way to create +simple ones. This library attempts to fill that gap, as generic +functions are an excellent alternative to the Visitor pattern, as well +as being a great substitute for most common uses of adaptation. + +This library tries to be the simplest possible implementation of +generic functions, and it therefore eschews the use of multiple or +predicate dispatch, as well as avoiding speedup techniques such as C +dispatching or code generation. But it has absolutely no dependencies, +other than Python 2.4, and the implementation is just a single Python +module of less than 100 lines. diff --git a/build/pkgs/simplegeneric/checksums.ini b/build/pkgs/simplegeneric/checksums.ini new file mode 100644 index 00000000000..77e7d78ec0a --- /dev/null +++ b/build/pkgs/simplegeneric/checksums.ini @@ -0,0 +1,4 @@ +tarball=simplegeneric-VERSION.tar.bz2 +sha1=b736091e8cfe97bd9dfbbf56e3ee0579b5e5ffaf +md5=b804806ff347bec45fe8ad0f642537e6 +cksum=3156769418 diff --git a/build/pkgs/simplegeneric/dependencies b/build/pkgs/simplegeneric/dependencies new file mode 100644 index 00000000000..edf27112135 --- /dev/null +++ b/build/pkgs/simplegeneric/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/simplegeneric/package-version.txt b/build/pkgs/simplegeneric/package-version.txt new file mode 100644 index 00000000000..6f4eebdf6f6 --- /dev/null +++ b/build/pkgs/simplegeneric/package-version.txt @@ -0,0 +1 @@ +0.8.1 diff --git a/build/pkgs/simplegeneric/spkg-install b/build/pkgs/simplegeneric/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/simplegeneric/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/simplegeneric/type b/build/pkgs/simplegeneric/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/simplegeneric/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/singular/spkg-install b/build/pkgs/singular/spkg-install index d8ff399a465..4d7f0c318be 100755 --- a/build/pkgs/singular/spkg-install +++ b/build/pkgs/singular/spkg-install @@ -218,6 +218,11 @@ build_libsingular() return 1 fi + if [ "$UNAME" = "Darwin" ]; then + # on darwin we need to adjust the install name of the library + install_name_tool -id "${SAGE_LOCAL}"/lib/libsingular.dylib "${SAGE_LOCAL}"/lib/libsingular.dylib + fi + # singular does not install this header cp Singular/sing_dbm.h $SAGE_LOCAL/include/singular/ diff --git a/build/pkgs/six/SPKG.txt b/build/pkgs/six/SPKG.txt index f948a3ae39d..c10f9b57c47 100644 --- a/build/pkgs/six/SPKG.txt +++ b/build/pkgs/six/SPKG.txt @@ -8,10 +8,6 @@ Python 2 and 3 compatibility utilities MIT License -== SPKG Maintainers == - -John H. Palmieri - == Upstream Contact == Author: Benjamin Peterson diff --git a/build/pkgs/sphinx/SPKG.txt b/build/pkgs/sphinx/SPKG.txt index 7396e60affd..d56a0fffed1 100644 --- a/build/pkgs/sphinx/SPKG.txt +++ b/build/pkgs/sphinx/SPKG.txt @@ -13,10 +13,6 @@ useful to many other projects. Modified BSD; see e.g. its egg-info file for other options -== SPKG Maintainers == - -Mike Hansen - == Upstream Contact == Author: Georg Brandl diff --git a/build/pkgs/sqlalchemy/type b/build/pkgs/sqlalchemy/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/sqlalchemy/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/symmetrica/SPKG.txt b/build/pkgs/symmetrica/SPKG.txt index 4c5f67cc5b1..04e978bc418 100644 --- a/build/pkgs/symmetrica/SPKG.txt +++ b/build/pkgs/symmetrica/SPKG.txt @@ -22,9 +22,6 @@ to http://www.algorithm.uni-bayreuth.de/en/research/SYMMETRICA) Public Domain (see the above web site) -== SPKG Maintainers == - * Mike Hansen - == Upstream Contact == * Axel Kohnert - see http://www.mathe2.uni-bayreuth.de/axel/ diff --git a/build/pkgs/symmetrica/dependencies b/build/pkgs/symmetrica/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/symmetrica/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sympow/SPKG.txt b/build/pkgs/sympow/SPKG.txt index 03dcbc3b322..89dcf729da9 100644 --- a/build/pkgs/sympow/SPKG.txt +++ b/build/pkgs/sympow/SPKG.txt @@ -8,9 +8,6 @@ curve L-functions. It can compute up to about 64 digits of precision. * See the file src/COPYING -== SPKG Maintainers == - * David Kirkby (I'll do my best, but this is hard to maintain) - == Upstream Contact == SYMPOW does not appear to be maintained any longer, so there is no upstream web site. diff --git a/build/pkgs/sympow/dependencies b/build/pkgs/sympow/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/sympow/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sympy/SPKG.txt b/build/pkgs/sympy/SPKG.txt index 0b1a59ef21c..3b384bb0c1d 100644 --- a/build/pkgs/sympy/SPKG.txt +++ b/build/pkgs/sympy/SPKG.txt @@ -12,9 +12,6 @@ http://sympy.org/ New BSD: http://www.opensource.org/licenses/bsd-license.php -== SPKG Maintainers == - * Ondrej Certik - == Upstream Contact == sympy mailinglist: http://groups.google.com/group/sympy diff --git a/build/pkgs/tachyon/SPKG.txt b/build/pkgs/tachyon/SPKG.txt index 2da7a7d7047..5c3deaba733 100644 --- a/build/pkgs/tachyon/SPKG.txt +++ b/build/pkgs/tachyon/SPKG.txt @@ -30,15 +30,6 @@ interface. * derived from this software without specific prior written permission. -== SPKG Maintainers == - - * Volker Braun - * Marshall Hampton - * David Kirkby - * Mike Hansen - * Andrzej Giniewicz - - == Upstream Contact == http://jedi.ks.uiuc.edu/~johns/raytracer/ diff --git a/build/pkgs/termcap/SPKG.txt b/build/pkgs/termcap/SPKG.txt index df8a4a34931..e6007fe8581 100644 --- a/build/pkgs/termcap/SPKG.txt +++ b/build/pkgs/termcap/SPKG.txt @@ -9,10 +9,6 @@ to terminals in a way independent of the terminal type. GPL version 2 -== SPKG Maintainers == - -None - == Upstream Contact == Please report any bugs in this library to bug-gnu-emacs@prep.ai.mit.edu diff --git a/build/pkgs/termcap/dependencies b/build/pkgs/termcap/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/termcap/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/tides/SPKG.txt b/build/pkgs/tides/SPKG.txt index 9e66a77c2b9..cbfeb88bb03 100644 --- a/build/pkgs/tides/SPKG.txt +++ b/build/pkgs/tides/SPKG.txt @@ -8,10 +8,6 @@ TIDES is a library for integration of ODE's with high precission. GPLv3+ -== SPKG Maintainers == - -* Miguel Marco - == Upstream Contact == * Marcos Rodriguez (marcos@unizar.es) diff --git a/build/pkgs/topcom/SPKG.txt b/build/pkgs/topcom/SPKG.txt index 39be1d130ee..30c32a01fbb 100644 --- a/build/pkgs/topcom/SPKG.txt +++ b/build/pkgs/topcom/SPKG.txt @@ -15,9 +15,6 @@ is a significant speed up compared to PUNTOS. == License == GPL v2 -== SPKG Maintainers == -* Volker Braun - == Upstream Contact == Prof. Dr. Jörg Rambau Lehrstuhl für Wirtschaftsmathematik diff --git a/build/pkgs/tornado/SPKG.txt b/build/pkgs/tornado/SPKG.txt index 66969ef5df5..7f0068d6516 100644 --- a/build/pkgs/tornado/SPKG.txt +++ b/build/pkgs/tornado/SPKG.txt @@ -8,10 +8,6 @@ Python web framework and asynchronous networking library Apache License -== SPKG Maintainers == - -John H. Palmieri - == Upstream Contact == Home page: http://www.tornadoweb.org diff --git a/build/pkgs/trac/type b/build/pkgs/trac/type new file mode 100644 index 00000000000..a1b589e38a3 --- /dev/null +++ b/build/pkgs/trac/type @@ -0,0 +1 @@ +pip diff --git a/build/pkgs/traitlets/SPKG.txt b/build/pkgs/traitlets/SPKG.txt new file mode 100644 index 00000000000..0cca54ec4bf --- /dev/null +++ b/build/pkgs/traitlets/SPKG.txt @@ -0,0 +1,7 @@ += traitlets = + +== Description == + +Traitlets Python config system + +A configuration system for Python applications. diff --git a/build/pkgs/traitlets/checksums.ini b/build/pkgs/traitlets/checksums.ini new file mode 100644 index 00000000000..7c6c5f431ee --- /dev/null +++ b/build/pkgs/traitlets/checksums.ini @@ -0,0 +1,4 @@ +tarball=traitlets-VERSION.tar.gz +sha1=f57049393ee8c514299e3873fc7ec9228778e929 +md5=27df56a921848686cf52766177a434f2 +cksum=3551254293 diff --git a/build/pkgs/traitlets/dependencies b/build/pkgs/traitlets/dependencies new file mode 100644 index 00000000000..dcd95f1afd8 --- /dev/null +++ b/build/pkgs/traitlets/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PYTHON) $(INST)/$(IPYTHON_GENUTILS) $(INST)/$(DECORATOR) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/traitlets/package-version.txt b/build/pkgs/traitlets/package-version.txt new file mode 100644 index 00000000000..fcdb2e109f6 --- /dev/null +++ b/build/pkgs/traitlets/package-version.txt @@ -0,0 +1 @@ +4.0.0 diff --git a/build/pkgs/traitlets/spkg-install b/build/pkgs/traitlets/spkg-install new file mode 100755 index 00000000000..afb3f302fd1 --- /dev/null +++ b/build/pkgs/traitlets/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && python setup.py install diff --git a/build/pkgs/traitlets/type b/build/pkgs/traitlets/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/traitlets/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/valgrind/SPKG.txt b/build/pkgs/valgrind/SPKG.txt index 77654776918..fa065c32de4 100644 --- a/build/pkgs/valgrind/SPKG.txt +++ b/build/pkgs/valgrind/SPKG.txt @@ -25,11 +25,6 @@ PPC64/Linux, S390X/Linux, ARM/Android (2.3.x), X86/Darwin and AMD64/Darwin Valgrind is Open Source / Free Software, and is freely available under the GNU General Public License, version 2. -== SPKG Maintainers == - - * Tim Dumol - * Ivan Andrus - == Upstream Contact == * http://www.valgrind.org/ diff --git a/build/pkgs/valgrind/dependencies b/build/pkgs/valgrind/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/valgrind/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zeromq/dependencies b/build/pkgs/zeromq/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zeromq/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zlib/SPKG.txt b/build/pkgs/zlib/SPKG.txt index 1fdd84507ed..d5f3d58f208 100644 --- a/build/pkgs/zlib/SPKG.txt +++ b/build/pkgs/zlib/SPKG.txt @@ -9,10 +9,6 @@ Free, Not to Mention Unencumbered by Patents) * Modified BSD. -== SPKG Maintainers == - - * William Stein - == Upstream Contact == * http://www.zlib.net/ diff --git a/build/pkgs/zlib/dependencies b/build/pkgs/zlib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zlib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zn_poly/SPKG.txt b/build/pkgs/zn_poly/SPKG.txt index 3e3ecdf8542..a4679c5f7d3 100644 --- a/build/pkgs/zn_poly/SPKG.txt +++ b/build/pkgs/zn_poly/SPKG.txt @@ -12,10 +12,6 @@ Website: http://cims.nyu.edu/~harvey/zn_poly/ GPL V2 or V3. Some of the code has been copied from other projects - see the file src/COPYING for details. -== SPKG Maintainers == - - * David Harvey - == Upstream Contact == * David Harvey diff --git a/build/pkgs/zn_poly/spkg-install b/build/pkgs/zn_poly/spkg-install index 00067eab437..25161e48a1c 100755 --- a/build/pkgs/zn_poly/spkg-install +++ b/build/pkgs/zn_poly/spkg-install @@ -192,6 +192,7 @@ else echo >&2 "Error copying 'libzn_poly.dylib'." exit 1 fi + install_name_tool -id ${SAGE_LOCAL}/lib/libzn_poly.dylib "${SAGE_LOCAL}"/lib/libzn_poly.dylib fi ############################################################################### diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index ce3036decf4..c08d958e4c4 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -76,7 +76,7 @@ class SagePkgApplication(CmdlineSubcommands): def run_config(self): """ - Print the configuration + config: Print the configuration $ sage-package config Configuration: @@ -88,7 +88,7 @@ def run_config(self): def run_list(self): """ - Print a list of all available packages + list: Print a list of all available packages $ sage-package list | sort 4ti2 @@ -103,7 +103,7 @@ def run_list(self): def run_name(self, tarball_filename): """ - Find the package name given a tarball filename + name: Find the package name given a tarball filename $ sage-package name pari-2.8-1564-gdeac36e.tar.gz pari @@ -113,14 +113,36 @@ def run_name(self, tarball_filename): def run_tarball(self, package_name): """ - Find the tarball filename given a package name + tarball: Find the tarball filename given a package name $ sage-package tarball pari pari-2.8-1564-gdeac36e.tar.gz """ package = Package(package_name) print(package.tarball.filename) - + + def run_apropos(self, incorrect_name): + """ + apropos: Find up to 5 package names that are close to the given name + + $ sage-package apropos python + Did you mean: cython, ipython, python2, python3, patch? + """ + from sage_bootstrap.levenshtein import Levenshtein, DistanceExceeded + levenshtein = Levenshtein(5) + names = [] + for pkg in Package.all(): + try: + names.append([levenshtein(pkg.name, incorrect_name), pkg.name]) + except DistanceExceeded: + pass + if names: + names = sorted(names)[:5] + print('Did you mean: {0}?'.format(', '.join(name[1] for name in names))) + else: + print('There is no package similar to {0}'.format(incorrect_name)) + print('You can find further packages at http://files.sagemath.org/spkg/') + class SageDownloadFileApplication(object): """ @@ -145,11 +167,13 @@ class SageDownloadFileApplication(object): def run(self): progress = True url = None + print_fastest_mirror = None destination = None for arg in sys.argv[1:]: if arg.startswith('--print-fastest-mirror'): - print(MirrorList().fastest) - sys.exit(0) + url = "" + print_fastest_mirror = True + continue if arg.startswith('--quiet'): progress = False continue @@ -162,13 +186,31 @@ def run(self): raise ValueError('too many arguments') if url is None: print(dedent(self.__doc__.format(SAGE_DISTFILES=SAGE_DISTFILES))) - sys.exit(1) - if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): - Download(url, destination, progress=progress, ignore_errors=True).run() - else: - # url is a tarball name - tarball = Tarball(url) - tarball.download() - if destination is not None: - tarball.save_as(destination) - + sys.exit(2) + + try: + if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): + Download(url, destination, progress=progress, ignore_errors=True).run() + elif print_fastest_mirror: + url = "fastest mirror" # For error message + print(MirrorList().fastest) + else: + # url is a tarball name + tarball = Tarball(url) + tarball.download() + if destination is not None: + tarball.save_as(destination) + except BaseException: + try: + stars = '*' * 72 + '\n' + sys.stderr.write(stars) + try: + import traceback + traceback.print_exc(file=sys.stderr) + sys.stderr.write(stars) + except: + pass + sys.stderr.write("Error downloading %s\n"%(url,)) + sys.stderr.write(stars) + finally: + sys.exit(1) diff --git a/build/sage_bootstrap/levenshtein.py b/build/sage_bootstrap/levenshtein.py new file mode 100644 index 00000000000..f451b288aa7 --- /dev/null +++ b/build/sage_bootstrap/levenshtein.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +u""" +Levenshtein Distance + +The Levenshtein distance between two words is the minimal number of +edits that turn one word into the other. Here, "edit" means a +single-letter addition, single-letter deletion, or exchange of a +letter with another letter. + +http://en.wikipedia.org/wiki/Levenshtein_distance + +EXAMPLES:: + + >>> from sage_bootstrap.levenshtein import Levenshtein + >>> Levenshtein(5)(u'Queensryche', u'Queensrÿche') + 1 +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +class DistanceExceeded(Exception): + pass + + +class Levenshtein(object): + + def __init__(self, limit): + """ + Levenshtein Distance with Maximum Distance Cutoff + + Args: + limit (int): if the distance exceeds the limit, a + :class:`DistanceExceeded` is raised and the + computation is aborted. + + EXAMPLES:: + + >>> from sage_bootstrap.levenshtein import Levenshtein + >>> lev3 = Levenshtein(3) + >>> lev3(u'saturday', u'sunday') + 3 + >>> lev3(u'kitten', u'sitting') + 3 + >>> lev2 = Levenshtein(2) + >>> lev2(u'kitten', u'sitting') + Traceback (most recent call last): + ... + DistanceExceeded + """ + self._limit = limit + + def __call__(self, a, b): + """ + calculate the levenshtein distance + + args: + a,b (str): the two strings to compare + + returns: + int: the Levenshtein distance if it is less or equal to + the distance limit. + + Example:: + + >>> from app.scoring.levenshtein import Levenshtein + >>> lev3 = Levenshtein(3) + >>> lev3(u'Saturday', u'Sunday') + 3 + """ + n, m = len(a), len(b) + if n > m: + # Optimization to use O(min(n,m)) space + a, b, n, m = b, a, m, n + curr = range(n+1) + for i in range(1, m+1): + prev, curr = curr, [i]+[0]*n + for j in range(1, n+1): + cost_add, cost_del = prev[j]+1, curr[j-1]+1 + cost_change = prev[j-1] + if a[j-1] != b[i-1]: + cost_change += 1 + curr[j] = min(cost_add, cost_del, cost_change) + if min(curr) > self._limit: + raise DistanceExceeded + if curr[n] > self._limit: + raise DistanceExceeded + return curr[n] + diff --git a/build/sage_bootstrap/mirror_list.py b/build/sage_bootstrap/mirror_list.py index 5d38dc02e7d..218276bd25e 100644 --- a/build/sage_bootstrap/mirror_list.py +++ b/build/sage_bootstrap/mirror_list.py @@ -21,29 +21,46 @@ from sage_bootstrap.compat import urllib, urlparse from sage_bootstrap.env import SAGE_DISTFILES +from fcntl import flock, LOCK_SH, LOCK_EX +from errno import ENOLCK + + +def try_lock(fd, operation): + """ + Try flock() but ignore ``ENOLCK`` errors, which could happen if the + file system does not support locking. + """ + try: + flock(fd, operation) + except IOError as e: + if e.errno != ENOLCK: + raise + class MirrorList(object): - URL = 'http://www.sagemath.org/mirror_list' - MAXAGE = 24*60*60 # seconds def __init__(self): self.filename = os.path.join(SAGE_DISTFILES, 'mirror_list') - if self.must_refresh(): - log.info('Downloading the Sage mirror list') - try: - with contextlib.closing(urllib.urlopen(self.URL)) as f: - mirror_list = f.read().decode("ascii") - except IOError: - log.critical('Downloading the mirror list failed') + self.mirrors = None + + try: + self.mirrorfile = open(self.filename, 'r+t') + except IOError: + self.mirrorfile = open(self.filename, 'w+t') + + with self.mirrorfile: + self.mirrorfd = self.mirrorfile.fileno() + try_lock(self.mirrorfd, LOCK_SH) # shared (read) lock + if self._must_refresh(): + try_lock(self.mirrorfd, LOCK_EX) # exclusive (write) lock + # Maybe the mirror list file was updated by a different + # process while we waited for the lock? Check again. + if self._must_refresh(): + self._refresh() + if self.mirrors is None: self.mirrors = self._load() - else: - self.mirrors = self._load(mirror_list) - self._rank_mirrors() - self._save() - else: - self.mirrors = self._load() def _load(self, mirror_list=None): """ @@ -52,8 +69,8 @@ def _load(self, mirror_list=None): """ if mirror_list is None: try: - with open(self.filename, 'rt') as f: - mirror_list = f.read() + self.mirrorfile.seek(0) + mirror_list = self.mirrorfile.read() except IOError: log.critical('Failed to load the cached mirror list') return [] @@ -64,8 +81,10 @@ def _save(self): """ Save the mirror list for (short-term) future use. """ - with open(self.filename, 'wt') as f: - f.write(repr(self.mirrors)) + self.mirrorfile.seek(0) + self.mirrorfile.write(repr(self.mirrors)) + self.mirrorfile.truncate() + self.mirrorfile.flush() def _port_of_mirror(self, mirror): if mirror.startswith('http://'): @@ -79,7 +98,7 @@ def _rank_mirrors(self): """ Sort the mirrors by speed, fastest being first - This method is used by the YUM fastestmirror plugin + This method is used by the YUM fastestmirror plugin """ timed_mirrors = [] import time, socket @@ -113,29 +132,40 @@ def _rank_mirrors(self): self.mirrors = [m[1] for m in timed_mirrors] log.info('Fastest mirror: ' + self.fastest) - @property - def fastest(self): - return next(iter(self)) - - def age(self): + def _age(self): """ Return the age of the cached mirror list in seconds """ import time - mtime = os.path.getmtime(self.filename) + mtime = os.fstat(self.mirrorfd).st_mtime now = time.mktime(time.localtime()) return now - mtime - def must_refresh(self): + def _must_refresh(self): """ Return whether we must download the mirror list. If and only if this method returns ``False`` is it admissible to use the cached mirror list. """ - if not os.path.exists(self.filename): + if os.fstat(self.mirrorfd).st_size == 0: return True - return self.age() > self.MAXAGE + return self._age() > self.MAXAGE + + def _refresh(self): + """ + Download and rank the mirror list. + """ + log.info('Downloading the Sage mirror list') + try: + with contextlib.closing(urllib.urlopen(self.URL)) as f: + mirror_list = f.read().decode("ascii") + except IOError: + log.critical('Downloading the mirror list failed') + else: + self.mirrors = self._load(mirror_list) + self._rank_mirrors() + self._save() def __iter__(self): """ @@ -154,5 +184,7 @@ def __iter__(self): yield mirror # If all else fails: Try the packages we host ourselves yield 'http://sagepad.org/' - + @property + def fastest(self): + return next(iter(self)) diff --git a/build/sage_bootstrap/package.py b/build/sage_bootstrap/package.py index 346eb976cea..27be420ccfe 100644 --- a/build/sage_bootstrap/package.py +++ b/build/sage_bootstrap/package.py @@ -182,7 +182,7 @@ def all(cls): base = os.path.join(SAGE_ROOT, 'build', 'pkgs') for subdir in os.listdir(base): path = os.path.join(base, subdir) - if not os.path.isdir(path): + if not os.path.isfile(os.path.join(path, "checksums.ini")): continue yield cls(subdir) diff --git a/configure.ac b/configure.ac index d2d76752b9f..dec7721c73f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,14 @@ -dnl Very loosely based on configure.ac in prereq-0.3 written by William Stein. -dnl Version 0.7 written by David Kirkby, released under the GPL version 2. -dnl in January 2010 +#***************************************************************************** +# Copyright (C) 2005-2007 William Stein +# Copyright (C) 2009-2011 David Kirkby +# Copyright (C) 2012-2015 Jeroen Demeyer +# +# 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/ +#***************************************************************************** dnl If you are going to update this, please stick the recommended layout dnl in the autoconf manual - i.e. @@ -14,9 +22,8 @@ dnl Next check compiler characteristics dnl Next check for library functions dnl Next check for system services -dnl Older versions give "undefined macro: _m4_divert_diversion", see -dnl http://trac.sagemath.org/ticket/15606#comment:19 -AC_PREREQ([2.64]) +dnl Older versions do not support $GFC +AC_PREREQ([2.69]) AC_DEFUN([SAGE_VERSION], m4_esyscmd_s([. src/bin/sage-version.sh && echo $SAGE_VERSION])) AC_INIT([Sage], SAGE_VERSION, [sage-devel@googlegroups.com]) @@ -36,8 +43,6 @@ dnl Make sure the path to our own m4 macros is always properly set dnl and doesn't depend on how autoconf is called. AC_CONFIG_MACRO_DIR([m4]) -AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to a unpriviledged user])], []) - #--------------------------------------------------------- # We need to run this configure script with bash if test -z "$BASH_VERSION$CONFIG_SHELL" @@ -63,6 +68,64 @@ then fi #--------------------------------------------------------- +# +# This is essentially the configure part of the old "install" file. +# Over time, this should be changed to proper autoconf style with +# configure options. +# + +[ +######################################################################## +# Set various environment variables +######################################################################## + +# Assume current directory is SAGE_ROOT +SAGE_ROOT=`pwd -P` +SAGE_SRC="$SAGE_ROOT/src" +SAGE_LOCAL="$SAGE_ROOT/local" +SAGE_SHARE="$SAGE_LOCAL/share" +SAGE_EXTCODE="$SAGE_SHARE/sage/ext" +SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" + +############################################################################### +# Determine whether to use MPIR (default standard pkg) or GMP (optional pkg). +############################################################################### + +mkdir -p "$SAGE_SHARE" +if [ -z "$SAGE_MP_LIBRARY" ]; then + # Automatic detection of installed MP library. + if [ ! -f "$SAGE_SHARE/mp_config" ]; then + echo "MPIR" > "$SAGE_SHARE/mp_config" + fi + SAGE_MP_LIBRARY=`cat "$SAGE_SHARE/mp_config"` +else + if [ -f "$SAGE_SHARE/mp_config" -a "$SAGE_MP_LIBRARY" != `cat "$SAGE_SHARE/mp_config"` ]; then + echo "SAGE_MP_LIBRARY differs from the value stored in" + echo "\"$SAGE_SHARE/mp_config\"." + echo "This is not supported; you should rebuild Sage from scratch" + echo "using the new SAGE_MP_LIBRARY value." + exit 1 + fi + + case "$SAGE_MP_LIBRARY" in + MPIR|GMP) + echo "Using $SAGE_MP_LIBRARY as default MP library." + echo $SAGE_MP_LIBRARY > "$SAGE_SHARE/mp_config" + ;; + *) + echo "Allowed values for SAGE_MP_LIBRARY are \"MPIR\" and \"GMP\"." + echo "If you did not set this variable, check the content of" + echo "\"$SAGE_SHARE/mp_config\"." + exit 1 + ;; + esac +fi +] + + +#--------------------------------------------------------- +AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to an unpriviledged user])], []) + # Check whether we are on a supported platform AC_CANONICAL_BUILD() AC_CANONICAL_HOST() @@ -154,23 +217,10 @@ The Sage community would also appreciate any patches you submit]]);; esac -dnl Check compiler versions -buggy_gcc_version1="4.0.0" -minimum_gcc_version_for_no_hassle="4.0.1" -minimum_gcc_version_for_debugging_purposes="3.4.0" - -#--------------------------------------------------------- -# Process options and environment variables - -# Check --enable-compiler-checks (yes by default) -AC_ARG_ENABLE([compiler-checks], [Check versions and presence of C, C++ and Fortran compilers (default: yes)], - [enable_compiler_checks=$enableval], [enable_compiler_checks=yes]) +############################################################################### +# Check general programs +############################################################################### -# Import environment variables. -source src/bin/sage-env || AC_MSG_ERROR([failed to source sage-env]) - -#--------------------------------------------------------- -# Check some programs needed actually exist. AC_CHECK_PROG(found_ar, ar, yes, no) if test x$found_ar != xyes then @@ -289,10 +339,62 @@ then AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires dpkg-architecture on Debian]) fi -#--------------------------------------------------------- -# C/C++/Fortran compilers -# First check for programs we need. +############################################################################### +# Check C/C++/Fortran compilers +############################################################################### + +dnl Usage: SAGE_SHOULD_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we SHOULD install GCC. +dnl In this case, GCC will be installed unless SAGE_INSTALL_GCC=no. +dnl In the latter case, a warning is given. +AC_DEFUN([SAGE_SHOULD_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_WARN([$1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + +dnl Usage: SAGE_MUST_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we MUST install GCC. +dnl In this case, it is an error if SAGE_INSTALL_GCC=no. +AC_DEFUN([SAGE_MUST_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_ERROR([SAGE_INSTALL_GCC is set to 'no', but $1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + + +# By default, do not install GCC +need_to_install_gcc=no + +if test -f "$SAGE_LOCAL/bin/gcc"; then + # Special value for SAGE_INSTALL_GCC if GCC is already installed + SAGE_INSTALL_GCC=exists +elif test -n "$SAGE_INSTALL_GCC"; then + # Check the value of the environment variable SAGE_INSTALL_GCC + case "$SAGE_INSTALL_GCC" in + yes) + SAGE_MUST_INSTALL_GCC([SAGE_INSTALL_GCC is set to 'yes']);; + no) + true;; + *) + AC_MSG_ERROR([SAGE_INSTALL_GCC should be set to 'yes' or 'no'. You can also leave it unset to install GCC when needed]);; + esac +fi + + AC_LANG(C) AC_PROG_CC() AC_PROG_CPP() @@ -305,44 +407,41 @@ AC_PROG_FC() if test "x$CXX" = x then - AC_MSG_ERROR([Exiting, since a C++ compiler was not found.]) + AC_MSG_ERROR([a C++ compiler is missing]) fi -if test $enable_compiler_checks = yes -then - if test "x$CC" = x - then - AC_MSG_ERROR([Exiting, since a C compiler was not found.]) - fi - if test "x$FC" = x - then - AC_MSG_ERROR([Exiting, since a Fortran compiler was not found.]) - fi -fi -# Next one should check for header files. +############################################################################### +# Check header files +############################################################################### + # complex.h is one that might not exist on older systems. AC_LANG(C++) AC_CHECK_HEADER([complex.h],[],[ AC_MSG_ERROR([Exiting, since you do not have the 'complex.h' header file.]) ]) -# Next one should check for types. -# None needed -# Next one should check for structures. +############################################################################### +# Check types/structures +############################################################################### + # None needed -# Next one should check for compiler characterists. -# Check that we can compile C99 code +############################################################################### +# Check compiler characteristics +############################################################################### + AC_LANG(C) -AC_PROG_CC_C99() -if test $enable_compiler_checks = yes -then +if test -z "$CC"; then + SAGE_MUST_INSTALL_GCC([a C compiler is missing]) +else + # Check that we can compile C99 code + AC_PROG_CC_C99() if test "x$ac_cv_prog_cc_c99" = xno then - AC_MSG_ERROR([Exiting, as your C compiler cannot compile C99 code]) + SAGE_MUST_INSTALL_GCC([your C compiler cannot compile C99 code]) fi fi @@ -352,62 +451,82 @@ fi # sets FC to /usr/bin/ls, we will at least know it's # not a working Fortran compiler. AC_LANG(Fortran) -if test $enable_compiler_checks = yes -then +if test -z "$FC"; then + SAGE_MUST_INSTALL_GCC([a Fortran compiler is missing]) +else # see http://www.gnu.org/software/hello/manual/autoconf/Fortran-Compiler.html AC_FC_FREEFORM([], [ AC_MSG_NOTICE([Your Fortran compiler does not accept free-format source code]) AC_MSG_NOTICE([which means the compiler is either seriously broken, or]) AC_MSG_NOTICE([is too old to build Sage.]) - AC_MSG_ERROR([Exiting, as the Fortran compiler is not suitable]) + SAGE_MUST_INSTALL_GCC([the Fortran compiler is not suitable]) ]) fi -if test $enable_compiler_checks = yes -then - # Check that all compilers (C, C++, Fortan) are either all GNU - # compiler or all non-GNU compilers. If not, there is a problem, as - # mixing GNU and non-GNU compilers is likely to cause problems. - if test x$GCC = xyes && test x$GXX != xyes - then - AC_MSG_NOTICE([You are trying to use gcc but not g++]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$GXX = xyes && test x$ac_cv_fc_compiler_gnu != xyes - then - AC_MSG_NOTICE([You are trying to use g++ but not gfortran]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$ac_cv_fc_compiler_gnu = xyes && test x$GCC != xyes - then - AC_MSG_NOTICE([You are trying to use gfortran but not gcc]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi -fi -# The following tests check the version of the compilers (if GNU) -# are all the same. If non-GNU compilers are used, then no such -# checks are performed. -if test $enable_compiler_checks = yes -then -if test x$GCC = xyes -then - # Thank you to Andrew W. Nosenko andrew.w.nosenko@gmail.com - # who answered my query about testing of gcc versions on - # the autoconf@gnu.org mailing list. - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) - AX_GCC_VERSION - AX_GXX_VERSION - AS_VERSION_COMPARE([$GCC_VERSION], [$GXX_VERSION], - [ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions]) - ],[],[ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions])]) +# Check compiler versions + +if test x$GXX != xyes; then + SAGE_SHOULD_INSTALL_GCC([your C++ compiler isn't GCC (GNU C++)]) +elif test $need_to_install_gcc = yes; then + # If we're installing GCC anyway, skip the rest of these version + # checks. + true +elif test x$GCC != xyes; then + SAGE_SHOULD_INSTALL_GCC([your C compiler isn't GCC (GNU C)]) +elif test x$GFC != xyes; then + SAGE_SHOULD_INSTALL_GCC([your Fortran compiler isn't GCC (GNU Fortran)]) +else + # Since need_to_install_gcc is "no", we know that + # at least C, C++ and Fortran compilers are available. + # We also know that all compilers are GCC. + + # Find out the compiler versions: + AX_GCC_VERSION() + AX_GXX_VERSION() + + # Add the .0 because Debian/Ubuntu gives version numbers like + # 4.6 instead of 4.6.4 (Trac #18885) + case "$GXX_VERSION.0" in + [[0-3]].*|4.[[0-3]].*) + # Install our own GCC if the system-provided one is older than gcc-4.4. + # * gcc-4.2.4 compiles a slow IML: + # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/Ux3t0dW2FSI + # * gcc-4.3 might have trouble building ATLAS: + # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/KCeFqQ_w2FE + SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION, which is quite old]);; + 4.4.*|4.5.*) + # GCC 4.4.x and GCC 4.5.x fail to compile PARI/GP on ia64: + # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46044 + if test x`uname -m` = xia64; then + SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION on ia64]) + AC_MSG_NOTICE([gcc <= 4.5 fails to compile PARI/GP on ia64]) + fi;; + 4.6.*) + # Also install GCC if we have version 4.6.* which is + # known to give trouble within Sage: + # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48702 + # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 + # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52061 + # * https://groups.google.com/d/msg/sage-release/xgmJ3nAcUOY/jH8OZjftYRsJ + SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION]) + AC_MSG_NOTICE([g++-4.6.* has known bugs affecting Sage]);; + 4.7.0) + # GCC 4.7.0 is very broken on ia64, see + # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48496 + # This is fixed in GCC 4.7.1. + if test x`uname -m` = xia64; then + SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION on ia64]) + AC_MSG_NOTICE([g++-4.7.0 has a serious bug on ia64]) + fi;; + esac + + # The following tests check that the version of the compilers + # are all the same. + if test "$GCC_VERSION" != "$GXX_VERSION"; then + SAGE_SHOULD_INSTALL_GCC([$CC ($GCC_VERSION) and $CXX ($GXX_VERSION) are not the same version]) + fi # In the paragraph below, 'gfortran' is used to indicate the GNU Fortran # compiler, though it might be called something else. @@ -444,90 +563,41 @@ then if test "x$fortran_version_string" = x then - AC_MSG_NOTICE([Although gcc and g++ are both version $GCC_VERSION]) - AC_MSG_NOTICE([the Fortran compiler $FC is some other version.]) - AC_MSG_NOTICE([The output from $FC --version is below.]) - echo "" - $FC --version 2>&1 - echo "" - AC_MSG_ERROR([Exiting, since the Fortran compiler is not the same version as the C and C++ compilers]) - else - AC_MSG_NOTICE([Excellent, the C, C++ and Fortran compilers are all GCC $GCC_VERSION]) + SAGE_SHOULD_INSTALL_GCC([the Fortran compiler is not the same version as the C compiler]) fi +fi - # Exit if the version of GCC is known to be too old, and so old - # we have no intension whatsoever of trying to make Sage work with it. - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_debugging_purposes],[ - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME. ]) - AC_MSG_NOTICE([Please use a GCC of at least $minimum_gcc_version_for_no_hassle ]) - AC_MSG_NOTICE([There are no plans whatsoever to support GCC $GCC_VERSION]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - ], [],[]) - - # Exit if Sage is *precisely* version $buggy_gcc_version1, as that is very buggy. - # If any later versions of GCC are found to be buggy (which would never surprise me) - # We can easily add a buggy_gcc_version2 and repeat the next 5 lines. - # At the time of writing (28th September 2009) that is version 4.0.0, - # but rather than hard-code that, it is set as a variable. - AS_VERSION_COMPARE([$GCC_VERSION], [$buggy_gcc_version1],[],[ - AC_MSG_NOTICE([GCC $buggy_gcc_version1 is very buggy and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle.]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too buggy]) - ],[]) - - # Issue a warning if gcc is too old to work with all packages, but - # someone wants to try getting one or more packages work with - # an earlier gcc. At the time of writing, (28th Sept 2009), ratpoints - # has such an issue, requiring version 4.0.1, but we would like to - # get it to work with version 3.4.x - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_no_hassle],[ - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle]) - AC_MSG_NOTICE([if you just want AC_PACKAGE_NAME to build without problems.]) - AC_MSG_NOTICE([]) - if test "${SAGE_USE_OLD_GCC+set}" = set; then - AC_MSG_NOTICE([Since the variable SAGE_USE_OLD_GCC was set, the]) - AC_MSG_NOTICE([build will continue, but it will fail without changes]) - AC_MSG_NOTICE([to the Sage source code. You can be 100% sure of that.]) - else - AC_MSG_NOTICE([If you want to try building Sage with a GCC 3.4.x ]) - AC_MSG_NOTICE([with a view to debugging the problems which stop it ]) - AC_MSG_NOTICE([working on a gcc 3.4 series compiler, set the ]) - AC_MSG_NOTICE([environment variable SAGE_USE_OLD_GCC to something non]) - AC_MSG_NOTICE([empty. But if you just want Sage to work, upgrade GCC ]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - fi - ], -[ -AC_MSG_NOTICE([Good, gcc and g++ are are just new enough as GCC $minimum_gcc_version_for_no_hassle]) -AC_MSG_NOTICE([is the minimum version needed needed to build AC_PACKAGE_NAME]) -] -, -[ -AC_MSG_NOTICE([Excellent, GCC $GCC_VERSION is later than the minimum]) -AC_MSG_NOTICE([needed to build AC_PACKAGE_NAME, which is GCC version $minimum_gcc_version_for_no_hassle]) -]) - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) -else - AC_MSG_WARN([You have a non-GNU compiler, but AC_PACKAGE_NAME has never been built]) - AC_MSG_WARN([successfully with any compiler other than GCC, despite]) - AC_MSG_WARN([some attempts made on Solaris to use Sun's compiler, which]) - AC_MSG_WARN([produces faster code than GCC. However, the AC_PACKAGE_NAME developers]) - AC_MSG_WARN([want AC_PACKAGE_NAME to work with other compilers, so please try.]) - AC_MSG_WARN([The AC_PACKAGE_NAME developers would welcome any feedback you can give.]) - AC_MSG_WARN([Please visit http://groups.google.com/group/sage-devel]) - AC_MSG_WARN([If you just want to use AC_PACKAGE_NAME, we suggest the use of]) - AC_MSG_WARN([GCC of at least version $minimum_gcc_version_for_no_hassle]) +# Check that the assembler and linker used by $CXX match $AS and $LD. +# See http://trac.sagemath.org/sage_trac/ticket/14296 +if test -n "$AS"; then + CXX_as=`$CXX -print-file-name=as 2>/dev/null` + CXX_as=`command -v $CXX_as 2>/dev/null` + cmd_AS=`command -v $AS` + + if test "$CXX_as" != "" -a "$CXX_as" != "$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]) + fi +fi +if test -n "$LD"; then + CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` + CXX_ld=`command -v $CXX_ld 2>/dev/null` + cmd_LD=`command -v $LD` + + if test "$CXX_ld" != "" -a "$CXX_ld" != "$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]) + fi fi -fi # test $enable_compiler_checks = yes -# Testing for library functions + +############################################################################### +# Check libraries +############################################################################### + # First check for something that should be in any maths library (sqrt). AC_LANG(C++) AC_CHECK_LIB(m,sqrt,[],[ @@ -619,10 +689,265 @@ AC_MSG_ERROR([["found Fink in $FINK_PATH. Either: fi +############################################################################### +# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps +############################################################################### + +# Use file descriptor 7 since make uses 3 and 4 and configure uses 5 and 6 +exec 7>build/make/Makefile + +cat >&7 <&7 "SHELL = `command -v bash`" +echo >&7 + +AC_MSG_CHECKING([package versions]) +AC_MSG_RESULT([]) + +# Usage: newest_version $pkg +# Print version number of latest standard package $pkg +newest_version() { + PKG=$1 + if test -f "$SAGE_ROOT/build/pkgs/$PKG/package-version.txt" ; then + AS_ECHO_N(["$PKG-"]) + cat "$SAGE_ROOT/build/pkgs/$PKG/package-version.txt" + else + echo "$PKG" + fi +} + +# Outputs the list of packages, filtered by 'type', e.g.: +# +# filtered_packages_list base +# filtered_packages_list standard +# filtered_packages_list optional +# filtered_packages_list experimental +# +# Or, if you want all packages: +# +# filtered_packages_list all +# +# The output consists of triples +# PKG_NAME PKG_VERSION PKG_VAR +# + +[ +filtered_packages_list() { + # for each package in pkgs/ + for DIR in $SAGE_ROOT/build/pkgs/*; do + test -d "$DIR" || continue + + PKG_TYPE_FILE="$DIR/type" + if [ -f "$PKG_TYPE_FILE" ]; then + PKG_TYPE=`cat $PKG_TYPE_FILE` + else + echo >&2 "\"$PKG_TYPE_FILE\" is missing. Create it, and set its content" + echo >&2 "to 'base', 'standard', 'optional' or 'experimental'." + return 1 + fi + + # Check consistency of 'DIR/type' file + if [ "$PKG_TYPE" != "base" ] && \ + [ "$PKG_TYPE" != "standard" ] && \ + [ "$PKG_TYPE" != "optional" ] && \ + [ "$PKG_TYPE" != "experimental" ] && \ + [ "$PKG_TYPE" != "pip" ]; then + echo >&2 "The content of \"$PKG_TYPE_FILE\" must be 'base', 'standard', 'optional', 'experimental' or 'pip'" + return 1 + fi + + # Filter + if [ "$1" = "$PKG_TYPE" -o "$1" = all ]; then + PKG_NAME=$(basename $DIR) + PKG_VAR="$(echo $PKG_NAME | sed 's/^4/FOUR/' | tr '[:lower:]' '[:upper:]')" + PKG_VERSION=$(newest_version $PKG_NAME) + echo "$PKG_NAME $PKG_VERSION $PKG_VAR" + fi + done +} + +# Check that all packages define a (valid) 'type' file, or exit +filtered_packages_list none || exit $? + +echo >&7 "# All Sage packages" + +filtered_packages_list all | while read PKG_NAME PKG_VERSION PKG_VAR; do + if test "$PKG_NAME" != "$PKG_VERSION"; then + echo >&7 "$PKG_VAR = $PKG_VERSION" + ]AC_MSG_RESULT([ $PKG_VAR=$PKG_VERSION])[ + fi +done + +cat >&7 <&7 'PYTHON = $(PYTHON3)' +else + echo >&7 'PYTHON = $(PYTHON2)' +fi +] + +# Sage MP library +echo >&7 "SAGE_MP_LIBRARY = \$($SAGE_MP_LIBRARY)" + +# $(TOOLCHAIN) variable containing prerequisites for the build +AS_ECHO_N(>&7 ['TOOLCHAIN =']) +if test "$SAGE_INSTALL_CCACHE" = yes ; then + AS_ECHO_N(>&7 [' $(INST)/$(CCACHE)']) +fi +if test "$need_to_install_gcc" = yes ; then + AS_ECHO_N(>&7 [' $(INST)/$(GCC)']) +fi +echo >&7 +echo >&7 + +[ +echo >&7 '# All standard packages' +echo >&7 'STANDARD_PACKAGES = \' +filtered_packages_list standard | while read PKG_NAME PKG_VERSION PKG_VAR; do + echo >&7 " \$(INST)/\$($PKG_VAR) \\" +done +echo >&7 +echo >&7 + +echo >&7 '# All optional installed packages (triggers the auto-update)' +echo >&7 'OPTIONAL_INSTALLED_PACKAGES = \' +filtered_packages_list optional | while read PKG_NAME PKG_VERSION PKG_VAR; do + if [ "$PKG_NAME" = "gcc" ]; then + continue + fi + if [ -f $SAGE_SPKG_INST/$PKG_NAME-* ]; then + echo >&7 " \$(INST)/\$($PKG_VAR) \\" + fi; +done +echo >&7 +echo >&7 + +echo >&7 'SCRIPT_SOURCES = \' +for file in "$SAGE_SRC/bin/"*; do + echo >&7 " \$(SAGE_SRC)${file#$SAGE_SRC} \\" +done +echo >&7 +echo >&7 'SCRIPTS = \' +for file in "$SAGE_SRC/bin/"*; do + echo >&7 " \$(SAGE_LOCAL)${file#$SAGE_SRC} \\" +done +echo >&7 +echo >&7 'EXTCODE_SOURCES = \' +for file in `find "$SAGE_SRC"/ext -type f`; do + echo >&7 " \$(SAGE_SRC)${file#$SAGE_SRC} \\" +done +echo >&7 +echo >&7 'EXTCODE = \' +for file in `find "$SAGE_SRC"/ext -type f`; do + echo >&7 " \$(SAGE_EXTCODE)${file#$SAGE_SRC/ext} \\" +done +echo >&7 + +# Copy build/make/deps +cat >&7 <&7 +# Copy build/make/deps + +cat >&7 </dependencies files +#============================================================================== + +EOF + +# Add a Makefile target corresponding to a given package +filtered_packages_list all | while read PKG_NAME PKG_VERSION PKG_VAR; do + DEP_FILE="$SAGE_ROOT/build/pkgs/$PKG_NAME/dependencies" + TYPE=`cat "$SAGE_ROOT/build/pkgs/$PKG_NAME/type"` + if [ -f "$DEP_FILE" ]; then + DEPS=" $(head -n 1 $DEP_FILE)" + elif [ "$TYPE" = optional ]; then + DEPS=' | $(STANDARD_PACKAGES)' # default for optional packages + elif [ "$TYPE" = pip ]; then + DEPS=' | $(INST)/$(PIP)' + else + DEPS="" + fi + + if [ "$TYPE" = pip ]; then + # Special rules using PIP + echo >&7 "$PKG_NAME:$DEPS" + echo >&7 " sage-logger 'sage --pip install $PKG_NAME' \$(SAGE_LOGS)/$PKG_NAME.log" + echo >&7 + + # Add a target to remove the "installed" file for "sage -f" + echo >&7 "$PKG_NAME-clean:" + echo >&7 " -sage --pip uninstall -y $PKG_NAME" + echo >&7 + else + # Normal Sage packages + echo >&7 "\$(INST)/$PKG_VERSION:$DEPS" + echo >&7 " +sage-logger '\$(SAGE_SPKG) \$($PKG_VAR)' '\$(SAGE_LOGS)/\$($PKG_VAR).log'" + echo >&7 + + # Add a target with just the bare package name for "sage -i" + echo >&7 "$PKG_NAME: \$(INST)/\$($PKG_VAR)" + echo >&7 + + # Add a target to remove the "installed" file for "sage -f" + echo >&7 "$PKG_NAME-clean:" + echo >&7 " rm -f \$(INST)/\$($PKG_VAR)" + echo >&7 + fi +done + +# Close the Makefile +exec 7>&- +] + + dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([build/make/Makefile-auto]) AC_CONFIG_MACRO_DIR([m4]) +dnl Create basic directories needed for Sage +AC_CONFIG_COMMANDS(mkdirs, + [ + SAGE_LOGS=${ac_abs_top_builddir}/logs/pkgs + SAGE_LOCAL=${ac_abs_top_builddir}/local + SAGE_SHARE=${ac_abs_top_builddir}/local/share + SAGE_INST=${ac_abs_top_builddir}/local/var/lib/sage/installed + for d in "$SAGE_LOGS" "$SAGE_LOCAL" \ + "$SAGE_LOCAL/bin" "$SAGE_LOCAL/etc" \ + "$SAGE_LOCAL/include" "$SAGE_LOCAL/lib" \ + "$SAGE_SHARE" "$SAGE_INST"; do + AC_MSG_NOTICE([creating directory $d]) + mkdir -p "$d" || AC_MSG_ERROR(could not create $d) + done + ]) + AC_OUTPUT() dnl vim:syntax=m4 diff --git a/sage b/sage index 58e17aafc31..a4ee8dc9300 100755 --- a/sage +++ b/sage @@ -125,18 +125,10 @@ export SAGE_ROOT # Run the actual Sage script if [ -x "$SAGE_ROOT/src/bin/sage" ]; then - "$SAGE_ROOT/src/bin/sage" "$@" + exec "$SAGE_ROOT/src/bin/sage" "$@" elif [ -x "$SAGE_ROOT/local/bin/sage" ]; then # if in a stripped binary - "$SAGE_ROOT/local/bin/sage" "$@" + exec "$SAGE_ROOT/local/bin/sage" "$@" else echo >&2 "$0: no Sage installation found in \$SAGE_ROOT=$SAGE_ROOT" exit 1 fi - -# Kill all processes in the current process group. In practice, this -# means all child processes not running their own pty. -# Uncomment this if you have trouble with orphans. -# We do this in the background after waiting 10 seconds to give the -# various processes (including this shell script) some time to exit -# cleanly. -# { sleep 10; kill -9 -$$ 2>/dev/null; } & diff --git a/src/bin/sage b/src/bin/sage index 5ecb81bc7f2..57f4825efe9 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -23,7 +23,7 @@ usage() { echo " -maxima [...] -- run Sage's Maxima with given arguments" echo " -mwrank [...] -- run Sage's mwrank with given arguments" echo " --notebook=[...] -- start the Sage notebook (valid options are" - echo " 'default', 'sagenb', and 'ipython')" + echo " 'default', 'sagenb', and 'jupyter')" echo " -n, --notebook -- shortcut for --notebook=default" echo " -optional -- list all optional packages that can be installed" echo " -python [...] -- run the Python interpreter" @@ -74,7 +74,7 @@ usage_advanced() { #### |.....................--.|...................................................| echo "Running the notebook:" echo " --notebook=[...] -- start the Sage notebook (valid options are" - echo " 'default', 'sagenb', and 'ipython'). See the output" + echo " 'default', 'sagenb', and 'jupyter'). See the output" echo " of sage --notebook --help for more details and" echo " examples of how to pass optional arguments" echo " -bn, -build-and-notebook [...] -- build the Sage library then start" @@ -117,23 +117,27 @@ usage_advanced() { #### 1.......................26..................................................78 #### |.....................--.|...................................................| echo "Installing packages and upgrading:" + echo " -package [args] -- call the new package manager with given arguments." + echo " Run without arguments for package-specific help." echo " -experimental -- list all experimental packages that can be installed" echo " -f [opts] [packages]-- shortcut for -i -f: force build of the given Sage" echo " packages" - echo " -i [opts] [packages]-- install the given Sage packages (unless they are" - echo " already installed); if no packages are given, print" - echo " a list of all installed packages. Options:" + echo " -i [opts] [packages]-- install the given Sage packages. Options:" echo " -c -- run the packages' test suites" + echo " -d -- only download, do not install packages" echo " -f -- force build: install the packages even" echo " if they are already installed" - echo " -s -- do not delete the spkg/build directories" + echo " -s -- do not delete the temporary build directories" echo " after a successful build" + echo " -p [opts] [packages]-- install the given Sage packages, without dependency" + echo " checking and with support for old-style spkgs." + echo " Options are -c, -d and -s with the same meaning as" + echo " for the -i command" echo " -info [packages] -- print the SPKG.txt of the given packages" + echo " --location -- if needed, fix paths to make Sage relocatable" echo " -optional -- list all optional packages that can be installed" echo " -standard -- list all standard packages that can be installed" echo " -installed -- list all installed packages" - #echo " -update -- download latest non-optional Sage packages (do not build them)" - #echo " -update-build -- build and install all downloaded non-optional Sage packages" echo " -upgrade [version] -- download, build and install the given version. Here," echo " 'version' is a git branch or tag name. Useful values" echo " are 'master' (the current development version, this" @@ -238,6 +242,11 @@ usage_advanced() { exit 0 } + +##################################################################### +# Special options to be processed without sage-env +##################################################################### + # Check for '--nodotsage' before sourcing sage-env; otherwise sage-env # will already have set some environment variables with the old # setting for DOT_SAGE. @@ -266,10 +275,63 @@ if [ "$1" = '-upgrade' -o "$1" = "--upgrade" ]; then exec local/bin/sage-upgrade "$@" fi +# Check for '-i' before sourcing sage-env: running "make" +# should be run outside of the Sage shell. +if [ "$1" = '-f' ]; then + # -f is an alias for -i -f + set -- -i "$@" +fi + +if [ "$1" = '-i' ]; then + shift + if [ -z "$MAKE" ]; then + MAKE="make" + fi + + set -e + + cd "$SAGE_ROOT" + + # First of all, make sure that the toolchain is up-to-date + # (which is a dependency of every package) + ./sage --location + $MAKE all-toolchain + echo + + INSTALL_OPTIONS="" # Options to sage-spkg + for PKG in "$@" + do + case "$PKG" in + -info|--info) + echo >&2 "Error: 'sage -i $PKG ' is no longer supported, use 'sage --info ' instead." + exit 2;; + -f) FORCE_INSTALL=yes;; + -*) INSTALL_OPTIONS="$INSTALL_OPTIONS $PKG";; + *) + # First check that $PKG is actually a Makefile target + if ! grep "^$PKG: " build/make/Makefile >/dev/null; then + echo >&2 "Error: package '$PKG' not found" + echo >&2 "Assuming it is an old-style package... (this is deprecated: use -p instead of -i to install old-style packages)" + echo >&2 + sleep 5 + ./sage -p $INSTALL_OPTIONS "$PKG" + else + if [ x$FORCE_INSTALL = xyes ]; then + $MAKE "$PKG-clean" + fi + $MAKE SAGE_SPKG="sage-spkg $INSTALL_OPTIONS" "$PKG" + fi;; + esac + done + exit 0 +fi + +##################################################################### # Source sage-env ($0 is the name of this "sage" script, so we can just # append -env to that). We redirect stdout to stderr, which is safer # for scripts. +##################################################################### . "$0-env" >&2 if [ $? -ne 0 ]; then echo >&2 "Error setting environment variables by sourcing '$0-env';" @@ -749,39 +811,29 @@ if [ "$1" = '-c' ]; then exec sage-eval "$@" fi +if [ "$1" = '--location' ]; then + maybe_sage_location + exit 0 +fi + + install() { maybe_sage_location - mkdir -p "$SAGE_LOGS" for PKG in "$@" do # Check for options case "$PKG" in - -f) OPTF="-f" - continue;; - -m) OPTS="-s" - echo >&2 "Warning: the -m option is deprecated since Sage 5.0. Use -s instead." + -*) + INSTALL_OPTIONS="$INSTALL_OPTIONS $PKG" continue;; - -s) OPTS="-s" - continue;; - -c) OPTC="-c" - continue;; - --check) OPTC="-c" - continue;; - -info) OPTINFO="--info" - continue;; - --info) OPTINFO="--info" - continue;; - -*) echo >&2 "Error: unknown option '$PKG'" - exit 2;; esac PKG_NAME=`echo "$PKG" | sed -e "s/\.spkg$//"` PKG_NAME=`basename "$PKG_NAME"` - "$SAGE_ROOT"/build/make/pipestatus \ - "sage-spkg $OPTINFO $OPTF $OPTS $OPTC '$PKG' 2>&1" \ - "(trap '' SIGINT; tee -a '$SAGE_ROOT/logs/install.log' '$SAGE_LOGS/$PKG_NAME.log')" + sage-logger \ + "sage-spkg $INSTALL_OPTIONS '$PKG'" "$SAGE_LOGS/$PKG_NAME.log" # Do not try to install further packages if one failed if [ $? -ne 0 ]; then exit 1 @@ -798,6 +850,12 @@ install() { exit 0 } + +if [ "$1" = '-package' -o "$1" = "--package" ]; then + shift + exec sage-package $@ +fi + if [ "$1" = '-optional' -o "$1" = "--optional" ]; then shift exec sage-list-packages optional $@ @@ -818,26 +876,15 @@ if [ "$1" = '-installed' -o "$1" = "--installed" ]; then exec sage-list-packages installed $@ fi -if [ "$1" = '-i' ]; then +if [ "$1" = '-p' ]; then shift - # If there are no further arguments, simply list all installed - # packages. + # If there are no further arguments, display usage help. if [ $# -eq 0 ]; then exec sage-spkg fi install "$@" fi -if [ "$1" = '-f' ]; then - shift - # If there are no further arguments, simply list all installed - # packages. - if [ $# -eq 0 ]; then - exec sage-spkg - fi - install -f "$@" -fi - if [ "$1" = '-info' -o "$1" = '--info' ]; then shift for PKG in "$@" diff --git a/src/bin/sage-CSI b/src/bin/sage-CSI index 658f7fe8cee..7b995bee72a 100755 --- a/src/bin/sage-CSI +++ b/src/bin/sage-CSI @@ -183,7 +183,7 @@ if __name__ == '__main__': break if fail: - print('Install the gdb spkg (sage -f gdb) for enhanced tracebacks.') + print('Install the gdb spkg (sage -i gdb) for enhanced tracebacks.') else: filename = save_backtrace(trace) print('Saved trace to {0}'.format(filename)) diff --git a/src/bin/sage-banner b/src/bin/sage-banner index acc11a4a5e3..f2a44eff412 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 6.9.beta0, Release Date: 2015-07-29 │ +│ SageMath Version 6.10.beta2, Release Date: 2015-10-28 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-download-upstream b/src/bin/sage-download-upstream index d7b6b028ba0..c5d9afe31e0 100755 --- a/src/bin/sage-download-upstream +++ b/src/bin/sage-download-upstream @@ -3,6 +3,6 @@ for pkg in $SAGE_ROOT/build/pkgs/* do if [ -d $pkg ]; then - sage-spkg -f -d `basename $pkg` + sage-spkg -d `basename $pkg` fi done diff --git a/src/bin/sage-env b/src/bin/sage-env index 53246b12513..cac4860c415 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -279,13 +279,14 @@ if [ "$UNAME" = "Darwin" ]; then # cause lots of random "Abort trap" issues on OSX. see trac # #7095 for an example. MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` - MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] - if [ $MACOSX_DEPLOYMENT_TARGET = '10.10' ]; then - # Workaround for the libtool version detection bug - # See http://trac.sagemath.org/ticket/17204 + if [ $MACOSX_VERSION -ge 14 ]; then + # various packages have still have issues with + # two digit OS X versions MACOSX_DEPLOYMENT_TARGET=10.9 + else + MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] fi - export MACOSX_DEPLOYMENT_TARGET + export MACOSX_DEPLOYMENT_TARGET MACOSX_VERSION fi # Compile-time path for libraries. This is the equivalent of diff --git a/src/bin/sage-ipython b/src/bin/sage-ipython index 060212e095b..9cb3cc88a2b 100755 --- a/src/bin/sage-ipython +++ b/src/bin/sage-ipython @@ -4,11 +4,11 @@ Sage IPython startup script. """ -from sage.repl.interpreter import SageTerminalApp - -# installs the extra readline commands before the IPython initialization begins. +# Install extra readline commands before IPython initialization from sage.repl.readline_extra_commands import * +from sage.repl.interpreter import SageTerminalApp + app = SageTerminalApp.instance() app.initialize() app.start() diff --git a/src/bin/sage-list-packages b/src/bin/sage-list-packages index 88f0f55264f..8fe00ef0abf 100755 --- a/src/bin/sage-list-packages +++ b/src/bin/sage-list-packages @@ -52,6 +52,8 @@ installed = dict(pkgname_split(pkgname) SAGE_PKGS = os.path.join(SAGE_ROOT, "build", "pkgs") local = {} for p in os.listdir(SAGE_PKGS): + if not os.path.isfile(os.path.join(SAGE_PKGS, p, "checksums.ini")): + continue with open(os.path.join(SAGE_PKGS, p, "package-version.txt")) as f: version = f.read().strip() with open(os.path.join(SAGE_PKGS, p, "type")) as f: diff --git a/src/bin/sage-notebook b/src/bin/sage-notebook index 56243511189..cbe3e56c6f0 100755 --- a/src/bin/sage-notebook +++ b/src/bin/sage-notebook @@ -65,18 +65,16 @@ class NotebookSageNB(object): notebook(*self.args, **self.kwds) -class NotebookIPython(object): +class NotebookJupyter(object): PREREQUISITE_ERROR = textwrap.dedent(""" - The IPython notebook requires ssl, even if you do not use + The Jupyter notebook requires ssl, even if you do not use https. Install the openssl development packages in your system and - then rebuild Python (sage -f python). + then rebuild Python (sage -f python2). """) def __init__(self, argv): - from sage.repl.ipython_kernel.install import \ - SageKernelSpec, have_prerequisites - SageKernelSpec.update() + from sage.repl.ipython_kernel.install import have_prerequisites if not have_prerequisites(): print(self.PREREQUISITE_ERROR) raise SystemExit(1) @@ -109,16 +107,17 @@ EXAMPLES: sage -n -- port=1234 # equivalent sage -n port=1234 # ERROR: invalid notebook name -* Run IPython notebook in custom directory: +* Run Jupyter notebook in custom directory: - sage --notebook=ipython --notebook-dir=/home/foo/bar + sage --notebook=jupyter --notebook-dir=/home/foo/bar """ notebook_launcher = { 'default': NotebookSageNB, # change this to change the default 'sagenb': NotebookSageNB, - 'ipython': NotebookIPython, + 'ipython': NotebookJupyter, + 'jupyter': NotebookJupyter, } notebook_names = ', '.join(notebook_launcher.keys()) @@ -181,8 +180,8 @@ if __name__ == '__main__': parser.print_help() elif launcher == NotebookSageNB: NotebookSageNB([], help=True) - elif launcher == NotebookIPython: - NotebookIPython(['help']) + elif launcher == NotebookJupyter: + NotebookJupyter(['help']) else: parser.print_help() sys.exit(0) diff --git a/src/bin/sage-sdist b/src/bin/sage-sdist index 6b08cec0dc1..69424b20148 100755 --- a/src/bin/sage-sdist +++ b/src/bin/sage-sdist @@ -43,12 +43,10 @@ echo "Sage version $SAGE_VERSION, release date $SAGE_RELEASE_DATE" TARGET="sage-$SAGE_VERSION" sage-clone-source "$SAGE_ROOT" "$TMP_DIR/$TARGET" -# Download and copy all upstream tarballs (the -B option to make forces -# all targets to be built unconditionally) +# Download and copy all upstream tarballs cd "$SAGE_ROOT/build/make" -SAGE_INSTALL_GCC=yes SAGE_SPKG_COPY_UPSTREAM="$TMP_DIR/$TARGET/upstream" \ -SAGE_INSTALL_FETCH_ONLY=yes \ -./install -B all-build +export SAGE_SPKG_COPY_UPSTREAM="$TMP_DIR/$TARGET/upstream" +$MAKE download-for-sdist # Create source .tar.gz cd "$TMP_DIR" diff --git a/src/bin/sage-starts b/src/bin/sage-starts index 9f5e29ef02d..85cbae67e5b 100755 --- a/src/bin/sage-starts +++ b/src/bin/sage-starts @@ -14,7 +14,7 @@ echo "[`date +'%Y-%m-%d %H:%M:%S'`] `cat VERSION.txt 2>/dev/null`" | tee -a logs # sysadmin. We use --nodotsage so we don't force a .sage directory # in the sysadmin's HOME directory. cmd='sage.all._write_started_file(); print "Yes, Sage starts."' -build/make/pipestatus "./sage --nodotsage -c '$cmd' 2>&1" "tee -a logs/start.log" +build/bin/sage-logger "./sage --nodotsage -c '$cmd'" logs/start.log if [ $? -ne 0 ]; then echo >&2 "Sage failed to start up." diff --git a/src/bin/sage-unzip b/src/bin/sage-unzip new file mode 100755 index 00000000000..ca0fdf834a8 --- /dev/null +++ b/src/bin/sage-unzip @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# +# This file is a replacement for the 'unzip' command. +# +# We obtain its main feature (extraction) through the 'zipfile' python module. + +import argparse + +parser = argparse.ArgumentParser(description='Extract the contents of a .zip archive') +parser.add_argument('filename', help="the .zip archive", type=argparse.FileType('r')) +parser.add_argument('-d',help='An optional directory to which to extract files. Set to "." by default.',action='store',default='.') + +args = parser.parse_args() +from zipfile import ZipFile +ZipFile(args.filename).extractall(args.d) diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 493e910c254..9330d0a3dbb 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,4 +1,4 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='6.9.beta0' -SAGE_RELEASE_DATE='2015-07-29' +SAGE_VERSION='6.10.beta2' +SAGE_RELEASE_DATE='2015-10-28' diff --git a/src/doc/common/builder.py b/src/doc/common/builder.py index d7e70ae750e..851b7ee83ad 100755 --- a/src/doc/common/builder.py +++ b/src/doc/common/builder.py @@ -1582,7 +1582,7 @@ def fetch_inventory(self, app, uri, inv): except ValueError: help_message_short(parser=parser, error=True) sys.exit(1) - delete_empty_directories(SAGE_DOC) + delete_empty_directories(SAGE_DOC, verbose=False) # Set up module-wide logging. logger = setup_logger(options.verbose, options.color) @@ -1627,4 +1627,6 @@ def fetch_inventory(self, app, uri, inv): except Exception: print_build_error() raise - + + # Clean up empty directories. + delete_empty_directories(SAGE_DOC, verbose=False) diff --git a/src/doc/common/conf.py b/src/doc/common/conf.py index ae01ffb4a2a..4525abddd52 100644 --- a/src/doc/common/conf.py +++ b/src/doc/common/conf.py @@ -138,17 +138,21 @@ def set_intersphinx_mappings(app): not (os.path.exists(refpath) and os.path.exists(invpath)): app.config.intersphinx_mapping = {} return + app.config.intersphinx_mapping = intersphinx_mapping - def add(subdoc=''): - src = os.path.join(refpath, subdoc) if subdoc else refpath - dst = os.path.join(invpath, subdoc, 'objects.inv') - app.config.intersphinx_mapping[src] = dst + # Add master intersphinx mapping + dst = os.path.join(invpath, 'objects.inv') + app.config.intersphinx_mapping['sagemath'] = (refpath, dst) - add() + # Add intersphinx mapping for subdirectories + # We intentionally do not name these such that these get higher + # priority in case of conflicts for directory in os.listdir(os.path.join(invpath)): if os.path.isdir(os.path.join(invpath, directory)): - add(directory) + src = os.path.join(refpath, directory) + dst = os.path.join(invpath, directory, 'objects.inv') + app.config.intersphinx_mapping[src] = dst pythonversion = sys.version.split(' ')[0] diff --git a/src/doc/common/themes/sage/static/sage.css_t b/src/doc/common/themes/sage/static/sage.css_t index b359f576955..0f3870d979f 100644 --- a/src/doc/common/themes/sage/static/sage.css_t +++ b/src/doc/common/themes/sage/static/sage.css_t @@ -164,12 +164,16 @@ div.body h4, div.body h5, div.body h6 { font-family: {{ theme_headfont }}; - background-color: {{ theme_headbgcolor }}; font-weight: normal; color: {{ theme_headtextcolor }}; - border-bottom: 1px solid #ccc; margin: 20px -20px 10px -20px; padding: 3px 0 3px 10px; + border-bottom: 1px solid #ccc; +} + +div.body h1, +div.body h2 { + background-color: {{ theme_headbgcolor }}; } div.body h1 { margin-top: 0; font-size: 200%; } diff --git a/src/doc/de/thematische_anleitungen/sage_gymnasium.rst b/src/doc/de/thematische_anleitungen/sage_gymnasium.rst index c2913c86bf4..f185bee6ac8 100644 --- a/src/doc/de/thematische_anleitungen/sage_gymnasium.rst +++ b/src/doc/de/thematische_anleitungen/sage_gymnasium.rst @@ -592,7 +592,7 @@ einer rationalen Funktion zu finden. Diese Zerlegung kann auch mit Sage gemacht betrachten, kann diese als Summe von zwei Brüchen geschrieben werden: -.. math:: f(x) = \frac{1}{x^2 - 1} = \frac{\frac{1}{2}}{x^2-1} - \frac{\frac{1}{2}}{x^2+1} +.. math:: f(x) = \frac{1}{x^2 - 1} = \frac{\frac{1}{2}}{x-1} - \frac{\frac{1}{2}}{x+1} Diese Zerlegung findet ``partial_fraction()`` in Sage für uns:: diff --git a/src/doc/de/tutorial/interfaces.rst b/src/doc/de/tutorial/interfaces.rst index d3bf57c2ec0..c452b11220f 100644 --- a/src/doc/de/tutorial/interfaces.rst +++ b/src/doc/de/tutorial/interfaces.rst @@ -178,15 +178,10 @@ die GAP-Schnittstelle aufzurufen: sage: n = G.order(); n 120 -(Für einige GAP-Funktionen sollten Sie zwei optionale Sage Pakete -installieren. Geben Sie ``sage -optional`` ein, um eine Liste zu -erhalten und wählen Sie das Paket aus, das etwa so aussieht -``gap\_packages-x.y.z``. -Geben Sie dann ``sage -i gap\_packages-x.y.z`` ein. Das gleiche machen -Sie bitte mit ``database\_gap-x.y.z``. -Einige nicht-GPL Pakete können installiert -werden, indem Sie sie von der GAP-Website [GAPkg]_ herunter laden und -nach ``$SAGE_ROOT/local/lib/gap-4.4.10/pkg`` entpacken.) +Nach Installation zweier optionaler Sage-Pakete mit folgendem Befehl +sind weitere GAP-Funktionen verfügbar:: + + sage -i gap_packages database_gap Singular diff --git a/src/doc/de/tutorial/programming.rst b/src/doc/de/tutorial/programming.rst index ab8dd1a7009..1c03e5c1bc6 100644 --- a/src/doc/de/tutorial/programming.rst +++ b/src/doc/de/tutorial/programming.rst @@ -174,7 +174,7 @@ Polynome, usw.: :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys from sage.all import * diff --git a/src/doc/en/constructions/groups.rst b/src/doc/en/constructions/groups.rst index 8fab7ae7a04..df29415211f 100644 --- a/src/doc/en/constructions/groups.rst +++ b/src/doc/en/constructions/groups.rst @@ -225,9 +225,8 @@ The group id database ===================== The function ``group_id`` requires that the Small Groups Library of -E. A. O'Brien, B. Eick, and H. U. Besche be installed (you can do -this by typing ``./sage -i database_gap-4.4.9`` in the Sage home -directory). +E. A. O'Brien, B. Eick, and H. U. Besche be installed. You can do +this by typing ``sage -i database_gap`` in the shell. :: diff --git a/src/doc/en/constructions/interface_issues.rst b/src/doc/en/constructions/interface_issues.rst index 71f89b9e845..42c8a81eb70 100644 --- a/src/doc/en/constructions/interface_issues.rst +++ b/src/doc/en/constructions/interface_issues.rst @@ -362,37 +362,6 @@ version of Sage (which is the "source" version, not the "binary"). #. cd ``sage-*`` (we call this ``SAGE_ROOT``) and type ``make``. Now be patient because this process make take 2 hours or so. -#. Optional: When the compilation is finished, type on the command - line in the Sage home directory: - - :: - - ./sage -i database_jones_numfield - ./sage -i database_gap-4.4.8 - ./sage -i database_cremona_ellcurve-2005.11.03 - ./sage -i gap_packages-4.4.8_1 - - This last package loads the GAP GPL'd packages braid, ctbllib, - DESIGN, FactInt, GAPDoc, GRAPE, LAGUNA, SONATA 2.3, and TORIC . It - also compiles (automatically) the C programs in GUAVA and GRAPE. - - Other optional packages to install are at - http://modular.math.washington.edu/sage/packages/optional/. - - .. index:: installation of packages - - Another way: download packages from - http://sage.scipy.org/sage/packages/optional/ and save to the - directory ``SAGE_ROOT``. Type - - :: - - /sage -i sage-package.spkg - - for each ``sage-package`` you download (use ``sage -f`` if you are - reinstalling.) This might be useful if you have a CD of these - packages but no (or a very slow) internet connection. - .. index:: Python and Sage diff --git a/src/doc/en/constructions/plotting.rst b/src/doc/en/constructions/plotting.rst index c8aac355163..f46f3b3d4af 100644 --- a/src/doc/en/constructions/plotting.rst +++ b/src/doc/en/constructions/plotting.rst @@ -260,11 +260,7 @@ Other examples are in the Reference Manual. gnuplot ======= -You must have ``gnuplot`` installed to run these commands. This is an -"experimental package" which, if it isn't installed already on your -machine, can be (hopefully!) installed by typing -``./sage -i gnuplot-4.0.0`` on the command line in the Sage home -directory. +You must have ``gnuplot`` installed to run these commands. .. index:: plot; a function diff --git a/src/doc/en/developer/advanced_git.rst b/src/doc/en/developer/advanced_git.rst index dc441aca653..0717776466e 100644 --- a/src/doc/en/developer/advanced_git.rst +++ b/src/doc/en/developer/advanced_git.rst @@ -131,7 +131,7 @@ operations. Since we want to rewind to before the erroneous *git reset* command, we just have to reset back into the future:: [user@localhost sage]$ git reset --hard HEAD@{2} - + .. _section-git-rewriting-history: @@ -202,7 +202,7 @@ to exclude commit B, otherwise there will be two commits adding being merged into Sage. Hence, we first reset the first branch back to before B was added:: - [user@localhost]$ git checkout first_branch + [user@localhost]$ git checkout first_branch Switched to branch 'first_branch' [user@localhost]$ git reset --hard bf817a5 HEAD is now at bf817a5 added file A @@ -245,14 +245,14 @@ Now we start by making an identical branch to the first branch:: [user@localhost]$ git checkout -b second_branch Switched to a new branch 'second_branch' [user@localhost]$ git rebase -i HEAD~3 - + This will open an editor with the last 3 (corresponding to ``HEAD~3``) commits and instuctions for how to modify them:: pick bf817a5 added file A pick 7873447 added file B pick 9621dae added file C - + # Rebase 5b5588e..9621dae onto 5b5588e # # Commands: @@ -270,11 +270,11 @@ commits and instuctions for how to modify them:: # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out - + To only use commit B, we delete the first and third line. Then save and quit your editor, and your branch now consists only of the B commit. You still have to delete the B commit from the first branch, so you would go back (``git checkout first_branch``) and then run the same ``git rebase -i`` command and delete the B commit. - + diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index b1286bd00c9..5864c2bd12a 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -130,7 +130,7 @@ of several different types of polynomial rings. If you want to create a new directory in the Sage library ``SAGE_ROOT/src/sage`` (say, ``measure_theory``), that directory -should contain a file ``__init__.py`` that contains the single line +should contain a file ``__init__.py`` that contains the single line ``import all`` in addition to whatever files you want to add (say, ``borel_measure.py`` and ``banach_tarski.py``), and also a file ``all.py`` listing imports from @@ -181,7 +181,7 @@ The top of each Sage code file should follow this format:: - YOUR NAME (2005-01-03): initial version - person (date in ISO year-month-day format): short desc - + EXAMPLES:: diff --git a/src/doc/en/developer/coding_in_cython.rst b/src/doc/en/developer/coding_in_cython.rst index 16e9a544a81..beef51a48cd 100644 --- a/src/doc/en/developer/coding_in_cython.rst +++ b/src/doc/en/developer/coding_in_cython.rst @@ -8,7 +8,7 @@ This chapter discusses Cython, which is a compiled language based on Python. The major advantage it has over Python is that code can be much faster (sometimes orders of magnitude) and can directly call C and C++ code. As Cython is essentially a superset of the Python -language, one often doesn’t make a distinction between Cython and +language, one often doesn’t make a distinction between Cython and Python code in Sage (e.g. one talks of the “Sage Python Library” and “Python Coding Conventions”). diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index f92449dca1d..d8b74192727 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -190,7 +190,7 @@ replacements are made: - Sage supports a special syntax for generating rings or, more generally, parents with named generators:: - + sage: R. = QQ[] sage: preparse('R. = QQ[]') "R = QQ['x, y']; (x, y,) = R._first_ngens(2)" @@ -203,7 +203,7 @@ replacements are made: 4 sage: 87.factor() 3 * 29 - + - Raw literals are not preparsed, which can be useful from an efficiency point of view. Just like Python ints are denoted by an L, in Sage raw integer and floating literals are followed by an "r" (or @@ -503,7 +503,7 @@ documentation for more information on its behaviour and optional arguments. def my_new_function(): ... - my_old_function = deprecated_function_alias(my_new_function) + my_old_function = deprecated_function_alias(666, my_new_function) * **Moving an object to a different module:** if you rename a source file or move some function (or class) to a diff --git a/src/doc/en/developer/git_trac.rst b/src/doc/en/developer/git_trac.rst index 8363fc170d1..218baf14f0e 100644 --- a/src/doc/en/developer/git_trac.rst +++ b/src/doc/en/developer/git_trac.rst @@ -89,7 +89,7 @@ to the Sage directory and tell ``git trac`` about your trac account:: Password: PASSWORD Retrieving SSH keys... 1024 ab:1b:7c:c9:9b:48:fe:dd:59:56:1e:9d:a4:a6:51:9d My SSH Key - + where you have to replace USERNAME with your trac user name and PASSWORD with your trac password. If you don't have a trac account, use ``git trac config`` without any arguments. The single quotes in @@ -107,7 +107,7 @@ any changes. .. note:: The ``git trac config`` command will automatically add a ``trac`` - remote git repository to your list of remotes if necessary. + remote git repository to your list of remotes if necessary. If you followed the above instructions then you will have two remote repositories set up:: @@ -215,9 +215,9 @@ changes. Making Changes -------------- -Once you have checked out a ticket, edit the appropriate files and -commit your changes to the branch as described in -:ref:`section-walkthrough-add-edit` and +Once you have checked out a ticket, edit the appropriate files and +commit your changes to the branch as described in +:ref:`section-walkthrough-add-edit` and :ref:`section-walkthrough-commit`. .. _section-git_trac-push: @@ -253,7 +253,7 @@ the following logic to find out the remote branch name: * If there is no remote branch yet, the branch will be called ``u/user/description`` (``u/user/last_twin_prime`` in the example). - + * You can use the ``--branch`` option to specify the remote branch name explicitly, but it needs to follow the naming convention from :ref:`section-git_trac-branch-names` for you to have write @@ -272,7 +272,7 @@ to specify the ticket number (since there is no way to tell which ticket you have in mind). That is:: [user@localhost sage]$ git trac push TICKETNUM - + where you have to replace ``TICKETNUM`` with the number of the trac ticket. @@ -387,7 +387,7 @@ more on it:: [bob@home sage]$ git trac checkout TICKET_NUMBER ... EDIT EDIT ... [bob@home sage]$ git add . - [bob@home sage]$ git commit + [bob@home sage]$ git commit [bob@home sage]$ git trac push The trac ticket now has "Branch:" set to ``u/bob/a_and_b_ticket``, @@ -397,18 +397,18 @@ pull/push in their collaboration:: [alice@laptop sage]$ git trac pull ... EDIT EDIT ... [alice@laptop sage]$ git add . - [alice@laptop sage]$ git commit + [alice@laptop sage]$ git commit [alice@laptop sage]$ git trac push [bob@home sage]$ git trac pull ... EDIT EDIT ... [bob@home sage]$ git add . - [bob@home sage]$ git commit + [bob@home sage]$ git commit [bob@home sage]$ git trac push Alice and Bob need not alternate, they can also add further commits on top of their own remote branch. As long as their changes do not -conflict (edit the same lines simultaneously), this is fine. +conflict (edit the same lines simultaneously), this is fine. .. _section-git_trac-conflict: @@ -495,7 +495,7 @@ recent common parent of both. It is now Alice's job to resolve the conflict by reconciling their changes, for example by editing the file. Her result is:: - + def fibonacci(i): """ Return the `i`-th Fibonacci number @@ -503,22 +503,22 @@ changes, for example by editing the file. Her result is:: if i > 1: return fibonacci(i-1) + fibonacci(i-2) return [0, 1][i] - + And then upload both her original change *and* her merge commit to trac:: [alice@laptop sage]$ git add fibonacci.py [alice@laptop sage]$ git commit -m "merged Bob's changes with mine" The resulting commit graph now has a loop:: - + [alice@laptop sage]$ git log --graph --oneline * 6316447 merged Bob's changes with mine - |\ + |\ | * 41675df corrected recursion formula, must be + instead of * * | 14ae1d3 return correct seed values - |/ + |/ * 14afe53 initial commit - + If Bob decides to do further work on the ticket then he will have to pull Alice's changes. However, this time there is no conflict on his end: git downloads both Alice's conflicting commit and her resolution. diff --git a/src/doc/en/developer/manual_git.rst b/src/doc/en/developer/manual_git.rst index 4be27836795..ae31a1dd327 100644 --- a/src/doc/en/developer/manual_git.rst +++ b/src/doc/en/developer/manual_git.rst @@ -44,7 +44,7 @@ of them as bookmarks. You can then use ``git pull`` to get changes and [user@localhost sage]$ git trac [ARGS] .. note:: - + In the command above we set up the remote to only track the ``master`` branch on the trac server (the ``-t master`` option). This avoids clutter by not automatically downloading all @@ -327,4 +327,4 @@ screenshot. The middle file is the most recent common parent; on the right is Bob's version and on the left is Alice's conflicting version. Clicking on the arrow moves the marked change to the file in the adjacent -pane. +pane. diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 3c02e00abc6..1dfa4eea6a2 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -8,7 +8,7 @@ One of the mottoes of the Sage project is to not reinvent the wheel: If an algorithm is already implemented in a well-tested library then consider incorporating that library into Sage. The current list of available packages are the subdirectories of ``SAGE_ROOT/build/pkgs/``. -The management of packages is done through a bash script located in +The installation of packages is done through a bash script located in ``SAGE_ROOT/build/bin/sage-spkg``. This script is typically invoked by giving the command:: @@ -16,15 +16,11 @@ giving the command:: options can be: -- f: install a package even if the same version is already installed -- s: do not delete temporary build directory -- c: after installing, run the test suite for the spkg. This should +- -f: install a package even if the same version is already installed +- -s: do not delete temporary build directory +- -c: after installing, run the test suite for the spkg. This should override the settings of ``SAGE_CHECK`` and ``SAGE_CHECK_PACKAGES``. -- d: only download the package - -Not all packages are built by default, they are divided into standard, -optional and experimental ones. Standard packages are built by default -and have much more stringent quality requirements. +- -d: only download the package The section :ref:`section-directory-structure` describes the structure of each individual package in ``SAGE_ROOT/build/pkgs``. In section @@ -34,44 +30,90 @@ spkg that you or someone else wrote. Finally, for inclusion in the Sage source code. +.. _section-package-types: + +Package types +============= + +Not all packages are built by default, they are divided into standard, +optional and experimental ones: + +- **standard** packages are built by default. They have stringent quality + requirements: they should work on all supported platforms. In order + for a new standard package to be accepted, it should have been + optional for a while, see :ref:`section-inclusion-procedure`. + +- **optional** packages are subject to the same requirements, they + should also work on all supported platforms. If there are + :ref:`optional doctests ` in the Sage + library, those tests must pass. + Note that optional packages are not tested as much as standard + packages, so in practice they might break more often than standard + packages. + +- for **experimental** packages, the bar is much lower: even if there are + some problems, the package can still be accepted. + + .. _section-directory-structure: Directory Structure =================== -Third-party packages in Sage consists of two parts: +Third-party packages in Sage consists of two parts: #. The tarball as it is distributed by the third party, or as close as possible. Valid reasons for modifying the tarball are deleting - unnecessary files to keep the download size manageable or - regenerating auto-generated files if necessary. But the actual code - must be unmodified. See also :ref:`section-spkg-src`. + unnecessary files to keep the download size manageable, + regenerating auto-generated files or changing the directory structure + if necessary. In certain cases, you may need to (additionally) change + the filename of the tarball. + In any case, the actual code must be unmodified: if you need to + change the sources, add a :ref:`patch ` + instead. See also :ref:`section-spkg-src` for automating the + modifications to the upstream tarball. #. The build scripts and associated files are in a subdirectory - ``SAGE_ROOT/build/pkgs/package``, where you replace ``package`` - with a lower-case version of the upstream project name. + ``SAGE_ROOT/build/pkgs/``, where you replace ```` + with a lower-case version of the upstream project name. If the + project name contains characters which are not alphanumeric + and are not an underscore, those characters should be removed + or replaced by an underscore. For example, the project + ``FFLAS-FFPACK`` is called ``fflas_ffpack`` in Sage and ``path.py`` + is renamed ``pathpy`` in Sage. As an example, let us consider a hypothetical FoO project. They -(upstream) distribute a tarball ``foo-1.3.tar.gz`` (that will be +(upstream) distribute a tarball ``FoO-1.3.tar.gz`` (that will be automatically placed in ``SAGE_ROOT/upstream`` during the installation -process). To package it in Sage, we create a subdirectory containing the -following:: +process). To package it in Sage, we create a subdirectory containing as +a minimum the following files:: + + SAGE_ROOT/build/pkgs/foo + |-- checksums.ini + |-- dependencies + |-- package-version.txt + |-- spkg-install + |-- SPKG.txt + `-- type + +The following are some additional files which can be added:: SAGE_ROOT/build/pkgs/foo |-- patches | |-- bar.patch | `-- baz.patch - |-- checksums.ini - |-- package-version.txt |-- spkg-check - |-- spkg-install - |-- spkg-src - `-- SPKG.txt + `-- spkg-src -When installing Sage these files are used to patch the tarball and to -start the build and install process of the package. +We discuss the individual files in the following sections. -We discuss the individual files in the following. + +Package type +------------ + +The file ``type`` should contain a single word, which is either +``standard``, ``optional`` or ``experimental``. +See :ref:`section-package-types` for the meaning of these types. .. _section-spkg-install: @@ -79,11 +121,11 @@ We discuss the individual files in the following. Install Script -------------- -The ``spkg-install`` file is a shell script installing the package, -with ``PACKAGE_NAME`` replaced by the the package name. In the best -case, the upstream project can simply be installed by the usual -configure / make / make install steps. In that case, the build script -would simply consist of:: +The ``spkg-install`` file is a shell script or Python script which +installs the package. +In the best case, the upstream project can simply be installed by the +usual configure / make / make install steps. In that case, the build +script would simply consist of:: #!/usr/bin/env bash @@ -101,13 +143,12 @@ would simply consist of:: exit 1 fi - $MAKE -j1 install + $MAKE install if [ $? -ne 0 ]; then echo >&2 "Error installing PACKAGE_NAME." exit 1 fi - Note that the top-level directory inside the tarball is renamed to ``src`` before calling the ``spkg-install`` script, so you can just use ``cd src`` instead of ``cd foo-1.3``. @@ -125,8 +166,6 @@ install it:: mkdir -p "$SAGE_LOCAL/share/doc/PACKAGE_NAME" cp -R doc/* "$SAGE_ROOT/local/share/doc/PACKAGE_NAME" fi - - .. _section-spkg-check: @@ -147,27 +186,6 @@ the ``spkg-check`` script would simply contain:: $MAKE check -.. _section-spkg-versioning: - -Package Versioning ------------------- - -The ``package-version.txt`` file containts just the version. So if -upstream is ``foo-1.3.tar.gz`` then the package version file would only -contain ``1.3``. - -If the upstream package is taken from some revision other than a stable -version, you should use the date at which the revision is made, e.g. the -Singular package ``20090818`` is made with the revision as of -2009-08-18. - -If you made any changes to the upstream tarball (see -:ref:`section-directory-structure` for allowable changes) then you -should append a ``.p1`` to the version. If you make further changes, -increase the patch level as necessary. So the different versions would -be ``1.3``, ``1.3.p1``, ``1.3.p2``, ... - - .. _section-spkg-SPKG-txt: The SPKG.txt File @@ -185,12 +203,6 @@ The ``SPKG.txt`` file should follow this pattern:: What is the license? If non-standard, is it GPLv3+ compatible? - == SPKG Maintainers == - - * Mary Smith - * Bill Jones - * Leonhard Euler - == Upstream Contact == Provide information for upstream contact. @@ -213,6 +225,61 @@ with ``PACKAGE_NAME`` replaced by the the package name. Legacy information is now kept in the git repository. +.. _section-dependencies: + +Package dependencies +-------------------- + +Many packages depend on other packages. Consider for example the +``eclib`` package for elliptic curves. This package uses the libraries +PARI, NTL and FLINT. So the following is the ``dependencies`` file +for ``eclib``:: + + $(INST)/$(PARI) $(INST)/$(NTL) $(INST)/$(FLINT) + + ---------- + All lines of this file are ignored except the first. + It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. + +If there are no dependencies, you can use :: + + # no dependencies + + ---------- + All lines of this file are ignored except the first. + It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. + +There are actually two kinds of dependencies: there are normal +dependencies and order-only dependencies, which are weaker. The syntax +for the ``dependencies`` file is :: + + normal dependencies | order-only dependencies + +If there is no ``|``, then all dependencies are normal. + +- If package A has an **order-only dependency** on B, it simply means + that B must be built before A can be built. The version of B does not + matter, only the fact that B is installed matters. + This should be used if the dependency is purely a build-time + dependency (for example, a dependency on Python simply because the + ``spkg-install`` file is written in Python). + +- If A has a **normal dependency** on B, it means additionally that A + should be rebuilt every time that B gets updated. This is the most + common kind of dependency. A normal dependency is what you need for + libraries: if we upgrade NTL, we should rebuild everything which + uses NTL. + +In order to check that the dependencies of your package are likely +correct, the following command should work without errors:: + + [user@localhost]$ make distclean && make base && make PACKAGE_NAME + +Finally, note that standard packages should only depend on standard +packages and optional packages should only depend on standard or +optional packages. + + .. _section-spkg-patching: Patching Sources @@ -230,12 +297,12 @@ typical patch file should look like this:: what the patch does, where you got it from if you did not write it yourself, if they are platform specific, if they should be pushed upstream, etc... - + diff -dru Sphinx-1.2.2/sphinx/ext/autodoc.py.orig Sphinx-1.2.2/sphinx/ext/autodoc.py --- Sphinx-1.2.2/sphinx/ext/autodoc.py.orig 2014-03-02 20:38:09.000000000 +1300 +++ Sphinx-1.2.2/sphinx/ext/autodoc.py 2014-10-19 23:02:09.000000000 +1300 @@ -1452,6 +1462,7 @@ - + app.add_config_value('autoclass_content', 'class', True) app.add_config_value('autodoc_member_order', 'alphabetic', True) + app.add_config_value('autodoc_builtin_argspec', None, True) @@ -274,6 +341,41 @@ changes. This not only serves as documentation but also makes it easier to apply the same modifications to future versions. +.. _section-spkg-versioning: + +Package Versioning +------------------ + +The ``package-version.txt`` file containts just the version. So if +upstream is ``FoO-1.3.tar.gz`` then the package version file would only +contain ``1.3``. + +If the upstream package is taken from some revision other than a stable +version or if upstream doesn't have a version number, you should use the +date at which the revision is made. For example, the +``database_stein_watkins`` package with version ``20110713`` contains +the database as of 2011-07-13. Note that the date should refer to the +*contents* of the tarball, not to the day it was packaged for Sage. +This particular Sage package for ``database_stein_watkins`` was created +in 2014, but the data it contains was last updated in 2011. + +If you apply any patches, or if you made changes to the upstream tarball +(see :ref:`section-directory-structure` for allowable changes), +then you should append a ``.p0`` to the version to indicate that it's +not a vanilla package. + +Additionally, whenever you make changes to a package *without* changing +the upstream tarball (for example, you add an additional patch or you +fix something in the ``spkg-install`` file), you should also add or +increase the patch level. So the different versions would +be ``1.3``, ``1.3.p0``, ``1.3.p1``, ... +The change in version number or patch level will trigger +re-installation of the package, such that the changes are taken into +account. + + +.. _section-spkg-checksums: + Checksums --------- @@ -281,31 +383,47 @@ The ``checksums.ini`` file contains checksums of the upstream tarball. It is autogenerated, so you just have to place the upstream tarball in the ``SAGE_ROOT/upstream/`` directory and run:: - [user@localhost]$ sage -sh sage-fix-pkg-checksums + [user@localhost]$ sage --fix-pkg-checksums .. _section-manual-build: -Manual package build and installation -===================================== +Building the package +==================== At this stage you have a new tarball that is not yet distributed with -Sage (``foo-1.3.tar.gz`` in the example of section +Sage (``FoO-1.3.tar.gz`` in the example of section :ref:`section-directory-structure`). Now you need to manually place it -in the ``SAGE_ROOT/upstream/`` directory. Then you can run the -installation via:: +in the ``SAGE_ROOT/upstream/`` directory and run +``sage --fix-pkg-checksums`` if you have not done that yet. + +In order to update ``build/make/Makefile``, which contains the rules +to build all packages, you need to run the following command from +``SAGE_ROOT``:: + + [user@localhost]$ ./configure + +You need to re-run ``./configure`` whenever you change any package +metadata: if you add or remove a package or if you change the version, +type or dependencies of a package. + +Now you can install the package using:: [user@localhost]$ sage -i package_name or:: - [user@localhost]$ sage -i -f package_name + [user@localhost]$ sage -f package_name to force a reinstallation. If your package contains a ``spkg-check`` script (see :ref:`section-spkg-check`) it can be run with:: [user@localhost]$ sage -i -c package_name +or:: + + [user@localhost]$ sage -f -c package_name + If all went fine, open a ticket, put a link to the original tarball in the ticket and upload a branch with the code under ``SAGE_ROOT/build/pkgs``. @@ -330,10 +448,8 @@ requirements are described in the following sections. After the ticket was reviewed and included, optional packages stay in that status for at least a year, after which they can be proposed to be included as standard packages in Sage. For this a trac ticket is opened -with the ``Component:`` field set to ``packages:standard``. Note that -the script in ``SAGE_ROOT/build/make/deps`` is called when building Sage so -please include the build command for your standard package there. Then -make a proposal in the Google Group ``sage-devel``. +with the ``Component:`` field set to ``packages:standard``. Then make +a proposal in the Google Group ``sage-devel``. Upgrading packages to new upstream versions or with additional patches includes opening a ticket in the respective category too, as described diff --git a/src/doc/en/developer/packaging_old_spkgs.rst b/src/doc/en/developer/packaging_old_spkgs.rst index 49220bf09b6..3cc76ba9a8e 100644 --- a/src/doc/en/developer/packaging_old_spkgs.rst +++ b/src/doc/en/developer/packaging_old_spkgs.rst @@ -5,8 +5,14 @@ Packaging Old-Style SPKGs ========================= This chapter explains old-style spkgs; It applies only to legacy -optional spkgs and experimental spkgs. See :ref:`chapter-packaging` -for the modern way of packaging third-party software. +optional spkgs and experimental spkgs. + +.. WARNING:: + + Old-style packages are **deprecated**, it is strongly + suggested that you make a new-style package instead. + See :ref:`chapter-packaging` + for the modern way of packaging third-party software. Creating an Old-Style SPKG @@ -32,7 +38,7 @@ to discourage confusion. Although Sage packages are packed using tar and/or bzip2, note that ``.spkg`` files contain control information (installation scripts and metadata) that are necessary for building and installing them. When you compile Sage from a source distribution -(or when you run ``sage -i `` or ``sage -f ``), the file +(or when you run ``sage -p ``), the file ``SAGE_ROOT/build/bin/sage-spkg`` takes care of the unpacking, compilation, and installation of Sage packages for you. You can type:: @@ -309,7 +315,7 @@ refereed. Do not post the spkg itself to the trac server: you only need to provide a link to your spkg. If your spkg gets a positive review, it might be included into the core Sage library, or it might become an optional download from the Sage website, so anybody can -automatically install it by typing ``sage -i mypackage-version.spkg``. +automatically install it by typing ``sage -p mypackage-version.spkg``. .. note:: @@ -530,11 +536,11 @@ command line options ``--pkg`` or ``--pkg_nc`` (or tar and bzip2). To install your replacement spkg, you use:: - sage -f http://URL/to/package-x.y.z.spkg + sage -p http://URL/to/package-x.y.z.spkg or:: - sage -f /path/to/package-x.y.z.spkg + sage -p /path/to/package-x.y.z.spkg To compile Sage from source with the replacement (standard) spkg, untar a Sage source tarball, remove the existing spkg under diff --git a/src/doc/en/developer/walk_through.rst b/src/doc/en/developer/walk_through.rst index 16ec2102796..b760d7b96f0 100644 --- a/src/doc/en/developer/walk_through.rst +++ b/src/doc/en/developer/walk_through.rst @@ -29,7 +29,7 @@ Configuring Git One way or another, ``git`` is what Sage uses for tracking changes. So first, open a shell (for instance, Terminal on Mac) and check that ``git`` works:: - + [user@localhost]$ git usage: git [--version] [--help] [-C ] [-c name=value] ... @@ -37,7 +37,7 @@ So first, open a shell (for instance, Terminal on Mac) and check that add Add file contents to the index ... tag Create, list, delete or verify a tag object signed with GPG - + 'git help -a' and 'git help -g' lists available subcommands and some concept guides. See 'git help ' or 'git help ' to read about a specific subcommand or concept. @@ -74,7 +74,7 @@ internal git repository:: Cloning into 'sage'... [...] Checking connectivity... done. - + This creates a directory named ``sage`` containing the sources for the current stable and development releases of Sage. You will need to `compile Sage `_ @@ -127,7 +127,7 @@ you have to use ``git checkout``:: Note that, unless you explicitly upload ("push") a branch to remote git repository, the local branch will only be on your computer and not -visible to anyone else. +visible to anyone else. To avoid typing the new branch name twice you can use the shortcut ``git checkout -b my_new_branch`` to create and switch to the new @@ -177,15 +177,15 @@ changes:: Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) - + modified: some_file.py modified: src/sage/primes/all.py - + Untracked files: (use "git add ..." to include in what will be committed) - + src/sage/primes/last_pair.py - + no changes added to commit (use "git add" and/or "git commit -a") To dig deeper into what was changed in the files you can use:: diff --git a/src/doc/en/developer/workflows.rst b/src/doc/en/developer/workflows.rst index 800ea3284c3..eca4356dfe0 100644 --- a/src/doc/en/developer/workflows.rst +++ b/src/doc/en/developer/workflows.rst @@ -141,7 +141,7 @@ course free to use a different one:: Unpacking objects: 100% (74/74), done. From github.com:github_user_name/sage * [new branch] master -> github/master - + Develop ------- diff --git a/src/doc/en/faq/faq-usage.rst b/src/doc/en/faq/faq-usage.rst index 4b77b324775..a9e5abe4565 100644 --- a/src/doc/en/faq/faq-usage.rst +++ b/src/doc/en/faq/faq-usage.rst @@ -97,7 +97,7 @@ tcl/tk development library. On Ubuntu, this is the command :: or something along that line. Next, reinstall Sage's Python:: - sage -f python + sage -f python2 This will pick up the tcl/tk library automatically. After successfully reinstalling Sage's Python, from within the Sage command line interface, diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 8267b5e4168..e76f87355c6 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -96,6 +96,7 @@ computer: - **perl**: version 5.8.0 or later. - **ar** and **ranlib**: can be obtained as part of GNU binutils. - **tar**: GNU tar version 1.17 or later, or BSD tar. +- **python**: Python >= 2.6. Fortran and compiler suites ########################### @@ -142,7 +143,12 @@ System-specific requirements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On Mac OS X, there are various developer tools needed which may require -some registration on Apple's developer site; see :ref:`section_macprereqs`. +some registration on Apple's developer site; see +:ref:`section_macprereqs`. + +On Redhat-derived systems not all perl components are installed by +default and you might have to install the **perl-ExtUtils-MakeMaker** +package. On recent Debian or Ubuntu systems, the **dpkg-dev** package is needed for `multiarch `_ support. @@ -182,13 +188,21 @@ distribution, but on a `Debian `_ based system (e.g. you would use `apt-get `_:: - sudo apt-get install binutils gcc make m4 perl tar + # debian + sudo apt-get install binutils gcc make m4 perl tar git + # redhat + sudo yum install binutils gcc make m4 perl tar git perl-ExtUtils-MakeMaker + to install all general requirements, or, if you don't want Sage to build its own GCC:: - sudo apt-get install binutils gcc g++ gfortran make m4 perl tar + # debian + sudo apt-get install binutils gcc g++ gfortran make m4 perl tar git + # redhat + sudo yum install binutils gcc gcc-c++ gcc-gfortran make m4 perl tar git perl-ExtUtils-MakeMaker + (This was tested on Ubuntu 12.04.2.) On other Linux systems, you might use `rpm `_, @@ -402,8 +416,8 @@ or similar commands. If you installed Sage first, all is not lost. You just need to rebuild Sage's Python and any part of Sage relying on it:: - sage -f python # rebuild Python - make # rebuild components of Sage depending on Python + sage -f python2 # rebuild Python + make # rebuild components of Sage depending on Python after installing the Tcl/Tk development libraries as above. @@ -705,7 +719,7 @@ Starting from a fresh Sage tarball:: And if you've already built Sage:: ./sage -i openssl - ./sage -f python + ./sage -f python2 make ssl The third line will rebuild all parts of Sage that depend on Python; @@ -881,7 +895,7 @@ Here are some of the more commonly used variables affecting the build process: maximum of 8 and a minimum of 2). - :envvar:`SAGE_CHECK` - if set to ``yes``, then during the build process, - and when running ``sage -i `` or ``sage -f ``, + or when installing packages manually, run the test suite for each package which has one. See also :envvar:`SAGE_CHECK_PACKAGES`. diff --git a/src/doc/en/prep/Calculus.rst b/src/doc/en/prep/Calculus.rst index acf2fa3408f..109666620da 100644 --- a/src/doc/en/prep/Calculus.rst +++ b/src/doc/en/prep/Calculus.rst @@ -393,8 +393,8 @@ Fundamental Theorem of Calculus is not entirely helpful. Recall that :: - sage: integral(h,(x,0,pi/8)) - 1/2*log(sin(1/8*pi) + 1) - 1/2*log(-sin(1/8*pi) + 1) + sage: integral(h,(x,0,pi/7)) + 1/2*log(sin(1/7*pi) + 1) - 1/2*log(-sin(1/7*pi) + 1) Here, just a number might be more helpful. Sage has several ways of numerical evaluating integrals. @@ -411,7 +411,7 @@ was also mentioned in the introductory tutorial. :: sage: N(integral(h,(x,0,pi/8))) - 0.403199719161512 + 0.403199719161511 The second function, ``numerical_integral``, uses a powerful numerical program (the GNU Scientific Library). diff --git a/src/doc/en/prep/Programming.rst b/src/doc/en/prep/Programming.rst index ccdc524f618..785b67873db 100644 --- a/src/doc/en/prep/Programming.rst +++ b/src/doc/en/prep/Programming.rst @@ -337,18 +337,14 @@ That's it. This sort of turns the loop around. - The notation is easiest if you think of it mathematically; "The set of :math:`n^2`, for (all) :math:`n` in the range between 3 and 13." -This is phenomenally useful. Here is a nice plotting example. - -:: +This is phenomenally useful. Here is a nice plotting example:: sage: plot([x^n for n in [2..6]],(x,0,1)) Graphics object consisting of 5 graphics primitives Now we apply it to the example we were doing in the first place. Notice we now have a nice concise description of all determinants of these -matrices, without the syntax of colon and indentation. - -:: +matrices, without the syntax of colon and indentation:: sage: [det(A^i) for i in [0..4]] [1, -2, 4, -8, 16] @@ -361,15 +357,15 @@ Tables Finally, getting away from strictly programming, here is a useful tip. Some of you may be familiar with a way to take such data and put it in -tabular form from other programs. The ``html.table`` command does this -for us. +tabular form from other programs. The ``table`` command does this +for us:: -.. skip - -:: - - sage: html.table( [ (i,det(A^i)) for i in [0..4] ] ) - ... + sage: table( [ (i,det(A^i)) for i in [0..4] ] ) + 0 1 + 1 -2 + 2 4 + 3 -8 + 4 16 Notice that each element of *this* list is two items in parentheses (a so\-called *tuple*). @@ -377,12 +373,17 @@ so\-called *tuple*). Even better, we can put a header line on it to make it really clear what we are doing, by adding lists. We've seen keywords like ``header=True`` when doing some of our plotting and limits. What do you think will -happen if you put dollar signs around the labels in the header? +happen if you put dollar signs around the labels in the header? :: -:: + sage: table( [('i', 'det(A^i)')] + [ (i,det(A^i)) for i in [0..4] ], header_row=True) + i det(A^i) + +---+----------+ + 0 1 + 1 -2 + 2 4 + 3 -8 + 4 16 - sage: html.table( [('i', 'det(A^i)')] + [ (i,det(A^i)) for i in [0..4] ] , header=True) - ... .. _Defs: diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index f80229432e5..f07d5a3f78e 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -4,6 +4,8 @@ Algebras .. toctree:: :maxdepth: 2 + sage/algebras/associated_graded + sage/algebras/catalog sage/algebras/clifford_algebra @@ -25,6 +27,8 @@ Algebras sage/algebras/free_algebra_quotient sage/algebras/free_algebra_quotient_element + sage/algebras/free_zinbiel_algebra + sage/algebras/group_algebra sage/algebras/iwahori_hecke_algebra diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index ebeb2d010b8..34cf4165a73 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -1,47 +1,30 @@ -Category Theory -=============== +Category Framework +================== + +The Sage Category Framework +--------------------------- .. toctree:: :maxdepth: 2 sage/categories/primer - sage/categories/tutorial sage/categories/category - sage/categories/category_types - sage/categories/category_singleton sage/categories/category_with_axiom - sage/categories/map - sage/categories/homset - sage/categories/morphism sage/categories/functor - sage/categories/pushout + sage/categories/tutorial -Functorial constructions -======================== +Maps and Morphisms +------------------ .. toctree:: :maxdepth: 2 - sage/categories/covariant_functorial_construction - - sage/categories/cartesian_product - sage/categories/tensor - sage/categories/dual - sage/categories/algebra_functor - - sage/categories/subquotients - sage/categories/quotients - sage/categories/subobjects - sage/categories/isomorphic_objects - - sage/categories/homsets - - sage/categories/realizations - sage/categories/with_realizations - + sage/categories/map + sage/categories/homset + sage/categories/morphism -Categories -========== +Individual Categories +--------------------- .. toctree:: :maxdepth: 2 @@ -73,6 +56,7 @@ Categories sage/categories/coxeter_group_algebras sage/categories/coxeter_groups sage/categories/crystals + sage/categories/cw_complexes sage/categories/discrete_valuation sage/categories/distributive_magmas_and_additive_magmas sage/categories/division_rings @@ -80,6 +64,10 @@ Categories sage/categories/enumerated_sets sage/categories/euclidean_domains sage/categories/fields + sage/categories/filtered_algebras + sage/categories/filtered_algebras_with_basis + sage/categories/filtered_modules + sage/categories/filtered_modules_with_basis sage/categories/finite_coxeter_groups sage/categories/finite_crystals sage/categories/finite_dimensional_algebras_with_basis @@ -111,6 +99,7 @@ Categories sage/categories/graded_hopf_algebras_with_basis sage/categories/graded_modules sage/categories/graded_modules_with_basis + sage/categories/graphs sage/categories/group_algebras sage/categories/groupoid sage/categories/groups @@ -122,10 +111,13 @@ Categories sage/categories/integral_domains sage/categories/lattice_posets sage/categories/left_modules + sage/categories/lie_groups sage/categories/magmas sage/categories/magmas_and_additive_magmas sage/categories/magmatic_algebras + sage/categories/manifolds sage/categories/matrix_algebras + sage/categories/metric_spaces sage/categories/modular_abelian_varieties sage/categories/modules sage/categories/modules_with_basis @@ -152,21 +144,47 @@ Categories sage/categories/sets_cat sage/categories/sets_with_grading sage/categories/sets_with_partial_maps + sage/categories/simplicial_complexes + sage/categories/topological_spaces sage/categories/unique_factorization_domains sage/categories/unital_algebras sage/categories/vector_spaces sage/categories/weyl_groups Technical Categories -==================== +~~~~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 2 sage/categories/facade_sets +Functorial constructions +------------------------ + +.. toctree:: + :maxdepth: 2 + + sage/categories/covariant_functorial_construction + + sage/categories/cartesian_product + sage/categories/tensor + sage/categories/dual + sage/categories/algebra_functor + + sage/categories/subquotients + sage/categories/quotients + sage/categories/subobjects + sage/categories/isomorphic_objects + + sage/categories/homsets + + sage/categories/realizations + sage/categories/with_realizations + + Examples of parents using categories -===================================== +------------------------------------- .. toctree:: :maxdepth: 2 @@ -176,15 +194,20 @@ Examples of parents using categories sage/categories/examples/commutative_additive_semigroups sage/categories/examples/coxeter_groups sage/categories/examples/crystals + sage/categories/examples/cw_complexes sage/categories/examples/facade_sets sage/categories/examples/finite_coxeter_groups + sage/categories/examples/finite_dimensional_algebras_with_basis sage/categories/examples/finite_enumerated_sets sage/categories/examples/finite_monoids sage/categories/examples/finite_semigroups sage/categories/examples/finite_weyl_groups + sage/categories/examples/graded_connected_hopf_algebras_with_basis sage/categories/examples/graded_modules_with_basis + sage/categories/examples/graphs sage/categories/examples/hopf_algebras_with_basis sage/categories/examples/infinite_enumerated_sets + sage/categories/examples/manifolds sage/categories/examples/monoids sage/categories/examples/posets sage/categories/examples/semigroups_cython @@ -193,13 +216,14 @@ Examples of parents using categories sage/categories/examples/sets_with_grading sage/categories/examples/with_realizations -Miscellaneous -============= +Internals +--------- .. toctree:: :maxdepth: 2 - sage/categories/action + sage/categories/category_types + sage/categories/category_singleton sage/categories/category_cy_helper sage/categories/poor_man_map diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index 004458eba3f..6311e17f696 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -6,8 +6,10 @@ Coding Theory .. toctree:: :maxdepth: 1 - sage/coding/channels_catalog + sage/coding/encoder + sage/coding/encoders_catalog sage/coding/channel_constructions + sage/coding/channels_catalog sage/coding/codes_catalog sage/coding/linear_code sage/coding/code_constructions diff --git a/src/doc/en/reference/coercion/index.rst b/src/doc/en/reference/coercion/index.rst index dc94193c1a3..67defb146cb 100644 --- a/src/doc/en/reference/coercion/index.rst +++ b/src/doc/en/reference/coercion/index.rst @@ -672,5 +672,10 @@ Modules sage/structure/coerce_actions sage/structure/coerce_maps + sage/categories/pushout + sage/categories/action + + sage/structure/coerce_dict + sage/structure/coerce_exceptions .. include:: ../footer.txt diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 22e73970903..c2c594d9fd1 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -35,6 +35,7 @@ Comprehensive Module list sage/combinat/cluster_algebra_quiver/mutation_type sage/combinat/cluster_algebra_quiver/quiver sage/combinat/cluster_algebra_quiver/quiver_mutation_type + sage/combinat/colored_permutations sage/combinat/combinat sage/combinat/combinat_cython sage/combinat/combination @@ -97,6 +98,7 @@ Comprehensive Module list sage/combinat/designs/orthogonal_arrays_find_recursive sage/combinat/designs/steiner_quadruple_systems sage/combinat/designs/subhypergraph_search + sage/combinat/designs/twographs sage/combinat/diagram_algebras sage/combinat/dict_addition sage/combinat/dlx @@ -111,11 +113,14 @@ Comprehensive Module list sage/combinat/finite_state_machine_generators sage/combinat/free_module sage/combinat/free_prelie_algebra + sage/combinat/fully_packed_loop sage/combinat/gelfand_tsetlin_patterns sage/combinat/graph_path sage/combinat/gray_codes sage/combinat/hall_polynomial - sage/combinat/integer_list + sage/combinat/integer_lists/base + sage/combinat/integer_lists/lists + sage/combinat/integer_lists/invlex sage/combinat/integer_matrices sage/combinat/integer_vector sage/combinat/integer_vector_weighted @@ -160,11 +165,13 @@ Comprehensive Module list sage/combinat/permutation_nk sage/combinat/posets/__init__ sage/combinat/posets/all + sage/combinat/posets/cartesian_product sage/combinat/posets/elements sage/combinat/posets/hasse_diagram sage/combinat/posets/incidence_algebras sage/combinat/posets/lattices sage/combinat/posets/linear_extensions + sage/combinat/posets/moebius_algebra sage/combinat/posets/poset_examples sage/combinat/posets/posets sage/combinat/q_analogues @@ -206,6 +213,7 @@ Comprehensive Module list sage/combinat/root_system/cartan_type sage/combinat/root_system/coxeter_group sage/combinat/root_system/coxeter_matrix + sage/combinat/root_system/coxeter_type sage/combinat/root_system/dynkin_diagram sage/combinat/root_system/hecke_algebra_representation sage/combinat/root_system/integrable_representations @@ -337,3 +345,4 @@ Comprehensive Module list sage/combinat/words/word_options sage/combinat/words/words sage/combinat/yang_baxter_graph + sage/rings/cfinite_sequence diff --git a/src/doc/en/reference/conf.py b/src/doc/en/reference/conf.py index 99bb847ddde..26e14114b26 100644 --- a/src/doc/en/reference/conf.py +++ b/src/doc/en/reference/conf.py @@ -16,15 +16,8 @@ sys.path.append(SAGE_DOC) from common.conf import * -# settings for the intersphinx extension: - ref_src = os.path.join(SAGE_DOC, 'en', 'reference') ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None # General information about the project. project = u"Sage Reference Manual" diff --git a/src/doc/en/reference/conf_sub.py b/src/doc/en/reference/conf_sub.py index ae3b7eaf67f..54aefe527d0 100644 --- a/src/doc/en/reference/conf_sub.py +++ b/src/doc/en/reference/conf_sub.py @@ -12,18 +12,12 @@ # serve to show the default. import sys, os -sys.path.append(os.environ['SAGE_DOC']) +from sage.env import SAGE_DOC +sys.path.append(SAGE_DOC) from common.conf import * -# settings for the intersphinx extension: - ref_src = os.path.join(SAGE_DOC, 'en', 'reference') ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None # We use the main document's title, if we can find it. rst_file = open('index.rst', 'r') diff --git a/src/doc/en/reference/data_structures/conf.py b/src/doc/en/reference/data_structures/conf.py deleted file mode 100644 index ae3b7eaf67f..00000000000 --- a/src/doc/en/reference/data_structures/conf.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# settings for the intersphinx extension: - -ref_src = os.path.join(SAGE_DOC, 'en', 'reference') -ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None - -# We use the main document's title, if we can find it. -rst_file = open('index.rst', 'r') -rst_lines = rst_file.read().splitlines() -rst_file.close() - -title = u'' -for i in xrange(len(rst_lines)): - if rst_lines[i].startswith('==') and i > 0: - title = rst_lines[i-1].strip() - break - -# Otherwise, we use this directory's name. -name = os.path.basename(os.path.abspath('.')) -if not title: - title = name.capitalize() -title = title.replace(u'`', u'$') - -# General information about the project. -project = u'Sage Reference Manual: ' + title - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'Sage Reference Manual v' + release + ': ' + title - -# A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = title - -# HTML theme (e.g., 'default', 'sphinxdoc'). The pages for the -# reference manual use a custom theme, a slight variant on the 'sage' -# theme, to set the links in the top line. -html_theme = 'sageref' - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples (source -# start file, target name, title, author, document class -# [howto/manual]). -latex_documents = [ -('index', name + '.tex', project, u'The Sage Development Team', 'manual') -] - -#Ignore all .rst in the _sage subdirectory -exclude_trees = exclude_trees + ['_sage'] - -multidocs_is_master = False diff --git a/src/doc/en/reference/data_structures/conf.py b/src/doc/en/reference/data_structures/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/data_structures/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/data_structures/index.rst b/src/doc/en/reference/data_structures/index.rst index 52e3280b17d..90bf85efdf2 100644 --- a/src/doc/en/reference/data_structures/index.rst +++ b/src/doc/en/reference/data_structures/index.rst @@ -7,5 +7,6 @@ Data Structures sage/misc/binary_tree sage/data_structures/bitset sage/data_structures/bounded_integer_sequences + sage/data_structures/mutable_poset .. include:: ../footer.txt diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst index 6c24222bda7..5c198d9dcdf 100644 --- a/src/doc/en/reference/databases/index.rst +++ b/src/doc/en/reference/databases/index.rst @@ -55,6 +55,7 @@ database engine. sage/databases/jones sage/databases/oeis sage/databases/sloane + sage/databases/findstat sage/databases/conway sage/databases/odlyzko sage/databases/symbolic_data diff --git a/src/doc/en/reference/finite_rings/index.rst b/src/doc/en/reference/finite_rings/index.rst index 8f8519b7583..15a1066f957 100644 --- a/src/doc/en/reference/finite_rings/index.rst +++ b/src/doc/en/reference/finite_rings/index.rst @@ -1,25 +1,73 @@ Finite Rings ==================================== +Finite Rings +------------ + .. toctree:: :maxdepth: 2 - sage/rings/finite_rings/conway_polynomials - sage/rings/finite_rings/element_givaro - sage/rings/finite_rings/element_ntl_gf2e - sage/rings/finite_rings/element_pari_ffelt + sage/rings/finite_rings/integer_mod_ring + sage/rings/finite_rings/integer_mod + +Finite Fields +------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/finite_rings/constructor sage/rings/finite_rings/finite_field_base + sage/rings/finite_rings/element_base + sage/rings/finite_rings/homset + sage/rings/finite_rings/hom_finite_field + +Prime Fields +------------ + +.. toctree:: + :maxdepth: 2 + + sage/rings/finite_rings/finite_field_prime_modn + sage/rings/finite_rings/hom_prime_finite_field + +Finite Fields Using Pari +------------------------ + +.. toctree:: + :maxdepth: 2 + + sage/rings/finite_rings/finite_field_pari_ffelt + sage/rings/finite_rings/element_pari_ffelt sage/rings/finite_rings/finite_field_ext_pari + +Finite Fields Using Givaro +-------------------------- + +.. toctree:: + :maxdepth: 2 + sage/rings/finite_rings/finite_field_givaro - sage/rings/finite_rings/finite_field_ntl_gf2e - sage/rings/finite_rings/finite_field_pari_ffelt - sage/rings/finite_rings/finite_field_prime_modn - sage/rings/finite_rings/hom_finite_field + sage/rings/finite_rings/element_givaro sage/rings/finite_rings/hom_finite_field_givaro - sage/rings/finite_rings/hom_prime_finite_field - sage/rings/finite_rings/homset - sage/rings/finite_rings/integer_mod_ring + +Finite Fields of Characteristic 2 Using NTL +------------------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/finite_rings/finite_field_ntl_gf2e + sage/rings/finite_rings/element_ntl_gf2e + +Miscellaneous +------------- + +.. toctree:: + :maxdepth: 2 + sage/rings/finite_rings/residue_field sage/rings/algebraic_closure_finite_field + sage/rings/finite_rings/conway_polynomials .. include:: ../footer.txt diff --git a/src/doc/en/reference/game_theory/conf.py b/src/doc/en/reference/game_theory/conf.py deleted file mode 100644 index ae3b7eaf67f..00000000000 --- a/src/doc/en/reference/game_theory/conf.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# settings for the intersphinx extension: - -ref_src = os.path.join(SAGE_DOC, 'en', 'reference') -ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None - -# We use the main document's title, if we can find it. -rst_file = open('index.rst', 'r') -rst_lines = rst_file.read().splitlines() -rst_file.close() - -title = u'' -for i in xrange(len(rst_lines)): - if rst_lines[i].startswith('==') and i > 0: - title = rst_lines[i-1].strip() - break - -# Otherwise, we use this directory's name. -name = os.path.basename(os.path.abspath('.')) -if not title: - title = name.capitalize() -title = title.replace(u'`', u'$') - -# General information about the project. -project = u'Sage Reference Manual: ' + title - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'Sage Reference Manual v' + release + ': ' + title - -# A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = title - -# HTML theme (e.g., 'default', 'sphinxdoc'). The pages for the -# reference manual use a custom theme, a slight variant on the 'sage' -# theme, to set the links in the top line. -html_theme = 'sageref' - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples (source -# start file, target name, title, author, document class -# [howto/manual]). -latex_documents = [ -('index', name + '.tex', project, u'The Sage Development Team', 'manual') -] - -#Ignore all .rst in the _sage subdirectory -exclude_trees = exclude_trees + ['_sage'] - -multidocs_is_master = False diff --git a/src/doc/en/reference/game_theory/conf.py b/src/doc/en/reference/game_theory/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/game_theory/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index 047349237f8..a27c03f556a 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -26,6 +26,7 @@ Constructors and databases sage/graphs/digraph_generators sage/graphs/graph_generators_pyx sage/graphs/graph_database + sage/graphs/strongly_regular_db sage/graphs/isgci @@ -76,6 +77,7 @@ Libraries of algorithms sage/graphs/lovasz_theta sage/graphs/linearextensions sage/graphs/schnyder + sage/graphs/planarity sage/graphs/graph_plot sage/graphs/graph_plot_js sage/graphs/graph_decompositions/vertex_separation @@ -89,6 +91,7 @@ Libraries of algorithms sage/graphs/graph_latex sage/graphs/graph_editor sage/graphs/graph_list + sage/graphs/graph_input sage/graphs/hyperbolicity sage/graphs/tutte_polynomial sage/graphs/generic_graph_pyx diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 7834bd7d79d..0524030d31c 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -13,6 +13,7 @@ cell complexes. sage/homology/chain_complex sage/homology/chain_complex_morphism + sage/homology/chain_homotopy sage/homology/chain_complex_homspace sage/homology/simplicial_complex sage/homology/simplicial_complex_morphism @@ -23,6 +24,9 @@ cell complexes. sage/homology/cell_complex sage/homology/koszul_complex sage/homology/homology_group + sage/homology/homology_vector_space_with_basis + sage/homology/algebraic_topological_model + sage/homology/homology_morphism sage/homology/matrix_utils sage/interfaces/chomp diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 72e9b3bd8b7..85c52019900 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -31,15 +31,7 @@ Parents, Elements and Categories * :doc:`Basic Infrastructure ` * :doc:`Coercion ` * :doc:`Categories ` - -Standard Algebraic Structures ------------------------------ - -* :doc:`Monoids ` -* :doc:`Groups ` -* :doc:`Semirings ` -* :doc:`Rings ` -* :doc:`Algebras ` +* :doc:`Base Classes for Rings and Related Objects ` Standard Rings and Fields ------------------------- @@ -47,9 +39,9 @@ Standard Rings and Fields * :doc:`Integers, Rationals, etc. ` * :doc:`Real and Complex Numbers ` * :doc:`Finite Rings and Fields ` +* :doc:`Algebraic Numbers ` * :doc:`Polynomials ` * :doc:`Formal Power Series ` -* :doc:`Algebraic Number Fields ` * :doc:`Function Fields ` * :doc:`p-Adic Numbers ` * :doc:`Quaternion Algebras ` @@ -61,6 +53,14 @@ Linear Algebra * :doc:`Vectors and Modules ` * :doc:`Tensors on free modules of finite rank ` +Other Algebraic Structures +-------------------------- + +* :doc:`Monoids ` +* :doc:`Groups ` +* :doc:`Semirings ` +* :doc:`Algebras ` + Discrete Mathematics -------------------- @@ -78,6 +78,7 @@ Calculus * :doc:`Symbolic Calculus ` * :doc:`Mathematical Constants ` * :doc:`Elementary and Special Functions ` +* :doc:`Asymptotic Expansions ` (experimental) Geometry and Topology --------------------- @@ -120,7 +121,7 @@ Miscellaneous ------------- * :doc:`Cryptography ` -* :doc:`Optimization ` +* :doc:`Numerical Optimization ` * :doc:`Databases ` * :doc:`Games ` diff --git a/src/doc/en/reference/matrices/index.rst b/src/doc/en/reference/matrices/index.rst index b74f9c64953..bf33383ec01 100644 --- a/src/doc/en/reference/matrices/index.rst +++ b/src/doc/en/reference/matrices/index.rst @@ -85,6 +85,7 @@ objects like operation tables (e.g. the multiplication table of a group). sage/matrix/matrix_real_double_dense sage/matrix/matrix_complex_double_dense + sage/matrix/matrix_complex_ball_dense sage/matrix/matrix_mpolynomial_dense @@ -99,7 +100,7 @@ objects like operation tables (e.g. the multiplication table of a group). sage/matrix/matrix_integer_dense_saturation sage/matrix/matrix_integer_sparse sage/matrix/matrix_mod2_dense - sage/matrix/matrix_mod2e_dense + sage/matrix/matrix_gf2e_dense sage/matrix/matrix_modn_dense_double sage/matrix/matrix_modn_dense_float sage/matrix/matrix_rational_sparse diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 6efcf30199e..ab999774fd0 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -12,7 +12,6 @@ General Infrastructure sage/misc/randstate sage/misc/prandom sage/misc/unknown - sage/misc/exceptions Programming Utilities --------------------- @@ -49,6 +48,7 @@ Lists and Iteration, etc. :maxdepth: 1 sage/misc/callable_dict + sage/misc/converting_dict sage/misc/flatten sage/misc/search sage/misc/sage_itertools @@ -56,16 +56,6 @@ Lists and Iteration, etc. sage/misc/multireplace sage/misc/map_threaded -Integer Arithmetic -~~~~~~~~~~~~~~~~~~ - -.. toctree:: - :maxdepth: 1 - - sage/rings/sum_of_squares - sage/ext/multi_modular - sage/rings/arith - File and OS Access ~~~~~~~~~~~~~~~~~~ diff --git a/src/doc/en/reference/monoids/index.rst b/src/doc/en/reference/monoids/index.rst index de4db62002d..a91b3b9cc2c 100644 --- a/src/doc/en/reference/monoids/index.rst +++ b/src/doc/en/reference/monoids/index.rst @@ -7,17 +7,16 @@ finite number of indeterminates. .. toctree:: :maxdepth: 2 - sage/monoids/automatic_semigroup sage/monoids/monoid sage/monoids/free_monoid sage/monoids/free_monoid_element sage/monoids/free_abelian_monoid sage/monoids/free_abelian_monoid_element - sage/monoids/hecke_monoid sage/monoids/indexed_free_monoid - sage/monoids/string_monoid_element sage/monoids/string_monoid - + sage/monoids/string_monoid_element sage/monoids/string_ops + sage/monoids/hecke_monoid + sage/monoids/automatic_semigroup .. include:: ../footer.txt diff --git a/src/doc/en/reference/number_fields/index.rst b/src/doc/en/reference/number_fields/index.rst index 26eebb459e9..1c2807e6623 100644 --- a/src/doc/en/reference/number_fields/index.rst +++ b/src/doc/en/reference/number_fields/index.rst @@ -1,33 +1,72 @@ +Algebraic Numbers and Number Fields +=================================== + Algebraic Number Fields -======================= +----------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - sage/rings/number_field/number_field_base sage/rings/number_field/number_field + sage/rings/number_field/number_field_base sage/rings/number_field/number_field_rel sage/rings/number_field/number_field_element sage/rings/number_field/number_field_element_quadratic - sage/rings/number_field/order - sage/rings/number_field/number_field_ideal - sage/rings/number_field/number_field_ideal_rel + + sage/rings/number_field/splitting_field + sage/rings/number_field/galois_group + sage/rings/number_field/bdd_height + +Morphisms +--------- + +.. toctree:: + :maxdepth: 1 + sage/rings/number_field/morphism sage/rings/number_field/number_field_morphisms sage/rings/number_field/maps + sage/rings/number_field/structure + +Orders, Ideals, Ideal Classes +----------------------------- + +.. toctree:: + :maxdepth: 1 + + sage/rings/number_field/order + sage/rings/number_field/number_field_ideal + sage/rings/number_field/number_field_ideal_rel sage/rings/number_field/class_group - sage/rings/number_field/galois_group sage/rings/number_field/unit_group sage/rings/number_field/small_primes_of_degree_one - sage/rings/number_field/splitting_field - sage/rings/number_field/bdd_height - sage/rings/number_field/structure + +.. SEEALSO:: + + :ref:`sage.rings.finite_rings.residue_field` + +Totally Real Fields +------------------- + +.. toctree:: + :maxdepth: 1 + sage/rings/number_field/totallyreal sage/rings/number_field/totallyreal_rel sage/rings/number_field/totallyreal_data sage/rings/number_field/totallyreal_phc +Algebraic Numbers +----------------- + +.. toctree:: + :maxdepth: 1 + sage/rings/qqbar sage/rings/universal_cyclotomic_field +.. SEEALSO:: + + :ref:`sage.rings.algebraic_closure_finite_field` + .. include:: ../footer.txt diff --git a/src/doc/en/reference/plot3d/index.rst b/src/doc/en/reference/plot3d/index.rst index 3358a838cff..019fde4acb7 100644 --- a/src/doc/en/reference/plot3d/index.rst +++ b/src/doc/en/reference/plot3d/index.rst @@ -5,25 +5,53 @@ :maxdepth: 2 sage/plot/plot3d/examples + +Function and Data Plots +----------------------- + +.. toctree:: + :maxdepth: 2 + + sage/plot/plot3d/plot3d sage/plot/plot3d/parametric_plot3d - sage/plot/plot3d/parametric_surface sage/plot/plot3d/revolution_plot3d sage/plot/plot3d/plot_field3d sage/plot/plot3d/implicit_plot3d sage/plot/plot3d/list_plot3d - sage/plot/plot3d/plot3d - sage/plot/plot3d/platonic - sage/plot/plot3d/shapes2 + + +Basic Shapes and Primitives +--------------------------- + +.. toctree:: + :maxdepth: 2 + sage/plot/plot3d/base - sage/plot/plot3d/tachyon + sage/plot/plot3d/shapes + sage/plot/plot3d/shapes2 + sage/plot/plot3d/platonic + sage/plot/plot3d/parametric_surface + sage/plot/plot3d/implicit_surface + +Infrastructure +-------------- + +.. toctree:: + :maxdepth: 2 + sage/plot/plot3d/texture sage/plot/plot3d/index_face_set - - sage/plot/plot3d/implicit_surface - sage/plot/plot3d/shapes sage/plot/plot3d/transform sage/plot/plot3d/tri_plot +Backends +-------- + +.. toctree:: + :maxdepth: 2 + + sage/plot/plot3d/tachyon + .. broken, see #16647 .. sage/plot/plot3d/help .. sage/plot/java3d diff --git a/src/doc/en/reference/plotting/index.rst b/src/doc/en/reference/plotting/index.rst index eb2dd48109e..d58562a9a63 100644 --- a/src/doc/en/reference/plotting/index.rst +++ b/src/doc/en/reference/plotting/index.rst @@ -1,36 +1,67 @@ 2D Graphics =========== +General +------- + .. toctree:: :maxdepth: 2 sage/plot/plot - sage/plot/graphics + sage/plot/text + sage/plot/colors sage/plot/animate + +Function and Data Plots +----------------------- + +.. toctree:: + :maxdepth: 2 + + sage/plot/complex_plot + sage/plot/contour_plot + sage/plot/density_plot + sage/plot/plot_field + sage/plot/scatter_plot + sage/plot/step + sage/plot/histogram + sage/plot/bar_chart + +Plots of Other Mathematical Objects +----------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/graphs/graph_plot + sage/plot/matrix_plot + +Basic Shapes +------------ + +.. toctree:: + :maxdepth: 2 + sage/plot/arc sage/plot/arrow - sage/plot/bar_chart sage/plot/bezier_path sage/plot/circle - sage/plot/complex_plot - sage/plot/contour_plot - sage/plot/density_plot sage/plot/disk sage/plot/ellipse - sage/graphs/graph_plot - sage/plot/histogram sage/plot/line - sage/plot/step - sage/plot/matrix_plot - sage/plot/plot_field sage/plot/point sage/plot/polygon - sage/plot/primitive - sage/plot/scatter_plot - sage/plot/text - sage/plot/colors sage/plot/hyperbolic_arc sage/plot/hyperbolic_polygon + +Infrastructure and Low-Level Functions +-------------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/plot/graphics + sage/plot/primitive sage/plot/misc .. include:: ../footer.txt diff --git a/src/doc/en/reference/polynomial_rings/index.rst b/src/doc/en/reference/polynomial_rings/index.rst index 35d416aafce..f8eeb2baa46 100644 --- a/src/doc/en/reference/polynomial_rings/index.rst +++ b/src/doc/en/reference/polynomial_rings/index.rst @@ -1,25 +1,78 @@ +Polynomials +=========== + Polynomial Rings -================ +---------------- .. toctree:: :maxdepth: 2 sage/rings/polynomial/polynomial_ring_constructor +Univariate Polynomials +---------------------- + +.. toctree:: + :maxdepth: 2 + polynomial_rings_univar + sage/rings/polynomial/convolution + sage/rings/polynomial/cyclotomic + +Multivariate Polynomials +------------------------ + +.. toctree:: + :maxdepth: 2 polynomial_rings_multivar + sage/rings/invariant_theory + polynomial_rings_toy_implementations + +Rational Functions +------------------ + +.. toctree:: + :maxdepth: 1 - polynomial_rings_infinite + sage/rings/fraction_field + sage/rings/fraction_field_element + sage/rings/fraction_field_FpT - polynomial_rings_laurent +Laurent Polynomials +------------------- + +.. toctree:: + :maxdepth: 1 + + sage/rings/polynomial/laurent_polynomial_ring + sage/rings/polynomial/laurent_polynomial + +Infinite Polynomial Rings +------------------------- + +.. toctree:: + :maxdepth: 1 + + sage/rings/polynomial/infinite_polynomial_ring + sage/rings/polynomial/infinite_polynomial_element + + sage/rings/polynomial/symmetric_ideal + sage/rings/polynomial/symmetric_reduction + +Boolean Polynomials +------------------- + +.. toctree:: + :maxdepth: 1 sage/rings/polynomial/pbori - polynomial_rings_toy_implementations +Noncommutative Polynomials +-------------------------- - sage/rings/polynomial/convolution - sage/rings/polynomial/cyclotomic +.. toctree:: + :maxdepth: 1 sage/rings/polynomial/plural diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_infinite.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_infinite.rst deleted file mode 100644 index c6f32122eb5..00000000000 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_infinite.rst +++ /dev/null @@ -1,13 +0,0 @@ - -Infinite Polynomial Rings -========================= - -.. toctree:: - :maxdepth: 2 - - sage/rings/polynomial/infinite_polynomial_ring - sage/rings/polynomial/infinite_polynomial_element - - sage/rings/polynomial/symmetric_ideal - sage/rings/polynomial/symmetric_reduction - diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_laurent.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_laurent.rst deleted file mode 100644 index d0284c5643d..00000000000 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_laurent.rst +++ /dev/null @@ -1,10 +0,0 @@ - -Laurent Polynomials and Polynomial Rings -======================================== - -.. toctree:: - :maxdepth: 2 - - sage/rings/polynomial/laurent_polynomial_ring - sage/rings/polynomial/laurent_polynomial - diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst index cf2583c0aec..e6dd4480ccb 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst @@ -37,3 +37,5 @@ are implemented using the PolyBoRi library (cf. :mod:`sage.rings.polynomial.pbor sage/rings/polynomial/multi_polynomial_ideal_libsingular sage/rings/polynomial/polydict + + sage/rings/monomials diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index d84fd747804..bd55ed8b014 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -35,6 +35,7 @@ whereas others have multiple bases. sage/rings/polynomial/real_roots sage/rings/polynomial/complex_roots + sage/rings/polynomial/refine_root sage/rings/polynomial/ideal sage/rings/polynomial/polynomial_quotient_ring diff --git a/src/doc/en/reference/quadratic_forms/index.rst b/src/doc/en/reference/quadratic_forms/index.rst index 7efd3a3fb72..e071245bd1c 100644 --- a/src/doc/en/reference/quadratic_forms/index.rst +++ b/src/doc/en/reference/quadratic_forms/index.rst @@ -11,4 +11,32 @@ Quadratic Forms sage/quadratic_forms/special_values sage/quadratic_forms/count_local_2 + sage/quadratic_forms/extras + sage/quadratic_forms/genera/genus + sage/quadratic_forms/qfsolve + sage/quadratic_forms/ternary + sage/quadratic_forms/ternary_qf + sage/quadratic_forms/quadratic_form__evaluate + +.. already documented in quadratic_forms.quadratic_forms +.. sage/quadratic_forms/quadratic_form__automorphisms +.. sage/quadratic_forms/quadratic_form__count_local_2 +.. sage/quadratic_forms/quadratic_form__equivalence_testing +.. sage/quadratic_forms/quadratic_form__genus +.. sage/quadratic_forms/quadratic_form__local_density_congruence +.. sage/quadratic_forms/quadratic_form__local_density_interfaces +.. sage/quadratic_forms/quadratic_form__local_field_invariants +.. sage/quadratic_forms/quadratic_form__local_normal_form +.. sage/quadratic_forms/quadratic_form__local_representation_conditions +.. sage/quadratic_forms/quadratic_form__mass +.. sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses +.. sage/quadratic_forms/quadratic_form__mass__Siegel_densities +.. sage/quadratic_forms/quadratic_form__neighbors +.. sage/quadratic_forms/quadratic_form__reduction_theory +.. sage/quadratic_forms/quadratic_form__siegel_product +.. sage/quadratic_forms/quadratic_form__split_local_covering +.. sage/quadratic_forms/quadratic_form__ternary_Tornaria +.. sage/quadratic_forms/quadratic_form__theta +.. sage/quadratic_forms/quadratic_form__variable_substitutions + .. include:: ../footer.txt diff --git a/src/doc/en/reference/quivers/conf.py b/src/doc/en/reference/quivers/conf.py deleted file mode 100644 index ae3b7eaf67f..00000000000 --- a/src/doc/en/reference/quivers/conf.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# settings for the intersphinx extension: - -ref_src = os.path.join(SAGE_DOC, 'en', 'reference') -ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None - -# We use the main document's title, if we can find it. -rst_file = open('index.rst', 'r') -rst_lines = rst_file.read().splitlines() -rst_file.close() - -title = u'' -for i in xrange(len(rst_lines)): - if rst_lines[i].startswith('==') and i > 0: - title = rst_lines[i-1].strip() - break - -# Otherwise, we use this directory's name. -name = os.path.basename(os.path.abspath('.')) -if not title: - title = name.capitalize() -title = title.replace(u'`', u'$') - -# General information about the project. -project = u'Sage Reference Manual: ' + title - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'Sage Reference Manual v' + release + ': ' + title - -# A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = title - -# HTML theme (e.g., 'default', 'sphinxdoc'). The pages for the -# reference manual use a custom theme, a slight variant on the 'sage' -# theme, to set the links in the top line. -html_theme = 'sageref' - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples (source -# start file, target name, title, author, document class -# [howto/manual]). -latex_documents = [ -('index', name + '.tex', project, u'The Sage Development Team', 'manual') -] - -#Ignore all .rst in the _sage subdirectory -exclude_trees = exclude_trees + ['_sage'] - -multidocs_is_master = False diff --git a/src/doc/en/reference/quivers/conf.py b/src/doc/en/reference/quivers/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/quivers/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/quivers/index.rst b/src/doc/en/reference/quivers/index.rst index b29c84f807e..b1e3aa48f87 100644 --- a/src/doc/en/reference/quivers/index.rst +++ b/src/doc/en/reference/quivers/index.rst @@ -5,6 +5,7 @@ Quivers :maxdepth: 2 sage/quivers/algebra + sage/quivers/algebra_elements sage/quivers/homspace sage/quivers/morphism sage/quivers/path_semigroup diff --git a/src/doc/en/reference/repl/index.rst b/src/doc/en/reference/repl/index.rst index b372d46c650..0b7b2d11074 100644 --- a/src/doc/en/reference/repl/index.rst +++ b/src/doc/en/reference/repl/index.rst @@ -8,6 +8,8 @@ also be familiar with the documentation for IPython. For more details about using the Sage command line, see the Sage tutorial. +Running Sage +------------ .. toctree:: :maxdepth: 2 @@ -17,13 +19,6 @@ tutorial. environ sage/misc/trace - sage/repl/readline_extra_commands - sage/repl/interpreter - sage/repl/ipython_extension - sage/repl/ipython_kernel/install - sage/repl/ipython_kernel/kernel - - Preparsing ---------- @@ -77,6 +72,7 @@ Display Backend Infrastructure sage/repl/rich_output/output_basic sage/repl/rich_output/output_graphics sage/repl/rich_output/output_graphics3d + sage/repl/rich_output/output_video sage/repl/rich_output/output_catalog sage/repl/rich_output/backend_base @@ -93,9 +89,16 @@ Miscellaneous sage/ext/interactive_constructors_c + sage/repl/readline_extra_commands + + sage/repl/interpreter + sage/repl/ipython_extension + sage/repl/ipython_kernel/install + sage/repl/ipython_kernel/kernel + sage/repl/ipython_tests + sage/repl/display/jsmol_iframe sage/repl/image sage/repl/inputhook - sage/repl/ipython_tests .. include:: ../footer.txt diff --git a/src/doc/en/reference/riemannian_geometry/conf.py b/src/doc/en/reference/riemannian_geometry/conf.py deleted file mode 100644 index ae3b7eaf67f..00000000000 --- a/src/doc/en/reference/riemannian_geometry/conf.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# settings for the intersphinx extension: - -ref_src = os.path.join(SAGE_DOC, 'en', 'reference') -ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') -intersphinx_mapping[ref_out] = None - -for doc in os.listdir(ref_src): - if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): - intersphinx_mapping[os.path.join(ref_out, doc)] = None - -# We use the main document's title, if we can find it. -rst_file = open('index.rst', 'r') -rst_lines = rst_file.read().splitlines() -rst_file.close() - -title = u'' -for i in xrange(len(rst_lines)): - if rst_lines[i].startswith('==') and i > 0: - title = rst_lines[i-1].strip() - break - -# Otherwise, we use this directory's name. -name = os.path.basename(os.path.abspath('.')) -if not title: - title = name.capitalize() -title = title.replace(u'`', u'$') - -# General information about the project. -project = u'Sage Reference Manual: ' + title - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = u'Sage Reference Manual v' + release + ': ' + title - -# A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = title - -# HTML theme (e.g., 'default', 'sphinxdoc'). The pages for the -# reference manual use a custom theme, a slight variant on the 'sage' -# theme, to set the links in the top line. -html_theme = 'sageref' - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples (source -# start file, target name, title, author, document class -# [howto/manual]). -latex_documents = [ -('index', name + '.tex', project, u'The Sage Development Team', 'manual') -] - -#Ignore all .rst in the _sage subdirectory -exclude_trees = exclude_trees + ['_sage'] - -multidocs_is_master = False diff --git a/src/doc/en/reference/riemannian_geometry/conf.py b/src/doc/en/reference/riemannian_geometry/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/riemannian_geometry/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/rings/asymptotic_expansions_index.rst b/src/doc/en/reference/rings/asymptotic_expansions_index.rst new file mode 100644 index 00000000000..3a15f21a3d1 --- /dev/null +++ b/src/doc/en/reference/rings/asymptotic_expansions_index.rst @@ -0,0 +1,59 @@ +Asymptotic Expansions +===================== + + +The Asymptotic Ring +------------------- + +The asymptotic ring, as well as its main documentation is contained in +the module + +- :doc:`sage/rings/asymptotic/asymptotic_ring`. + + +Supplements +----------- + +Behind the scenes of working with asymptotic expressions a couple of +additional classes and tools turn up. For instance the growth of each +summand is managed in growth groups, see below. + + +Growth Groups +^^^^^^^^^^^^^ + +The growth of a summand of an asymptotic expression is managed in + +- :doc:`sage/rings/asymptotic/growth_group` and + +- :doc:`sage/rings/asymptotic/growth_group_cartesian`. + + +Term Monoids +^^^^^^^^^^^^ + +A summand of an asymptotic expression is basically a term out of the following monoid: + +- :doc:`sage/rings/asymptotic/term_monoid`. + + +Miscellaneous +^^^^^^^^^^^^^ + +Various useful functions and tools are collected in + +- :doc:`sage/rings/asymptotic/misc`. + + +Asymptotic Expansions --- Table of Contents +------------------------------------------- + +.. toctree:: + + sage/rings/asymptotic/asymptotic_ring + sage/rings/asymptotic/growth_group + sage/rings/asymptotic/growth_group_cartesian + sage/rings/asymptotic/term_monoid + sage/rings/asymptotic/misc + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/rings/index.rst b/src/doc/en/reference/rings/index.rst index 39deb9cfba4..f9fa15ac117 100644 --- a/src/doc/en/reference/rings/index.rst +++ b/src/doc/en/reference/rings/index.rst @@ -1,49 +1,70 @@ General Rings, Ideals, and Morphisms ==================================== +Base Classes for Rings, Algebras and Fields +------------------------------------------- + .. toctree:: :maxdepth: 2 sage/rings/ring + sage/rings/commutative_ring + sage/rings/commutative_algebra + sage/rings/dedekind_domain + sage/rings/euclidean_domain + sage/rings/integral_domain + sage/rings/principal_ideal_domain + +Ideals +------ + +.. toctree:: + :maxdepth: 2 + sage/rings/ideal sage/rings/ideal_monoid sage/rings/noncommutative_ideals + +Ring Morphisms +-------------- + +.. toctree:: + :maxdepth: 2 + sage/rings/morphism sage/rings/homset - sage/rings/infinity - sage/rings/fraction_field - sage/rings/fraction_field_element - sage/rings/fraction_field_FpT + +Quotient Rings +-------------- + +.. toctree:: + :maxdepth: 2 + sage/rings/quotient_ring sage/rings/quotient_ring_element - sage/rings/invariant_theory - sage/rings/cfinite_sequence - sage/rings/bernmm - sage/rings/bernoulli_mod_p +Fraction Fields +--------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/fraction_field + sage/rings/fraction_field_element + +Utilities +--------- + +.. toctree:: + :maxdepth: 2 + sage/rings/big_oh - sage/rings/contfrac - sage/rings/factorint - sage/rings/fast_arith + sage/rings/infinity sage/rings/misc - sage/rings/monomials +.. toctree:: + :hidden: - sage/rings/commutative_algebra - sage/rings/commutative_algebra_element - sage/rings/commutative_ring - sage/rings/commutative_ring_element - sage/rings/dedekind_domain - sage/rings/dedekind_domain_element - sage/rings/euclidean_domain - sage/rings/euclidean_domain_element - sage/rings/field - sage/rings/field_element - sage/rings/integral_domain - sage/rings/integral_domain_element - sage/rings/principal_ideal_domain - sage/rings/principal_ideal_domain_element - sage/rings/ring_element - + asymptotic_expansions_index .. include:: ../footer.txt diff --git a/src/doc/en/reference/rings_numerical/index.rst b/src/doc/en/reference/rings_numerical/index.rst index 3addbc1b441..9d396fe1559 100644 --- a/src/doc/en/reference/rings_numerical/index.rst +++ b/src/doc/en/reference/rings_numerical/index.rst @@ -1,43 +1,56 @@ Fixed and Arbitrary Precision Numerical Fields ============================================== -Sage supports two optimized fixed precision fields for numerical -computation, the real double (RealDoubleField) and complex double +Floating-Point Arithmetic +------------------------- + +Sage supports arbitrary precision real (RealField) and complex fields +(ComplexField). Sage also provides two optimized fixed precision fields for +numerical computation, the real double (RealDoubleField) and complex double fields (ComplexDoubleField). -Sage also supports arbitrary precision real (RealField) and -complex fields (ComplexField), and real and complex -interval arithmetic (RealIntervalField and ComplexIntervalField). +Real and complex double elements are optimized implementations that use the +GNU Scientific Library for arithmetic and some special functions. Arbitrary +precision real and complex numbers are implemented using the MPFR library, +which builds on GMP. In many cases the PARI C-library is used to compute +special functions when implementations aren't otherwise available. -Real and complex double elements are optimized implementations that -use the GNU Scientific Library for arithmetic and some special -functions. Arbitrary precision real and complex numbers are -implemented using the MPFR library, which builds on GMP. (Note that -Sage doesn't currently use the MPC library.) The interval arithmetic -field is implemented using the MPFI library. +.. toctree:: + :maxdepth: 2 -In many cases the PARI C-library is used to compute special functions -when implementations aren't otherwise available. + sage/rings/real_mpfr + sage/rings/complex_field + sage/rings/complex_number + sage/rings/complex_mpc + sage/rings/real_double + sage/rings/complex_double + +Interval Arithmetic +------------------- + +Sage implements real and complex interval arithmetic using MPFI +(RealIntervalField, ComplexIntervalField) and arb (RealBallField, +ComplexBallField). .. toctree:: :maxdepth: 2 - sage/rings/real_double - sage/rings/real_mpfr sage/rings/real_mpfi sage/rings/real_interval_field sage/rings/real_interval_absolute - sage/rings/real_lazy - - sage/rings/complex_double - sage/rings/complex_field - sage/rings/complex_number - sage/rings/complex_mpc sage/rings/complex_interval_field sage/rings/complex_interval -.. Modules depending on optional packages: -.. sage/rings/real_arb + sage/rings/real_arb + sage/rings/complex_arb + +Exact Real Arithmetic +--------------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/real_lazy .. include:: ../footer.txt diff --git a/src/doc/en/reference/rings_standard/index.rst b/src/doc/en/reference/rings_standard/index.rst index 97aeaefabae..3ff6764c9b6 100644 --- a/src/doc/en/reference/rings_standard/index.rst +++ b/src/doc/en/reference/rings_standard/index.rst @@ -1,19 +1,37 @@ Standard Commutative Rings ========================== +Integers +-------- + .. toctree:: :maxdepth: 2 sage/rings/integer_ring sage/rings/integer + sage/rings/bernmm + sage/rings/bernoulli_mod_p + sage/rings/factorint + sage/rings/fast_arith + sage/rings/sum_of_squares + sage/ext/multi_modular + sage/rings/arith + +.. SEEALSO:: - sage/rings/finite_rings/integer_mod_ring - sage/rings/finite_rings/integer_mod + - :ref:`sage.sets.integer_range` + - :ref:`sage.sets.positive_integers` + - :ref:`sage.sets.non_negative_integers` + - :ref:`sage.sets.primes` + +Rationals +--------- + +.. toctree:: + :maxdepth: 2 sage/rings/rational_field sage/rings/rational - - sage/rings/finite_rings/constructor - sage/rings/finite_rings/element_base + sage/rings/contfrac .. include:: ../footer.txt diff --git a/src/doc/en/reference/sat/index.rst b/src/doc/en/reference/sat/index.rst index 37adb48dd03..7f6314ac5d8 100644 --- a/src/doc/en/reference/sat/index.rst +++ b/src/doc/en/reference/sat/index.rst @@ -18,34 +18,36 @@ should be true, we write:: Solvers ------- -Any SAT solver supporting the DIMACS input format is easily interfaced using the -:class:`sage.sat.solvers.dimacs.DIMACS` blueprint. Sage ships with pre-written interfaces for *RSat* -[RS]_ and *Glucose* [GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ SAT -solver which can be used interchangably with DIMACS-based solvers, but also provides advanced -features. For this, the optional CryptoMiniSat package must be installed, this can be accomplished -by typing:: +By default, Sage solves SAT instances as an Integer Linear Program (see +:mod:`sage.numerical.mip`), but any SAT solver supporting the DIMACS input +format is easily interfaced using the :class:`sage.sat.solvers.dimacs.DIMACS` +blueprint. Sage ships with pre-written interfaces for *RSat* [RS]_ and *Glucose* +[GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ +SAT solver which can be used interchangably with DIMACS-based solvers, but also +provides advanced features. For this last solver, the optional CryptoMiniSat +package must be installed, this can be accomplished by typing the following in the +shell:: - sage: install_package('cryptominisat') # not tested + sage -i cryptominisat sagelib -and by running ``sage -b`` from the shell afterwards to build Sage's CryptoMiniSat extension module. - -Since by default Sage does not include any SAT solver, we demonstrate key features by instantiating -a fake DIMACS-based solver. We start with a trivial example:: +We now show how to solve a simple SAT problem. :: (x1 OR x2 OR x3) AND (x1 OR x2 OR (NOT x3)) In Sage's notation:: - sage: from sage.sat.solvers.dimacs import DIMACS - sage: solver = DIMACS(command="sat-solver") + sage: solver = SAT() sage: solver.add_clause( ( 1, 2, 3) ) sage: solver.add_clause( ( 1, 2, -3) ) + sage: solver() # random + (None, True, True, False) .. NOTE:: - :meth:`sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables when necessary. In - particular, it creates *all* variables up to the given index. Hence, adding a literal involving - the variable 1000 creates up to 1000 internal variables. + :meth:`~sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables + when necessary. When using CryptoMiniSat, it creates *all* variables up to + the given index. Hence, adding a literal involving the variable 1000 creates + up to 1000 internal variables. DIMACS-base solvers can also be used to write DIMACS files:: @@ -77,14 +79,6 @@ Alternatively, there is :meth:`sage.sat.solvers.dimacs.DIMACS.clauses`:: These files can then be passed external SAT solvers. -We demonstrate solving using CryptoMiniSat:: - - sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat - sage: cms = CryptoMiniSat() # optional - cryptominisat - sage: cms.add_clause((1,2,-3)) # optional - cryptominisat - sage: cms() # optional - cryptominisat - (None, True, True, False) - Details on Specific Solvers ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -93,6 +87,7 @@ Details on Specific Solvers sage/sat/solvers/satsolver sage/sat/solvers/dimacs + sage/sat/solvers/sat_lp .. optional - cryptominisat .. sage/sat/solvers/cryptominisat/cryptominisat .. sage/sat/solvers/cryptominisat/solverconf diff --git a/src/doc/en/reference/structure/index.rst b/src/doc/en/reference/structure/index.rst index d00892d0783..983fec165f6 100644 --- a/src/doc/en/reference/structure/index.rst +++ b/src/doc/en/reference/structure/index.rst @@ -80,6 +80,8 @@ Sets sage/sets/non_negative_integers sage/sets/primes sage/sets/real_set + sage/structure/set_factories + sage/structure/set_factories_example Use of Heuristic and Probabilistic Algorithms --------------------------------------------- @@ -101,9 +103,6 @@ Utilities sage/structure/dynamic_class sage/structure/mutability - sage/structure/coerce_dict - sage/structure/coerce_exceptions - Internals --------- diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 462215dc4b6..b6f8e8bb015 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -119,7 +119,6 @@ This base class provides a lot more methods than a general parent:: '_coerce_', '_coerce_c', '_coerce_impl', - '_coerce_self', '_coerce_try', '_default_category', '_gcd_univariate_polynomial', @@ -140,7 +139,6 @@ This base class provides a lot more methods than a general parent:: 'cardinality', 'class_group', 'coerce_map_from_c', - 'coerce_map_from_impl', 'content', 'divides', 'epsilon', @@ -153,7 +151,6 @@ This base class provides a lot more methods than a general parent:: 'get_action_c', 'get_action_impl', 'has_coerce_map_from_c', - 'has_coerce_map_from_impl', 'ideal', 'ideal_monoid', 'integral_closure', @@ -469,19 +466,19 @@ And indeed, ``MS2`` has *more* methods than ``MS1``:: sage: import inspect sage: len([s for s in dir(MS1) if inspect.ismethod(getattr(MS1,s,None))]) - 58 + 59 sage: len([s for s in dir(MS2) if inspect.ismethod(getattr(MS2,s,None))]) - 86 + 87 This is because the class of ``MS2`` also inherits from the parent class for algebras:: sage: MS1.__class__.__bases__ (, - ) + ) sage: MS2.__class__.__bases__ (, - ) + ) .. end of output @@ -881,7 +878,7 @@ The four axioms requested for coercions rational field is a homomorphism of euclidean domains:: sage: QQ.coerce_map_from(ZZ).category_for() - Category of euclidean domains + Join of Category of euclidean domains and Category of metric spaces .. end of output diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst index ddd98051361..034e52699de 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst @@ -6,9 +6,10 @@ Cremona's Databases Cremona's databases of elliptic curves are part of Sage. The curves up to conductor 10,000 come standard with Sage, and an there is an optional -download to gain access to his complete tables. From within sage run:: +download to gain access to his complete tables. From a shell, you +should run :: - sage: install_package('database_cremona_ellcurve') # not tested + sage -i database_cremona_ellcurve to automatically download and install the extended table. diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst index e4de374dda9..4756963f818 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst @@ -89,8 +89,7 @@ groups in the GAP transitive groups database. :: sage: K. = NumberField(x^3 - 2) - sage: K.galois_group(type="gap", algorithm='magma') # optional - magma - verbose... + sage: K.galois_group(type="gap", algorithm='magma') # optional - magma database_gap Galois group Transitive group number 2 of degree 3 of the Number Field in a with defining polynomial x^3 - 2 diff --git a/src/doc/en/thematic_tutorials/group_theory.rst b/src/doc/en/thematic_tutorials/group_theory.rst index 5fd024692ff..ef98e01de6b 100644 --- a/src/doc/en/thematic_tutorials/group_theory.rst +++ b/src/doc/en/thematic_tutorials/group_theory.rst @@ -1009,9 +1009,9 @@ groups of order less than 16. :: 8 D1 = CyclicPermutationGroup(2) D2 = CyclicPermutationGroup(2) D3 = CyclicPermutationGroup(2) - G = direct_product_permgroups([D1,D2,D3])} Abelian, non-cyclic + G = direct_product_permgroups([D1,D2,D3]) Abelian, non-cyclic 8 DihedralGroup(4) Non-abelian - 8 QuaternionGroup()} Quaternions, also DiCyclicGroup(2) + 8 QuaternionGroup() Quaternions, also DiCyclicGroup(2) 9 CyclicPermutationGroup(9) Cyclic 9 D1 = CyclicPermutationGroup(3) D2 = CyclicPermutationGroup(3) diff --git a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst index 3caeb368547..af5ed9821dd 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst @@ -164,13 +164,13 @@ coefficients) through the usual free module accessors:: [((0, 0, 0), 1), ((1, 0, 0), 1), ((1, 1, 0), 1), ((1, 1, 1), 1)] sage: pprint(dict(chi)) {(0, 0, 0): 1, (1, 0, 0): 1, (1, 1, 0): 1, (1, 1, 1): 1} - sage: chi.monomials() + sage: M = sorted(chi.monomials(), key=lambda x: x.support()); M [B3(0,0,0), B3(1,0,0), B3(1,1,0), B3(1,1,1)] - sage: chi.support() + sage: sorted(chi.support()) [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1)] sage: chi.coefficients() [1, 1, 1, 1] - sage: [r.degree() for r in chi.monomials()] + sage: [r.degree() for r in M] [1, 7, 21, 35] sage: sum(r.degree() for r in chi.monomials()) 64 @@ -484,10 +484,10 @@ itself, that is, the integral of `|tr(g)|^{10}`:: sage: tr^5 5*A2(2,2,1) + 6*A2(3,1,1) + 5*A2(3,2,0) + 4*A2(4,1,0) + A2(5,0,0) - sage: (tr^5).monomials() + sage: sorted((tr^5).monomials(), key=lambda x: x.support()) [A2(2,2,1), A2(3,1,1), A2(3,2,0), A2(4,1,0), A2(5,0,0)] - sage: (tr^5).coefficients() - [5, 6, 5, 4, 1] + sage: sorted((tr^5).coefficients()) + [1, 4, 5, 5, 6] sage: sum(x^2 for x in (tr^5).coefficients()) 103 diff --git a/src/doc/en/thematic_tutorials/linear_programming.rst b/src/doc/en/thematic_tutorials/linear_programming.rst index d9a0bd08517..63b96676910 100644 --- a/src/doc/en/thematic_tutorials/linear_programming.rst +++ b/src/doc/en/thematic_tutorials/linear_programming.rst @@ -446,7 +446,8 @@ following libraries are currently supported: `COIN-OR `_ Provided under the open source license CPL, but incompatible with - GPL. CBC can be installed through the command ``install_package("cbc")``. + GPL. CBC can be installed using the shell command + ``sage -i cbc sagelib``. * `CPLEX `_: @@ -535,6 +536,6 @@ create symbolic links to these files in the appropriate directories: ** be precisely as indicated. If the names differ, Sage will not notice that** **the files are present** -Once this is done, Sage is to be asked to notice the changes by calling:: +Once this is done, Sage is to be asked to notice the changes by running:: - sage -b + make diff --git a/src/doc/en/thematic_tutorials/sws2rst.rst b/src/doc/en/thematic_tutorials/sws2rst.rst index 451ebdf5c15..af76bd3a3c6 100644 --- a/src/doc/en/thematic_tutorials/sws2rst.rst +++ b/src/doc/en/thematic_tutorials/sws2rst.rst @@ -21,10 +21,10 @@ using the Mac app and have placed it in your Applications directory. * Next, you will need an optional package to parse your worksheet. Use the command:: - sage -i beautifulsoup + sage --pip install beautifulsoup4 to install it (or, in the Mac app, use the ``Terminal Session`` advanced - menu with ``-i beautifulsoup``). + menu with ``--pip install beautifulsoup4``). * Then we will use the ``sws2rst`` script to turn the worksheet into a document in the `ReStructuredText `_ diff --git a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst index 388d7d1d473..118e03c69c2 100644 --- a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst +++ b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst @@ -246,7 +246,7 @@ As an element of a vector space, ``el`` has a particular behavior:: sage: 2*el 2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]] - sage: el.support() + sage: sorted(el.support()) [[1, 2, 3], [1, 3, 2], [3, 2, 1]] sage: el.coefficient([1, 2, 3]) 1 diff --git a/src/doc/en/tutorial/interfaces.rst b/src/doc/en/tutorial/interfaces.rst index 3a683f6d602..eeb98ed1c60 100644 --- a/src/doc/en/tutorial/interfaces.rst +++ b/src/doc/en/tutorial/interfaces.rst @@ -144,7 +144,7 @@ things about it. GAP === -Sage comes with GAP 4.4.10 for computational discrete mathematics, +Sage comes with GAP for computational discrete mathematics, especially group theory. Here's an example of GAP's ``IdGroup`` function, which uses the @@ -176,16 +176,11 @@ GAP interface as follows: sage: n = G.order(); n 120 -(For some GAP functionality, you should install two optional -Sage packages. -Type ``sage -optional`` for a list and choose -the one that looks like ``gap\_packages-x.y.z``, then type -``sage -i gap\_packages-x.y.z``. Do the same -for ``database\_gap-x.y.z``. -Some non-GPL'd GAP packages may be installed by downloading them -from the GAP web site [GAPkg]_, -and unpacking them in ``$SAGE_ROOT/local/lib/gap-4.4.10/pkg``. -) +For some GAP functionality, you should install two optional +Sage packages. This can be done with the command:: + + sage -i gap_packages database_gap + Singular ======== diff --git a/src/doc/en/tutorial/programming.rst b/src/doc/en/tutorial/programming.rst index b3ec5818885..5c4f3c5620e 100644 --- a/src/doc/en/tutorial/programming.rst +++ b/src/doc/en/tutorial/programming.rst @@ -165,7 +165,7 @@ etc: :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys from sage.all import * diff --git a/src/doc/en/tutorial/tour_coercion.rst b/src/doc/en/tutorial/tour_coercion.rst index 2bbb25d352b..7f94a0c84fd 100644 --- a/src/doc/en/tutorial/tour_coercion.rst +++ b/src/doc/en/tutorial/tour_coercion.rst @@ -118,6 +118,7 @@ implemented in Sage as well: sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/fr/tutorial/interfaces.rst b/src/doc/fr/tutorial/interfaces.rst index ac9b4942387..a1fc5cf4e31 100644 --- a/src/doc/fr/tutorial/interfaces.rst +++ b/src/doc/fr/tutorial/interfaces.rst @@ -146,7 +146,7 @@ GAP === Pour les mathématiques discrètes effectives et principalement la théorie -des groupes, Sage utilise GAP 4.4.10. +des groupes, Sage utilise GAP. Voici un exemple d'utilisation de la fonction GAP ``IdGroup``, qui nécessite une base de données optionnelle de groupes de petit ordre, à @@ -177,15 +177,12 @@ l'interface GAP comme suit : sage: n = G.order(); n 120 -(Certaines fonctionnalités de GAP nécessitent l'installation de deux -paquets facultatifs. Saisissez ``sage -optional`` pour consulter la -liste des paquets facultatifs, et choisissez celui dont le nom ressemble -à ``gap\_packages-x.y.z``, puis installez-le par -``sage -i gap\_packages-x.y.z``. Faites de même avec -``database\_gap-x.y.z``. D'autres paquets GAP, non couverts par la -licence GPL, peuvent être téléchargés depuis le site web de GAP -[GAPkg]_ et installés en les désarchivant dans -``$SAGE_ROOT/local/lib/gap-4.4.10/pkg``.) +Pour utiliser certaines fonctionnalités de GAP, +vous devez installer deux paquets Sage optionnels. +Cela peut être fait avec la commande:: + + sage -i gap_packages database_gap + Singular ======== diff --git a/src/doc/fr/tutorial/programming.rst b/src/doc/fr/tutorial/programming.rst index f4995b4ee29..82cbde2bdbe 100644 --- a/src/doc/fr/tutorial/programming.rst +++ b/src/doc/fr/tutorial/programming.rst @@ -173,7 +173,7 @@ entiers, des polynômes, etc. : :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys from sage.all import * diff --git a/src/doc/fr/tutorial/tour_coercion.rst b/src/doc/fr/tutorial/tour_coercion.rst index a032be7968c..395493ba3a6 100644 --- a/src/doc/fr/tutorial/tour_coercion.rst +++ b/src/doc/fr/tutorial/tour_coercion.rst @@ -119,6 +119,7 @@ par ailleurs les catégories en tant que telles : sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/pt/tutorial/interfaces.rst b/src/doc/pt/tutorial/interfaces.rst index a1c3cea5671..7feea552c51 100644 --- a/src/doc/pt/tutorial/interfaces.rst +++ b/src/doc/pt/tutorial/interfaces.rst @@ -144,7 +144,7 @@ coisas a respeito dele. GAP === -O Sage vem com o GAP 4.4.10 para matemática discreta computacional, +O Sage vem com o GAP para matemática discreta computacional, especialmente teoria de grupos. Aqui está um exemplo com a função ``IdGroup`` do GAP, a qual usa a @@ -176,12 +176,11 @@ a interface do GAP da seguinte forma: sage: n = G.order(); n 120 -(Para alguns recursos adicionais do GAP, você deve instalar dois -pacotes opcionais. Digite ``sage -optional`` para uma lista e escolha -o pacote da forma ``gap\_packages-x.y.z``, então digite ``sage -i -gap\_packages-x.y.z``. Faça o mesmo para ``database\_gap-x.y.z``. -Alguns pacotes do GAP sem licensa GPL podem ser obtidos no site do GAP -[GAPkg]_, e copiados em ``$SAGE_ROOT/local/lib/gap-4.4.10/pkg``.) +Para algumas funcionalidades do GAP, deve-se instalar dois pacotes +Sage opcionais. Isso pode ser feito com o comando:: + + sage -i gap_packages database_gap + Singular ======== diff --git a/src/doc/pt/tutorial/programming.rst b/src/doc/pt/tutorial/programming.rst index 968a57dee10..32ce91b4eec 100644 --- a/src/doc/pt/tutorial/programming.rst +++ b/src/doc/pt/tutorial/programming.rst @@ -192,7 +192,7 @@ O seguinte script em Sage fatora inteiros, polinômios, etc: :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys from sage.all import * diff --git a/src/doc/pt/tutorial/tour_coercion.rst b/src/doc/pt/tutorial/tour_coercion.rst index 58ca4301876..569caee10fa 100644 --- a/src/doc/pt/tutorial/tour_coercion.rst +++ b/src/doc/pt/tutorial/tour_coercion.rst @@ -121,7 +121,9 @@ categorias matemáticas também são implementadas no Sage: sage: Rings() Category of rings sage: ZZ.category() - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True diff --git a/src/doc/ru/tutorial/interfaces.rst b/src/doc/ru/tutorial/interfaces.rst index e8a5eb4d6cb..4be09f909ca 100644 --- a/src/doc/ru/tutorial/interfaces.rst +++ b/src/doc/ru/tutorial/interfaces.rst @@ -140,7 +140,7 @@ Sage использует С-библиотеку PARI, чтобы поддер GAP === -Sage поставляется с GAP 4.4.10 для вычислений в области дискретной математики, +Sage поставляется с GAP для вычислений в области дискретной математики, в особенности, в теории групп. Вот пример функции ``IdGroup`` из GAP, которая использует базу данных небольших @@ -171,12 +171,11 @@ Sage поставляется с GAP 4.4.10 для вычислений в об sage: n = G.order(); n 120 -(Для функционала GAP следует установить два дополнительных пакета Sage. -Введите ``sage -optional`` для списка и выберите пакет вида -``gap\_packages-x.y.z``, потом введите ``sage -i gap\_packages-x.y.z``. -Сделайте то же для ``database\_gap-x.y.z``. Некоторые не-GPL пакеты GAP -могут быть установлены скачиванием их с сайта GAP [GAPkg]_, и распаковкой -их в директорию ``$SAGE_ROOT/local/lib/gap-4.4.10/pkg``.) +Некотоые функции системы GAP доступны только после инсталляции +двух дополнительных пакетов Sage. Они могут быть установлены командой:: + + sage -i gap_packages database_gap + Singular ======== diff --git a/src/doc/ru/tutorial/programming.rst b/src/doc/ru/tutorial/programming.rst index 6b831e3d9bc..a81d01054e4 100644 --- a/src/doc/ru/tutorial/programming.rst +++ b/src/doc/ru/tutorial/programming.rst @@ -159,7 +159,7 @@ C и обработан компилятором C. :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys from sage.all import * diff --git a/src/ext/doctest/rich_output/example.avi b/src/ext/doctest/rich_output/example.avi new file mode 100644 index 00000000000..db69e00674a Binary files /dev/null and b/src/ext/doctest/rich_output/example.avi differ diff --git a/src/ext/doctest/rich_output/example.flv b/src/ext/doctest/rich_output/example.flv new file mode 100644 index 00000000000..e4f9177e3ca Binary files /dev/null and b/src/ext/doctest/rich_output/example.flv differ diff --git a/src/ext/doctest/rich_output/example.mkv b/src/ext/doctest/rich_output/example.mkv new file mode 100644 index 00000000000..0ab63871ccf Binary files /dev/null and b/src/ext/doctest/rich_output/example.mkv differ diff --git a/src/ext/doctest/rich_output/example.mov b/src/ext/doctest/rich_output/example.mov new file mode 100644 index 00000000000..d958c241691 Binary files /dev/null and b/src/ext/doctest/rich_output/example.mov differ diff --git a/src/ext/doctest/rich_output/example.mp4 b/src/ext/doctest/rich_output/example.mp4 new file mode 100644 index 00000000000..b41de48519f Binary files /dev/null and b/src/ext/doctest/rich_output/example.mp4 differ diff --git a/src/ext/doctest/rich_output/example.ogv b/src/ext/doctest/rich_output/example.ogv new file mode 100644 index 00000000000..c87858564e6 Binary files /dev/null and b/src/ext/doctest/rich_output/example.ogv differ diff --git a/src/ext/doctest/rich_output/example.webm b/src/ext/doctest/rich_output/example.webm new file mode 100644 index 00000000000..b1e738a8d27 Binary files /dev/null and b/src/ext/doctest/rich_output/example.webm differ diff --git a/src/ext/doctest/rich_output/example.wmv b/src/ext/doctest/rich_output/example.wmv new file mode 100644 index 00000000000..4e4397bd71d Binary files /dev/null and b/src/ext/doctest/rich_output/example.wmv differ diff --git a/src/module_list.py b/src/module_list.py index 19262fbbd20..49c23836fda 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -62,13 +62,6 @@ givaro_extra_compile_args =['-D__STDC_LIMIT_MACROS'] -######################################################### -### PolyBoRi settings -######################################################### - -polybori_extra_compile_args = [] -polybori_major_version = '0.8' - ######################################################### ### Library order ######################################################### @@ -86,8 +79,8 @@ "linboxsage", "ntl", "iml", "linbox", "givaro", "gsl", "pari", "flint", "ratpoints", "ecl", "glpk", "ppl", "arb", "mpfi", "mpfr", "mpc", "gmp", "gmpxx", - "polybori-" + polybori_major_version, - "polybori_groebner-" + polybori_major_version, + "polybori", + "polybori_groebner", "m4rie", "m4ri", "zn_poly", "gap", "gd", "png12", @@ -332,7 +325,8 @@ def uname_specific(name, value, alternative): sources = ['sage/graphs/base/static_dense_graph.pyx']), Extension('sage.graphs.base.static_sparse_graph', - sources = ['sage/graphs/base/static_sparse_graph.pyx']), + sources = ['sage/graphs/base/static_sparse_graph.pyx'], + language = 'c++'), Extension('sage.graphs.base.static_sparse_backend', sources = ['sage/graphs/base/static_sparse_backend.pyx']), @@ -365,6 +359,9 @@ def uname_specific(name, value, alternative): sources = ['sage/graphs/planarity.pyx'], libraries=['planarity']), + Extension('sage.graphs.strongly_regular_db', + sources = ['sage/graphs/strongly_regular_db.pyx']), + Extension('sage.graphs.graph_decompositions.rankwidth', sources = ['sage/graphs/graph_decompositions/rankwidth.pyx'], libraries=['rw']), @@ -545,12 +542,7 @@ def uname_specific(name, value, alternative): extra_compile_args = ["-std=c99", "-D_XPG6"]), Extension('sage.libs.fplll.fplll', - sources = ['sage/libs/fplll/fplll.pyx'], - libraries = ['gmp', 'mpfr', 'fplll'], - language="c++", - include_dirs = [SAGE_INC + '/fplll'], - extra_compile_args=["-DFPLLL_V3_COMPAT"], - depends = [SAGE_INC + "/fplll/fplll.h"]), + sources = ['sage/libs/fplll/fplll.pyx']), Extension('sage.libs.gmp.pylong', sources = ['sage/libs/gmp/pylong.pyx']), @@ -613,10 +605,7 @@ def uname_specific(name, value, alternative): libraries = ['flint']), Extension('sage.libs.ppl', - sources = ['sage/libs/ppl.pyx', 'sage/libs/ppl_shim.cc'], - libraries = ['ppl', 'gmpxx', 'gmp', 'm'], - language="c++", - depends = [SAGE_INC + "/ppl.hh"]), + sources = ['sage/libs/ppl.pyx', 'sage/libs/ppl_shim.cc']), Extension('sage.libs.ratpoints', sources = ["sage/libs/ratpoints.pyx"], @@ -880,12 +869,10 @@ def uname_specific(name, value, alternative): Extension('sage.matrix.matrix2', sources = ['sage/matrix/matrix2.pyx']), - OptionalExtension("sage.matrix.matrix_complex_ball_dense", - ["sage/matrix/matrix_complex_ball_dense.pyx"], - language="c", - libraries=['arb', 'mpfi', 'mpfr'], - include_dirs=[SAGE_INC + '/flint'], - package='arb'), + Extension("sage.matrix.matrix_complex_ball_dense", + ["sage/matrix/matrix_complex_ball_dense.pyx"], + libraries=['arb', 'mpfi', 'mpfr'], + include_dirs=[SAGE_INC + '/flint']), Extension('sage.matrix.matrix_complex_double_dense', sources = ['sage/matrix/matrix_complex_double_dense.pyx']), @@ -922,8 +909,8 @@ def uname_specific(name, value, alternative): extra_compile_args = m4ri_extra_compile_args, depends = [SAGE_INC + "/png.h", SAGE_INC + "/m4ri/m4ri.h"]), - Extension('sage.matrix.matrix_mod2e_dense', - sources = ['sage/matrix/matrix_mod2e_dense.pyx'], + Extension('sage.matrix.matrix_gf2e_dense', + sources = ['sage/matrix/matrix_gf2e_dense.pyx'], libraries = ['m4rie', 'm4ri', 'm'], depends = [SAGE_INC + "/m4rie/m4rie.h"], extra_compile_args = m4ri_extra_compile_args), @@ -1021,9 +1008,7 @@ def uname_specific(name, value, alternative): Extension('sage.modular.arithgroup.farey_symbol', sources = ['sage/modular/arithgroup/farey_symbol.pyx', 'sage/modular/arithgroup/farey.cpp', - 'sage/modular/arithgroup/sl2z.cpp'], - libraries = ['gmpxx', 'gmp'], - language = 'c++'), + 'sage/modular/arithgroup/sl2z.cpp']), Extension('sage.modular.arithgroup.arithgroup_element', sources = ['sage/modular/arithgroup/arithgroup_element.pyx']), @@ -1248,11 +1233,10 @@ def uname_specific(name, value, alternative): libraries=['ntl'], language = 'c++'), - OptionalExtension("sage.rings.complex_ball_acb", - ["sage/rings/complex_ball_acb.pyx"], - libraries=['arb', 'mpfi', 'mpfr'], - include_dirs=[SAGE_INC + '/flint'], - package='arb'), + Extension("sage.rings.complex_arb", + ["sage/rings/complex_arb.pyx"], + libraries=['mpfi', 'mpfr'], + include_dirs=[SAGE_INC + '/flint']), Extension('sage.rings.complex_double', sources = ['sage/rings/complex_double.pyx'], @@ -1321,11 +1305,10 @@ def uname_specific(name, value, alternative): Extension('sage.rings.real_interval_absolute', sources = ['sage/rings/real_interval_absolute.pyx']), - OptionalExtension("sage.rings.real_arb", - ["sage/rings/real_arb.pyx"], - libraries = ['arb', 'mpfi', 'mpfr'], - include_dirs = [SAGE_INC + '/flint'], - package = 'arb'), + Extension("sage.rings.real_arb", + ["sage/rings/real_arb.pyx"], + libraries = ['mpfi', 'mpfr'], + include_dirs = [SAGE_INC + '/flint']), Extension('sage.rings.real_lazy', sources = ['sage/rings/real_lazy.pyx']), @@ -1583,11 +1566,10 @@ def uname_specific(name, value, alternative): Extension('sage.rings.polynomial.pbori', sources = ['sage/rings/polynomial/pbori.pyx'], - libraries=['polybori-' + polybori_major_version, - 'polybori_groebner-' + polybori_major_version, 'm4ri', 'gd', 'png12'], - depends = [SAGE_INC + "/polybori/" + hd + ".h" for hd in ["polybori", "config"] ] + \ + libraries=['polybori', 'polybori_groebner', 'm4ri', 'png12'], + depends = [SAGE_INC + "/polybori/" + hd + ".h" for hd in ["polybori", "config"] ] + [SAGE_INC + '/m4ri/m4ri.h'], - extra_compile_args = polybori_extra_compile_args + m4ri_extra_compile_args, + extra_compile_args = m4ri_extra_compile_args, language = 'c++'), Extension('sage.rings.polynomial.polynomial_real_mpfr_dense', @@ -1598,6 +1580,10 @@ def uname_specific(name, value, alternative): sources = ['sage/rings/polynomial/real_roots.pyx'], libraries=['mpfr']), + Extension('sage.rings.polynomial.refine_root', + sources = ['sage/rings/polynomial/refine_root.pyx'], + libraries=['gmp', 'mpfr', 'mpfi']), + Extension('sage.rings.polynomial.symmetric_reduction', sources = ['sage/rings/polynomial/symmetric_reduction.pyx']), diff --git a/src/sage/algebras/associated_graded.py b/src/sage/algebras/associated_graded.py new file mode 100644 index 00000000000..750cbe33df1 --- /dev/null +++ b/src/sage/algebras/associated_graded.py @@ -0,0 +1,341 @@ +r""" +Associated Graded Algebras To Filtered Algebras + +AUTHORS: + +- Travis Scrimshaw (2014-10-08): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from copy import copy + +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.sets.family import Family +from sage.combinat.free_module import CombinatorialFreeModule + +class AssociatedGradedAlgebra(CombinatorialFreeModule): + r""" + The associated graded algebra/module `\operatorname{gr} A` + of a filtered algebra/module with basis `A`. + + Let `A` be a filtered module over a commutative ring `R`. + Let `(F_i)_{i \in I}` be the filtration of `A`, with `I` being + a totally ordered set. Define + + .. MATH:: + + G_i = F_i / \sum_{j < i} F_j + + for every `i \in I`, and then + + .. MATH:: + + \operatorname{gr} A = \bigoplus_{i \in I} G_i. + + There are canonical projections `p_i : F_i \to G_i` for + every `i \in I`. Moreover `\operatorname{gr} A` is naturally a + graded `R`-module with `G_i` being the `i`-th graded component. + This graded `R`-module is known as the *associated graded module* + (or, for short, just *graded module*) of `A`. + + Now, assume that `A` (endowed with the filtration + `(F_i)_{i \in I}`) is not just a filtered `R`-module, but also + a filtered `R`-algebra. + Let `u \in G_i` and `v \in G_j`, and let `u' \in F_i` and + `v' \in F_j` be lifts of `u` and `v`, respectively (so that + `u = p_i(u')` and `v = p_j(v')`). Then, we define a + multiplication `*` on `\operatorname{gr} A` (not to be mistaken + for the multiplication of the original algebra `A`) by + + .. MATH:: + + u * v = p_{i+j} (u' v'). + + The *associated graded algebra* (or, for short, just + *graded algebra*) of `A` is the graded algebra + `\operatorname{gr} A` (endowed with this multiplication). + + Now, assume that `A` is a filtered `R`-algebra with basis. + Let `(b_x)_{x \in X}` be the basis of `A`, + and consider the partition `X = \bigsqcup_{i \in I} X_i` of + the set `X`, which is part of the data of a filtered + algebra with basis. We know (see + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis`) + that `A` (being a filtered `R`-module with basis) is canonically + (when the basis is considered to be part of the data) + isomorphic to `\operatorname{gr} A` as an `R`-module. Therefore + the `k`-th graded component `G_k` can be identified with + the span of `(b_x)_{x \in X_k}`, or equivalently the + `k`-th homogeneous component of `A`. Suppose + that `u' v' = \sum_{k \leq i+j} m_k` where `m_k \in G_k` (which + has been identified with the `k`-th homogeneous component of `A`). + Then `u * v = m_{i+j}`. We also note that the choice of + identification of `G_k` with the `k`-th homogeneous component + of `A` depends on the given basis. + + The basis `(b_x)_{x \in X}` of `A` gives rise to a basis + of `\operatorname{gr} A`. This latter basis is still indexed + by the elements of `X`, and consists of the images of the + `b_x` under the `R`-module isomorphism from `A` to + `\operatorname{gr} A`. It makes `\operatorname{gr} A` into + a graded `R`-algebra with basis. + + In this class, the `R`-module isomorphism from `A` to + `\operatorname{gr} A` is implemented as + :meth:`to_graded_conversion` and also as the default + conversion from `A` to `\operatorname{gr} A`. Its + inverse map is implemented as + :meth:`from_graded_conversion`. + The projection `p_i : F_i \to G_i` is implemented as + :meth:`projection` ``(i)``. + + INPUT: + + - ``A`` -- a filtered module (or algebra) with basis + + OUTPUT: + + The associated graded module of `A`, if `A` is just a filtered + `R`-module. + The associated graded algebra of `A`, if `A` is a filtered + `R`-algebra. + + EXAMPLES: + + Associated graded module of a filtered module:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded modules with basis over Rational Field + sage: x = A.basis()[Partition([3,2,1])] + sage: grA(x) + Bbar[[3, 2, 1]] + + Associated graded algebra of a filtered algebra:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded algebras with basis over Rational Field + sage: x,y,z = map(lambda s: grA.algebra_generators()[s], ['x','y','z']) + sage: x + bar(U['x']) + sage: y * x + z + bar(U['x']*U['y']) + bar(U['z']) + sage: A(y) * A(x) + A(z) + U['x']*U['y'] + + We note that the conversion between ``A`` and ``grA`` is + the canonical ``QQ``-module isomorphism stemming from the + fact that the underlying ``QQ``-modules of ``A`` and + ``grA`` are isomorphic:: + + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: elt = A.an_element() + A.algebra_generators()['x'] + 2 + sage: grelt = grA(elt); grelt + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: A(grelt) == elt + True + + .. TODO:: + + The algebra ``A`` must currently be an instance of (a subclass of) + :class:`CombinatorialFreeModule`. This should work with any + filtered algebra with a basis. + + .. TODO:: + + Implement a version of associated graded algebra for + filtered algebras without a distinguished basis. + + REFERENCES: + + - :wikipedia:`Filtered_algebra#Associated_graded_algebra` + """ + def __init__(self, A, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: TestSuite(grA).run(elements=[prod(grA.algebra_generators())]) + """ + if A not in ModulesWithBasis(A.base_ring().category()).Filtered(): + raise ValueError("the base algebra must be filtered and with basis") + self._A = A + + base_ring = A.base_ring() + base_one = base_ring.one() + + category = A.category().Graded().or_subcategory(category) + try: + opts = copy(A.print_options()) + if not opts['prefix'] and not opts['bracket']: + opts['bracket'] = '(' + opts['prefix'] = opts['prefix'] + 'bar' + except AttributeError: + opts = {'prefix': 'Abar'} + + CombinatorialFreeModule.__init__(self, base_ring, A.basis().keys(), + category=category, **opts) + + # Setup the conversion back + phi = self.module_morphism(diagonal=lambda x: base_one, codomain=A) + self._A.register_conversion(phi) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + """ + from sage.categories.algebras_with_basis import AlgebrasWithBasis + if self in AlgebrasWithBasis: + return "Graded Algebra of {}".format(self._A) + return "Graded Module of {}".format(self._A) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: latex(A.graded_algebra()) + \operatorname{gr} ... + """ + from sage.misc.latex import latex + return "\\operatorname{gr} " + latex(self._A) + + def _element_constructor_(self, x): + r""" + Construct an element of ``self`` from ``x``. + + If ``self`` `= \operatorname{gr} A` for a filtered algebra + `A` with basis, and if ``x`` is an element of `A`, then + this returns the image of `x` under the canonical `R`-module + isomorphism `A \to \operatorname{gr} A`. (In this case, + this is equivalent to calling + ``self.to_graded_conversion()(x)``.) + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: grA(A.an_element() + A.algebra_generators()['x'] + 2) + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + """ + if isinstance(x, CombinatorialFreeModule.Element): + if x.parent() is self._A: + return self._from_dict(dict(x)) + return super(AssociatedGradedAlgebra, self)._element_constructor_(x) + + def gen(self, *args, **kwds): + """ + Return a generator of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.gen('x') + bar(U['x']) + """ + try: + x = self._A.gen(*args, **kwds) + except AttributeError: + x = self._A.algebra_generators()[args[0]] + return self(x) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + This assumes that the algebra generators of `A` provided by + its ``algebra_generators`` method are homogeneous. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.algebra_generators() + Finite family {'y': bar(U['y']), 'x': bar(U['x']), 'z': bar(U['z'])} + """ + G = self._A.algebra_generators() + return Family(G.keys(), lambda x: self(G[x]), name="generator") + + def degree_on_basis(self, x): + """ + Return the degree of the basis element indexed by ``x``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: all(A.degree_on_basis(x) == grA.degree_on_basis(x) + ....: for g in grA.algebra_generators() for x in g.support()) + True + """ + return self._A.degree_on_basis(x) + + @cached_method + def one_basis(self): + """ + Return the basis index of the element `1` of + `\operatorname{gr} A`. + + This assumes that the unity `1` of `A` belongs to `F_0`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.one_basis() + 1 + """ + return self._A.one_basis() + + def product_on_basis(self, x, y): + """ + Return the product on basis elements given by the + indices ``x`` and ``y``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: G = grA.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: x * y # indirect doctest + bar(U['x']*U['y']) + sage: y * x + bar(U['x']*U['y']) + sage: z * y * x + bar(U['x']*U['y']*U['z']) + """ + ret = self._A.product_on_basis(x, y) + deg = self._A.degree_on_basis(x) + self._A.degree_on_basis(y) + return self.sum_of_terms([(i,c) for i,c in ret + if self._A.degree_on_basis(i) == deg], + distinct=True) + diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index da062022b5d..06910534633 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -16,6 +16,7 @@ - :class:`algebras.FiniteDimensional ` - :class:`algebras.Free ` +- :class:`algebras.FreeZinbiel ` - :class:`algebras.PreLieAlgebra ` - :func:`algebras.GradedCommutative ` @@ -24,6 +25,7 @@ - :class:`algebras.Incidence ` - :class:`algebras.IwahoriHecke ` +- :class:`algebras.Mobius ` - :class:`algebras.Jordan ` - :class:`algebras.NilCoxeter @@ -48,12 +50,14 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.algebras.nil_coxeter_algebra', 'NilCoxeterAlgebra', 'NilCoxeter') +lazy_import('sage.algebras.free_zinbiel_algebra', 'FreeZinbielAlgebra', 'FreeZinbiel') lazy_import('sage.algebras.hall_algebra', 'HallAlgebra', 'Hall') lazy_import('sage.algebras.jordan_algebra', 'JordanAlgebra', 'Jordan') lazy_import('sage.algebras.shuffle_algebra', 'ShuffleAlgebra', 'Shuffle') lazy_import('sage.algebras.schur_algebra', 'SchurAlgebra', 'Schur') lazy_import('sage.algebras.commutative_dga', 'GradedCommutativeAlgebra', 'GradedCommutative') lazy_import('sage.combinat.posets.incidence_algebras', 'IncidenceAlgebra', 'Incidence') +lazy_import('sage.combinat.posets.mobius_algebra', 'MobiusAlgebra', 'Mobius') lazy_import('sage.combinat.free_prelie_algebra', 'FreePreLieAlgebra', 'FreePreLie') del lazy_import # We remove the object from here so it doesn't appear under tab completion diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index b419d985be3..0e570434890 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -18,8 +18,7 @@ from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis -from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis -from sage.categories.graded_hopf_algebras_with_basis import GradedHopfAlgebrasWithBasis +from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.modules.with_basis.morphism import ModuleMorphismByLinearity from sage.categories.poor_man_map import PoorManMap from sage.rings.all import ZZ @@ -423,6 +422,14 @@ class CliffordAlgebra(CombinatorialFreeModule): canonical isomorphism. The inclusion `i` is commonly used to identify `V` with a vector subspace of `Cl(V)`. + The Clifford algebra `Cl(V, Q)` is a `\ZZ_2`-graded algebra + (where `\ZZ_2 = \ZZ / 2 \ZZ`); this grading is determined by + placing all elements of `V` in degree `1`. It is also an + `\NN`-filtered algebra, with the filtration too being defined + by placing all elements of `V` in degree `1`. The :meth:`degree` gives + the `\NN`-*filtration* degree, and to get the super degree use instead + :meth:`~sage.categories.super_modules.SuperModules.ElementMethods.is_even_odd`. + The Clifford algebra also can be considered as a covariant functor from the category of vector spaces equipped with quadratic forms to the category of algebras. In fact, if `(V, Q)` and `(W, R)` @@ -465,7 +472,8 @@ class CliffordAlgebra(CombinatorialFreeModule): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) sage: Cl - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -480,11 +488,6 @@ class CliffordAlgebra(CombinatorialFreeModule): a*d sage: d*c*b*a + a + 4*b*c a*b*c*d + 4*b*c + a - - .. WARNING:: - - The Clifford algebra is not graded, but instead filtered. This - will be changed once :trac:`17096` is finished. """ @staticmethod def __classcall_private__(cls, Q, names=None): @@ -520,6 +523,9 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) + sage: Cl.category() + Category of finite dimensional super algebras with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: TestSuite(Cl).run() TESTS: @@ -536,8 +542,7 @@ def __init__(self, Q, names, category=None): """ self._quadratic_form = Q R = Q.base_ring() - if category is None: - category = GradedAlgebrasWithBasis(R) + category = AlgebrasWithBasis(R.category()).Super().Filtered().or_subcategory(category) indices = SubsetsSorted(range(Q.dim())) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -550,7 +555,8 @@ def _repr_(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: CliffordAlgebra(Q) - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -826,7 +832,7 @@ def quadratic_form(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.quadratic_form() - Quadratic form in 3 variables over Integer Ring with coefficients: + Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -837,19 +843,8 @@ def degree_on_basis(self, m): r""" Return the degree of the monomial indexed by ``m``. - This degree is a nonnegative integer, and should be interpreted - as a residue class modulo `2`, since we consider ``self`` to be - `\ZZ_2`-graded (not `\ZZ`-graded, although there is a natural - *filtration* by the length of ``m``). The degree of the monomial - ``m`` in this `\ZZ_2`-grading is defined to be the length of ``m`` - taken mod `2`. - - .. WARNING: - - On the :class:`ExteriorAlgebra` class (which inherits from - :class:`CliffordAlgebra`), the :meth:`degree_on_basis` - method is overridden to return an actual `\NN`-degree. So - don't count on this method always returning `0` or `1` !! + We are considering the Clifford algebra to be `\NN`-filtered, + and the degree of the monomial ``m`` is the length of ``m``. EXAMPLES:: @@ -858,9 +853,22 @@ def degree_on_basis(self, m): sage: Cl.degree_on_basis((0,)) 1 sage: Cl.degree_on_basis((0,1)) - 0 + 2 """ - return len(m) % ZZ(2) + return ZZ(len(m)) + + def graded_algebra(self): + """ + Return the associated graded algebra of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: Cl.graded_algebra() + The exterior algebra of rank 3 over Integer Ring + """ + return ExteriorAlgebra(self.base_ring(), self.variable_names()) @cached_method def free_module(self): @@ -1051,7 +1059,7 @@ def lift_module_morphism(self, m, names=None): remove_zeros=True ) for i in x) return Cl.module_morphism(on_basis=f, codomain=self, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) def lift_isometry(self, m, names=None): r""" @@ -1116,7 +1124,7 @@ def lift_isometry(self, m, names=None): remove_zeros=True ) for i in x) return self.module_morphism(on_basis=f, codomain=Cl, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) # This is a general method for finite dimensional algebras with bases # and should be moved to the corresponding category once there is @@ -1339,7 +1347,7 @@ class ExteriorAlgebra(CliffordAlgebra): `Q(v) = 0` for all vectors `v \in V`. See :class:`CliffordAlgebra` for the notion of a Clifford algebra. - The exterior algebra of an `R`-module `V` is a `\ZZ`-graded connected + The exterior algebra of an `R`-module `V` is a connected `\ZZ`-graded Hopf superalgebra. It is commutative in the super sense (i.e., the odd elements anticommute and square to `0`). @@ -1353,10 +1361,6 @@ class ExteriorAlgebra(CliffordAlgebra): Hopf superalgebra with the odd-degree components forming the odd part. So use Hopf-algebraic methods with care! - .. TODO:: - - Add a category for Hopf superalgebras (perhaps part of :trac:`16513`). - INPUT: - ``R`` -- the base ring, *or* the free module whose exterior algebra @@ -1417,9 +1421,13 @@ def __init__(self, R, names): EXAMPLES:: sage: E. = ExteriorAlgebra(QQ) + sage: E.category() + Category of finite dimensional super hopf algebras with basis + over Rational Field sage: TestSuite(E).run() """ - CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, GradedHopfAlgebrasWithBasis(R)) + cat = HopfAlgebrasWithBasis(R).Super() + CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, cat) # TestSuite will fail if the HopfAlgebra classes will ever have tests for # the coproduct being an algebra morphism -- since this is really a # Hopf superalgebra, not a Hopf algebra. @@ -1563,7 +1571,7 @@ def lift_morphism(self, phi, names=None): f = lambda x: E.prod(E._from_dict( {(j,): phi[j,i] for j in range(n)}, remove_zeros=True ) for i in x) - return self.module_morphism(on_basis=f, codomain=E, category=GradedAlgebrasWithBasis(R)) + return self.module_morphism(on_basis=f, codomain=E, category=AlgebrasWithBasis(R).Super()) def volume_form(self): """ @@ -1830,9 +1838,9 @@ def lifted_bilinear_form(self, M): sage: M = Matrix(QQ, [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) sage: Eform = E.lifted_bilinear_form(M) sage: Eform - Bilinear Form from Cartesian product of The exterior algebra of rank 3 over - Rational Field, The exterior algebra of rank 3 over Rational Field to - Rational Field + Bilinear Form from The exterior algebra of rank 3 over Rational + Field (+) The exterior algebra of rank 3 over Rational Field to + Rational Field sage: Eform(x*y, y*z) -1 sage: Eform(x*y, y) @@ -1903,8 +1911,8 @@ def lifted_form(x, y): # typing (:trac:`17124`). result += cx * cy * matr.determinant() return result - from sage.combinat.cartesian_product import CartesianProduct - return PoorManMap(lifted_form, domain=CartesianProduct(self, self), + from sage.categories.cartesian_product import cartesian_product + return PoorManMap(lifted_form, domain=cartesian_product([self, self]), codomain=self.base_ring(), name="Bilinear Form") diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 7e7f8e3111e..8c2de5b3573 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -2517,6 +2517,16 @@ def __init__(self, x): """ self._x = x + def __hash__(self): + r""" + TESTS:: + + sage: from sage.algebras.commutative_dga import CohomologyClass + sage: hash(CohomologyClass(sin)) == hash(sin) + True + """ + return hash(self._x) + def _repr_(self): """ EXAMPLES:: diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py index 898ceabe337..c17d4be8f4c 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py @@ -134,6 +134,28 @@ def matrix(self): """ return self._matrix + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) + sage: elt = B(Matrix([[1,1], [-1,1]])) + sage: elt.monomial_coefficients() + {0: 1, 1: 1} + """ + return self._vector.dict(copy) + def left_matrix(self): """ Return the matrix for multiplication by ``self`` from the left. diff --git a/src/sage/algebras/free_algebra_quotient_element.py b/src/sage/algebras/free_algebra_quotient_element.py index 56895d128c3..702d2bcc7b5 100644 --- a/src/sage/algebras/free_algebra_quotient_element.py +++ b/src/sage/algebras/free_algebra_quotient_element.py @@ -23,7 +23,7 @@ #***************************************************************************** from sage.misc.misc import repr_lincomb -from sage.rings.ring_element import RingElement +from sage.structure.element import RingElement from sage.rings.integer import Integer from sage.algebras.algebra_element import AlgebraElement from sage.modules.free_module_element import FreeModuleElement diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py new file mode 100644 index 00000000000..9c00304eea7 --- /dev/null +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -0,0 +1,253 @@ +""" +Free Zinbiel Algebras + +AUTHORS: + +- Travis Scrimshaw (2015-09): initial version +""" + +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.magmatic_algebras import MagmaticAlgebras +from sage.categories.rings import Rings +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.words.words import Words +from sage.combinat.words.alphabet import Alphabet +from sage.sets.family import Family + +class FreeZinbielAlgebra(CombinatorialFreeModule): + r""" + The free Zinbiel algebra on `n` generators. + + Let `R` be a ring. A *Zinbiel algebra* is a non-associative + algebra with multiplication `\circ` that satisfies + + .. MATH:: + + a \circ (b \circ c) = a \circ (b \circ c) + a \circ (c \circ b). + + Zinbiel algebras were first introduced by Loday as the Koszul + dual to Leibniz algebras (hence the name coined by Lemaire). + + Zinbiel algebras are divided power algebras, in that for + + .. MATH:: + + x^{\circ n} = \bigl(x \circ (x \circ \cdots \circ( x \circ x) \cdots + ) \bigr) + + we have + + .. MATH:: + + x^{\circ m} \circ x^{\circ n} = \binom{n+m-1}{m} x^{n+m} + + and + + .. MATH:: + + \underbrace{\bigl( ( x \circ \cdots \circ x \circ (x \circ x) \cdots + ) \bigr)}_{n+1 \text{ times}} = n! x^n. + + .. NOTE:: + + This implies that Zinbiel algebras are not power associative. + + To every Zinbiel algebra, we can construct a corresponding commutative + associative algebra by using the symmetrized product: + + .. MATH:: + + a * b = a \circ b + b \circ a. + + The free Zinbiel algebra on `n` generators is isomorphic as `R`-modules + to the reduced tensor algebra `\bar{T}(R^n)` with the product + + .. MATH:: + + (x_0 x_1 \cdots x_p) \circ (x_{p+1} x_{p+2} \cdots x_{p+q}) + = \sum_{\sigma \in S_{p,q}} x_0 (x_{\sigma(1)} x_{\sigma(2)} + \cdots x_{\sigma(p+q)}, + + where `S_{p,q}` is the set of `(p,q)`-shuffles. + + The free Zinbiel algebra is free as a divided power algebra. Moreover, + the corresponding commutative algebra is isomorphic to the (non-unital) + shuffle algebra. + + INPUT: + + - ``R`` -- a ring + - ``n`` -- (optional) the number of generators + - ``names`` -- the generator names + + .. WARNING:: + + Currently the basis is indexed by all words over the variables, + incuding the empty word. This is a slight abuse as it is suppose + to be the indexed by all non-empty words. + + EXAMPLES: + + We create the free Zinbiel algebra and check the defining relation:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z + Z[xyz] + Z[xzy] + sage: x*(y*z) + x*(z*y) + Z[xyz] + Z[xzy] + + We see that the Zinbiel algebra is not associative, nor even + power associative:: + + sage: x*(y*z) + Z[xyz] + sage: x*(x*x) + Z[xxx] + sage: (x*x)*x + 2*Z[xxx] + + We verify that it is a divided powers algebra:: + + sage: (x*(x*x)) * (x*(x*(x*x))) + 15*Z[xxxxxxx] + sage: binomial(3+4-1,4) + 15 + sage: (x*(x*(x*x))) * (x*(x*x)) + 20*Z[xxxxxxx] + sage: binomial(3+4-1,3) + 20 + sage: ((x*x)*x)*x + 6*Z[xxxx] + sage: (((x*x)*x)*x)*x + 24*Z[xxxxx] + + REFERENCES: + + - :wikipedia:`Zinbiel_algebra` + + .. [Loday95] Jean-Louis Loday. + *Cup-product for Leibniz cohomology and dual Leibniz algebras*. + Math. Scand., pp. 189--196 (1995). + http://www.math.uiuc.edu/K-theory/0015/cup_product.pdf + .. [LV12] Jean-Louis Loday and Bruno Vallette. *Algebraic Operads*. + Springer-Verlag Berlin Heidelberg (2012). + :doi:`10.1007/978-3-642-30362-3`. + """ + @staticmethod + def __classcall_private__(cls, R, n=None, names=None): + """ + Standardize input to ensure a unqiue representation. + + TESTS:: + + sage: Z1. = algebras.FreeZinbiel(QQ) + sage: Z2. = algebras.FreeZinbiel(QQ, 3) + sage: Z3 = algebras.FreeZinbiel(QQ, 3, 'x,y,z') + sage: Z4. = algebras.FreeZinbiel(QQ, 'x,y,z') + sage: Z1 is Z2 and Z1 is Z3 and Z1 is Z4 + True + """ + if isinstance(n, (list,tuple)): + names = n + n = len(names) + elif isinstance(n, str): + names = n.split(',') + n = len(names) + elif isinstance(names, str): + names = names.split(',') + elif n is None: + n = len(names) + return super(FreeZinbielAlgebra, cls).__classcall__(cls, R, n, tuple(names)) + + def __init__(self, R, n, names): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: TestSuite(Z).run() + """ + if R not in Rings: + raise TypeError("argument R must be a ring") + indices = Words(Alphabet(n, names=names)) + cat = MagmaticAlgebras(R).WithBasis() + self._n = n + CombinatorialFreeModule.__init__(self, R, indices, prefix='Z', + category=cat) + self._assign_names(names) + + def _repr_term(self, t): + """ + Return a string representation of the basis element indexed by ``t``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z._repr_term(Z._indices('xyzxxy')) + 'Z[xyzxxy]' + """ + return "{!s}[{!s}]".format(self._print_options['prefix'], repr(t)[6:]) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) over Rational Field + """ + return "Free Zinbiel algebra on generators {} over {}".format( + self.gens(), self.base_ring()) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: list(Z.algebra_generators()) + [Z[x], Z[y], Z[z]] + """ + A = self.variable_names() + return Family( A, lambda g: self.monomial(self._indices(g)) ) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z.gens() + (Z[x], Z[y], Z[z]) + """ + return tuple(self.algebra_generators()) + + def product_on_basis(self, x, y): + """ + Return the product of the basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z # indirect doctest + Z[xyz] + Z[xzy] + """ + if not x: + return self.monomial(y) + x0 = self._indices(x[0]) + return self.sum_of_monomials(x0 + sh for sh in x[1:].shuffle(y)) + diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index d434996437b..0f024e61113 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -556,6 +556,30 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._x) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F. = FreeAlgebra(QQ) + sage: J = JordanAlgebra(F) + sage: a,b,c = map(J, F.gens()) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {x: 1, y: 2, z: -1} + """ + return self._x.monomial_coefficients(copy) + class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. @@ -935,6 +959,29 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._s, other * self._v) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {0: 1, 1: 2, 2: -1} + """ + d = {0: self._s} + for i,c in enumerate(self._v): + d[i+1] = c + return d + def trace(self): r""" Return the trace of ``self``. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3664235c448..b591d5a18ee 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1735,7 +1735,17 @@ def ternary_quadratic_form(self, include_basis=False): return Q class QuaternionFractionalIdeal(Ideal_fractional): - pass + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R = QuaternionAlgebra(-11,-1).maximal_order() + sage: hash(R.right_ideal(R.basis())) + 0 + """ + return 0 class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal): """ @@ -2495,6 +2505,7 @@ def __contains__(self, x): Returns whether x is in self. EXAMPLES:: + sage: R. = QuaternionAlgebra(-3, -13) sage: I = R.ideal([2+i, 3*i, 5*j, j+k]) sage: 2+i in I diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index 7ef9cfabbde..f3092e9cf3f 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -21,20 +21,24 @@ .. [GreenPoly] J. Green, Polynomial representations of `GL_n`, Springer Verlag. """ + #***************************************************************************** -# Copyright (C) 2010 Eric Webster -# Copyright (C) 2011 Hugh Thomas (hugh.ross.thomas@gmail.com) +# Copyright (C) 2010 Eric Webster +# Copyright (C) 2011 Hugh Thomas # -# Distributed under the terms of the GNU General Public License (GPL) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools from sage.categories.all import AlgebrasWithBasis from sage.categories.rings import Rings from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor -from sage.combinat.cartesian_product import CartesianProduct -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.partition import Partitions, Partition from sage.combinat.permutation import Permutations from sage.combinat.sf.sf import SymmetricFunctions @@ -465,7 +469,7 @@ def _monomial_product(self, xi, v): B[1] # B[1] # B[2] + B[1] # B[2] # B[1] + B[2] # B[1] # B[1] """ ret = [] - for i in CartesianProduct(*[range(1, self._n + 1)] * self._r): + for i in itertools.product(range(1, self._n + 1), repeat=self._r): if schur_representative_from_index(i, v) == xi: ret.append(tuple(i)) return self.sum_of_monomials(ret) diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index de25ebb1564..e98c6d2474e 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -404,12 +404,13 @@ 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] sage: Adem = SteenrodAlgebra(basis='adem') - sage: (Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1)).monomials() + sage: elt = Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1) + sage: sorted(elt.monomials(), key=lambda x: x.support()) [Sq^9 Sq^1, Sq^10] sage: A7 = SteenrodAlgebra(p=7) @@ -1567,13 +1568,13 @@ def counit_on_basis(self, t): return self.base_ring().one() def _milnor_on_basis(self, t): - """ - Convert the tuple t in the current basis to an element in the + r""" + Convert the tuple ``t`` in the current basis to an element in the Milnor basis. INPUT: - - t - tuple, representing basis element in the current basis. + - ``t`` - tuple, representing basis element in the current basis. OUTPUT: element of the Steenrod algebra with the Milnor basis @@ -3115,9 +3116,9 @@ class Element(CombinatorialFreeModuleElement): 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] See the documentation for this module (type @@ -3451,8 +3452,8 @@ def excess(self): OUTPUT: ``excess`` - non-negative integer The excess of a Milnor basis element `\text{Sq}(a,b,c,...)` is - `a + b + c + ...`. When `p` is odd, the excess of `Q_{0}^{e_0} - Q_{1}^{e_1} ... P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. + `a + b + c + \cdots`. When `p` is odd, the excess of `Q_{0}^{e_0} + Q_{1}^{e_1} \cdots P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. @@ -3470,9 +3471,11 @@ def excess(self): 6 sage: (Sq(0,0,1) + Sq(4,1) + Sq(7)).excess() 1 - sage: [m.excess() for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: elt = Sq(0,0,1) + Sq(4,1) + Sq(7) + sage: M = sorted(elt.monomials(), key=lambda x: x.support()) + sage: [m.excess() for m in M] [1, 5, 7] - sage: [m for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: [m for m in M] [Sq(0,0,1), Sq(4,1), Sq(7)] sage: B = SteenrodAlgebra(7) sage: a = B.Q(1,2,5) @@ -3495,7 +3498,7 @@ def excess_odd(mono): of factors, plus twice the sum of the terms in the second component. """ - if len(mono) == 0: + if not mono: return 0 else: return len(mono[0]) + 2 * sum(mono[1]) diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index 366b6bd99f2..af3f004c2aa 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -402,10 +402,39 @@ def _lmul_(self, other): M = self.__monomials return self.__class__(self.parent(), {t: M[t]*other for t in M}) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary which has the basis keys in the support + of ``self`` as keys and their corresponding coefficients + as values. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = (dy - (3*x - z)*dx) + sage: sorted(elt.monomial_coefficients().items()) + [(((0, 0, 0), (0, 1, 0)), 1), + (((0, 0, 1), (1, 0, 0)), 1), + (((1, 0, 0), (1, 0, 0)), -3)] + """ + if copy: + return dict(self.__monomials) + return self.__monomials + def __iter__(self): """ Return an iterator of ``self``. + This is the iterator of ``self.list()``. + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -421,6 +450,11 @@ def list(self): """ Return ``self`` as a list. + This list consists of pairs `(m, c)`, where `m` is a pair of + tuples indexing a basis element of ``self``, and `c` is the + coordinate of ``self`` corresponding to this basis element. + (Only nonzero coordinates are shown.) + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -434,6 +468,23 @@ def list(self): return sorted(self.__monomials.items(), key=lambda x: (-sum(x[0][1]), x[0][1], -sum(x[0][0]), x[0][0]) ) + def support(self): + """ + Return the support of ``self``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = dy - (3*x - z)*dx + 1 + sage: elt.support() + [((0, 0, 0), (0, 1, 0)), + ((1, 0, 0), (1, 0, 0)), + ((0, 0, 0), (0, 0, 0)), + ((0, 0, 1), (1, 0, 0))] + """ + return self.__monomials.keys() + # This is essentially copied from # sage.combinat.free_module.CombinatorialFreeModuleElement def __div__(self, x, self_on_left=False): @@ -527,6 +578,11 @@ class DifferentialWeylAlgebra(Algebra, UniqueRepresentation): sage: W. = DifferentialWeylAlgebra(QQ); W Differential Weyl algebra of polynomials in a, b over Rational Field + + .. TODO:: + + Implement the :meth:`graded_algebra` as a polynomial ring once + they are considered to be graded rings (algebras). """ @staticmethod def __classcall__(cls, R, names=None): @@ -567,7 +623,12 @@ def __init__(self, R, names=None): raise ValueError("variable names cannot differ by a leading 'd'") # TODO: Make this into a filtered algebra under the natural grading of # x_i and dx_i have degree 1 - Algebra.__init__(self, R, names, category=AlgebrasWithBasis(R).NoZeroDivisors()) + # Filtered is not included because it is a supercategory of super + if R.is_field(): + cat = AlgebrasWithBasis(R).NoZeroDivisors().Super() + else: + cat = AlgebrasWithBasis(R).Super() + Algebra.__init__(self, R, names, category=cat) def _repr_(self): r""" @@ -661,6 +722,24 @@ def _coerce_map_from_(self, R): and self.base_ring().has_coerce_map_from(R.base_ring()) ) return super(DifferentialWeylAlgebra, self)._coerce_map_from_(R) + def degree_on_basis(self, i): + """ + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: W.degree_on_basis( ((1, 3, 2), (0, 1, 3)) ) + 10 + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = y*dy - (3*x - z)*dx + sage: elt.degree() + 2 + """ + return sum(i[0]) + sum(i[1]) + def polynomial_ring(self): """ Return the associated polynomial ring of ``self``. @@ -693,13 +772,21 @@ def basis(self): sage: [next(it) for i in range(20)] [1, x, y, dx, dy, x^2, x*y, x*dx, x*dy, y^2, y*dx, y*dy, dx^2, dx*dy, dy^2, x^3, x^2*y, x^2*dx, x^2*dy, x*y^2] + sage: dx, dy = W.differentials() + sage: (dx*x).monomials() + [1, x*dx] + sage: B[(x*y).support()[0]] + x*y + sage: sorted((dx*x).monomial_coefficients().items()) + [(((0, 0), (0, 0)), 1), (((1, 0), (1, 0)), 1)] """ n = self._n - # TODO in #17927: use IntegerVectors(length=2*n) - from sage.combinat.integer_list import IntegerListsNN - I = IntegerListsNN(length=n*2) + from sage.combinat.integer_lists.nn import IntegerListsNN + from sage.categories.cartesian_product import cartesian_product + elt_map = lambda u : (tuple(u[:n]), tuple(u[n:])) + I = IntegerListsNN(length=2*n, element_constructor=elt_map) one = self.base_ring().one() - f = lambda x: self.element_class(self, {(tuple(x[:n]),tuple(x[n:])): one}) + f = lambda x: self.element_class(self, {(x[0], x[1]): one}) return Family(I, f, name="basis map") @cached_method diff --git a/src/sage/all.py b/src/sage/all.py index 2158832213a..3ddfe089bff 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -106,6 +106,7 @@ from sage.monoids.all import * from sage.algebras.all import * from sage.modular.all import * +from sage.sat.all import * from sage.schemes.all import * from sage.graphs.all import * from sage.groups.all import * @@ -176,8 +177,8 @@ lazy_import('sagenb.notebook.notebook_object', 'notebook') lazy_import('sagenb.notebook.notebook_object', 'inotebook') lazy_import('sagenb.notebook.sage_email', 'email') -lazy_import('sagenb.notebook.interact', 'interact') lazy_import('sage.interacts', 'all', 'interacts') +lazy_import('sage.interacts.decorator', 'interact') from sage.interacts.debugger import debug from copy import copy, deepcopy diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index 66654ae4a2d..c895e9cb135 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -889,16 +889,16 @@ def minpoly(ex, var='x', algorithm=None, bits=None, degree=None, epsilon=0): sage: f = a.minpoly(); f x^8 - 40*x^6 + 352*x^4 - 960*x^2 + 576 sage: f(a) - ((((sqrt(5) + sqrt(3) + sqrt(2))^2 - 40)*(sqrt(5) + sqrt(3) + sqrt(2))^2 + 352)*(sqrt(5) + sqrt(3) + sqrt(2))^2 - 960)*(sqrt(5) + sqrt(3) + sqrt(2))^2 + 576 + (sqrt(5) + sqrt(3) + sqrt(2))^8 - 40*(sqrt(5) + sqrt(3) + sqrt(2))^6 + 352*(sqrt(5) + sqrt(3) + sqrt(2))^4 - 960*(sqrt(5) + sqrt(3) + sqrt(2))^2 + 576 sage: f(a).expand() 0 :: - sage: a = sin(pi/5) - sage: f(x) = a.minpoly(algorithm='numerical'); f - x |--> 1/4*(4*x^2 - 5)*x^2 + 5/16 - sage: f(a).numerical_approx(100) + sage: a = sin(pi/7) + sage: f = a.minpoly(algorithm='numerical'); f + x^6 - 7/4*x^4 + 7/8*x^2 - 7/64 + sage: f(a).horner(a).numerical_approx(100) 0.00000000000000000000000000000 The degree must be high enough (default tops out at 24). diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index a56568ce72e..db9fd8f5f8d 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -1116,7 +1116,7 @@ def desolve_rk4(de, dvar, ics=None, ivar=None, end_points=None, step=0.1, output - ``de`` - equation, including term with ``diff(y,x)`` - - ``dvar``` - dependent variable (declared as funciton of independent variable) + - ``dvar`` - dependent variable (declared as function of independent variable) - Other parameters diff --git a/src/sage/calculus/wester.py b/src/sage/calculus/wester.py index 833fcbe9a44..b2519933cbb 100644 --- a/src/sage/calculus/wester.py +++ b/src/sage/calculus/wester.py @@ -617,9 +617,8 @@ sage: # (YES) Convert the above to Horner's form. sage: # Verify(Horner(p, x), ((((a[5]*x+a[4])*x sage: # +a[3])*x+a[2])*x+a[1])*x); - sage: # We use the trick of evaluating the algebraic poly at a symbolic variable: sage: restore('x') - sage: p(x) + sage: SR(p).horner(x) ((((a4*x + a3)*x + a2)*x + a1)*x + a0)*x :: diff --git a/src/sage/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index e84ede20193..3f990e84a0a 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -711,9 +711,11 @@ def _test_zero(self, **options): tester.assert_(self.is_parent_of(zero)) for x in tester.some_elements(): tester.assert_(x + zero == x) - # Check that zero is immutable by asking its hash: - tester.assertEqual(type(zero.__hash__()), int) - tester.assertEqual(zero.__hash__(), zero.__hash__()) + # Check that zero is immutable if it looks like we can: + if hasattr(zero,"is_immutable"): + tester.assertEqual(zero.is_immutable(),True) + if hasattr(zero,"is_mutable"): + tester.assertEqual(zero.is_mutable(),False) # Check that bool behave consistently on zero tester.assertFalse(bool(self.zero())) diff --git a/src/sage/categories/additive_monoids.py b/src/sage/categories/additive_monoids.py index ff9e14feab4..475dcabe622 100644 --- a/src/sage/categories/additive_monoids.py +++ b/src/sage/categories/additive_monoids.py @@ -13,11 +13,12 @@ from sage.categories.additive_semigroups import AdditiveSemigroups from sage.categories.homsets import HomsetsCategory + class AdditiveMonoids(CategoryWithAxiom_singleton): """ The category of additive monoids. - An *additive monoid* is a unital class:`additive semigroup + An *additive monoid* is a unital :class:`additive semigroup `, that is a set endowed with a binary operation `+` which is associative and admits a zero (see :wikipedia:`Monoid`). diff --git a/src/sage/categories/additive_semigroups.py b/src/sage/categories/additive_semigroups.py index 7f9c2de9b5c..bb45bd19e8b 100644 --- a/src/sage/categories/additive_semigroups.py +++ b/src/sage/categories/additive_semigroups.py @@ -80,8 +80,8 @@ def _test_additive_associativity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(S, 3, tester._max_runs): tester.assert_((x + y) + z == x + (y + z)) class Homsets(HomsetsCategory): diff --git a/src/sage/categories/algebras.py b/src/sage/categories/algebras.py index 5e81cee137a..8e1c4f9a1f2 100644 --- a/src/sage/categories/algebras.py +++ b/src/sage/categories/algebras.py @@ -109,7 +109,9 @@ def Semisimple(self): return self & SemisimpleAlgebras(self.base_ring()) Commutative = LazyImport('sage.categories.commutative_algebras', 'CommutativeAlgebras', at_startup=True) + Filtered = LazyImport('sage.categories.filtered_algebras', 'FilteredAlgebras') Graded = LazyImport('sage.categories.graded_algebras', 'GradedAlgebras') + Super = LazyImport('sage.categories.super_algebras', 'SuperAlgebras') WithBasis = LazyImport('sage.categories.algebras_with_basis', 'AlgebrasWithBasis') #if/when Semisimple becomes an axiom Semisimple = LazyImport('sage.categories.semisimple_algebras', 'SemisimpleAlgebras') diff --git a/src/sage/categories/algebras_with_basis.py b/src/sage/categories/algebras_with_basis.py index d4b63827895..ee0ad3865ae 100644 --- a/src/sage/categories/algebras_with_basis.py +++ b/src/sage/categories/algebras_with_basis.py @@ -117,8 +117,10 @@ def example(self, alphabet = ('a','b','c')): from sage.categories.examples.algebras_with_basis import Example return Example(self.base_ring(), alphabet) + Filtered = LazyImport('sage.categories.filtered_algebras_with_basis', 'FilteredAlgebrasWithBasis') FiniteDimensional = LazyImport('sage.categories.finite_dimensional_algebras_with_basis', 'FiniteDimensionalAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_algebras_with_basis', 'GradedAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_algebras_with_basis', 'SuperAlgebrasWithBasis') class ParentMethods: @@ -198,7 +200,7 @@ class ElementMethods: def __invert__(self): """ - Returns the inverse of self if self is a multiple of one, + Return the inverse of ``self`` if ``self`` is a multiple of one, and one is in the basis of this algebra. Otherwise throws an error. @@ -207,6 +209,14 @@ def __invert__(self): inversed this way. It is correct though for graded connected algebras with basis. + .. WARNING:: + + This might produce a result which does not belong to + the parent of ``self``, yet believes to do so. For + instance, inverting 2 times the unity will produce 1/2 + times the unity, even if 1/2 is not in the base ring. + Handle with care. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ).example() @@ -222,17 +232,18 @@ def __invert__(self): ValueError: cannot invert self (= B[word: a]) """ # FIXME: make this generic - mcs = self._monomial_coefficients + mcs = self.monomial_coefficients(copy=False) one = self.parent().one_basis() if len(mcs) == 1 and one in mcs: - return self.parent()( ~mcs[ one ] ) + return self.parent().term(one, ~mcs[one]) else: raise ValueError("cannot invert self (= %s)"%self) class CartesianProducts(CartesianProductsCategory): """ - The category of algebras with basis, constructed as cartesian products of algebras with basis + The category of algebras with basis, constructed as cartesian + products of algebras with basis. Note: this construction give the direct products of algebras with basis. See comment in :class:`Algebras.CartesianProducts diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index cae2f9dec34..279174d751a 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -2,10 +2,11 @@ from category_types import( Elements, - SimplicialComplexes, ChainComplexes, ) +from sage.categories.simplicial_complexes import SimplicialComplexes + from tensor import tensor from cartesian_product import cartesian_product diff --git a/src/sage/categories/bialgebras.py b/src/sage/categories/bialgebras.py index e78f080ef18..fd7ff083689 100644 --- a/src/sage/categories/bialgebras.py +++ b/src/sage/categories/bialgebras.py @@ -11,6 +11,8 @@ from sage.categories.category_types import Category_over_base_ring from sage.categories.all import Algebras, Coalgebras +from sage.categories.super_modules import SuperModulesCategory +from sage.misc.lazy_import import LazyImport class Bialgebras(Category_over_base_ring): """ @@ -56,8 +58,8 @@ def additional_structure(self): """ return None - class ParentMethods: + class Super(SuperModulesCategory): pass - class ElementMethods: - pass + WithBasis = LazyImport('sage.categories.bialgebras_with_basis', 'BialgebrasWithBasis') + diff --git a/src/sage/categories/bialgebras_with_basis.py b/src/sage/categories/bialgebras_with_basis.py index 0487c0db3a3..d0fd23e870f 100644 --- a/src/sage/categories/bialgebras_with_basis.py +++ b/src/sage/categories/bialgebras_with_basis.py @@ -9,8 +9,12 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -def BialgebrasWithBasis(base_ring): - """ +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.categories.tensor import tensor + +class BialgebrasWithBasis(CategoryWithAxiom_over_base_ring): + r""" The category of bialgebras with a distinguished basis. EXAMPLES:: @@ -27,6 +31,378 @@ def BialgebrasWithBasis(base_ring): sage: TestSuite(BialgebrasWithBasis(ZZ)).run() """ - from sage.categories.all import Bialgebras - return Bialgebras(base_ring).WithBasis() + class ParentMethods: + + def convolution_product(self, *maps): + r""" + Return the convolution product (a map) of the given maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self``; or a single ``list`` or ``tuple`` + of such maps + + OUTPUT: + + - the new map `f_1 * f_2 * \cdots * f_2` representing their + convolution product + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + AUTHORS: + + - Aaron Lauve - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We construct some maps: the identity, the antipode and + projection onto the homogeneous componente of degree 2:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: Proj2 = lambda x: x.parent().sum_of_terms([(m, c) for (m, c) in x if m.size() == 2]) + + Compute the convolution product of the identity with itself and + with the projection ``Proj2`` on the Hopf algebra of + non-commutative symmetric functions:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).ribbon() + sage: T = R.convolution_product([Id, Id]) + sage: [T(R(comp)) for comp in Compositions(3)] + [4*R[1, 1, 1] + R[1, 2] + R[2, 1], + 2*R[1, 1, 1] + 4*R[1, 2] + 2*R[2, 1] + 2*R[3], + 2*R[1, 1, 1] + 2*R[1, 2] + 4*R[2, 1] + 2*R[3], + R[1, 2] + R[2, 1] + 4*R[3]] + sage: T = R.convolution_product(Proj2, Id) + sage: [T(R([i])) for i in range(1, 5)] + [0, R[2], R[2, 1] + R[3], R[2, 2] + R[4]] + + Compute the convolution product of no maps on the Hopf algebra of + symmetric functions in non-commuting variables. This is the + composition of the counit with the unit:: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: T = m.convolution_product() + sage: [T(m(lam)) for lam in SetPartitions(0).list() + SetPartitions(2).list()] + [m{}, 0, 0] + + Compute the convolution product of the projection ``Proj2`` with + the identity on the Hopf algebra of symmetric functions in + non-commuting variables:: + + sage: T = m.convolution_product(Proj2, Id) + sage: [T(m(lam)) for lam in SetPartitions(3)] + [0, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + 3*m{{1}, {2}, {3}} + 3*m{{1}, {2, 3}} + 3*m{{1, 3}, {2}}] + + Compute the convolution product of the antipode with itself and the + identity map on group algebra of the symmetric group:: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G, QQ) + sage: x = QG.sum_of_terms([(p,p.number_of_peaks() + p.number_of_inversions()) for p in Permutations(3)]); x + 2*[1, 3, 2] + [2, 1, 3] + 3*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: T = QG.convolution_product(Antipode, Antipode, Id) + sage: T(x) + 2*[1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 3*[3, 1, 2] + 3*[3, 2, 1] + """ + onbasis = lambda x: self.term(x).convolution_product(*maps) + return self.module_morphism(on_basis=onbasis, codomain=self) + + class ElementMethods: + + def adams_operator(self, n): + r""" + Compute the `n`-th convolution power of the identity morphism + `\mathrm{Id}` on ``self``. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: + + - the image of ``self`` under the convolution power `\mathrm{Id}^{*n}` + + .. NOTE:: + + In the literature, this is also called a Hopf power or + Sweedler power, cf. [AL2015]_. + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + REFERENCES: + + .. [AL2015] *The characteristic polynomial of the Adams operators + on graded connected Hopf algebras*. + Marcelo Aguiar and Aaron Lauve. + Algebra Number Theory, v.9, 2015, n.3, 2015. + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].adams_operator(2) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h[5].plethysm(2*h[1]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h([]).adams_operator(0) + h[] + sage: h([]).adams_operator(1) + h[] + sage: h[3,2].adams_operator(0) + 0 + sage: h[3,2].adams_operator(1) + h[3, 2] + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].adams_operator(5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].adams_operator(-2) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + """ + if n < 0: + if hasattr(self, 'antipode'): + T = lambda x: x.antipode() + n = abs(n) + else: + raise ValueError("antipode not defined; cannot take negative convolution powers: {} < 0".format(n)) + else: + T = lambda x: x + return self.convolution_product([T] * n) + + def convolution_product(self, *maps): + r""" + Return the image of ``self`` under the convolution product (map) of + the maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self.parent()``; or a single ``list`` or + ``tuple`` of such maps + + OUTPUT: + + - the convolution product of ``maps`` applied to ``self`` + + REFERENCES: + + .. [KMN2012] On the trace of the antipode and higher indicators. + Yevgenia Kashina and Susan Montgomery and Richard Ng. + Israel J. Math., v.188, 2012. + + .. [Sw1969] Hopf algebras. + Moss Sweedler. + W.A. Benjamin, Math Lec Note Ser., 1969. + + AUTHORS: + + - Amy Pang - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We compute convolution products of the identity and antipode maps + on Schur functions:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: s = SymmetricFunctions(QQ).schur() + sage: s[3].convolution_product(Id, Id) + 2*s[2, 1] + 4*s[3] + sage: s[3,2].convolution_product(Id) == s[3,2] + True + + The method accepts multiple arguments, or a single argument + consisting of a list of maps:: + + sage: s[3,2].convolution_product(Id, Id) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + sage: s[3,2].convolution_product([Id, Id]) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + + We test the defining property of the antipode morphism; namely, + that the antipode is the inverse of the identity map in the + convolution algebra whose identity element is the composition of + the counit and unit:: + + sage: s[3,2].convolution_product() == s[3,2].convolution_product(Antipode, Id) == s[3,2].convolution_product(Id, Antipode) + True + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,1].convolution_product(Id, Id, Id) + 3*Psi[1, 2] + 6*Psi[2, 1] + sage: (Psi[5,1] - Psi[1,5]).convolution_product(Id, Id, Id) + -3*Psi[1, 5] + 3*Psi[5, 1] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Id, Id) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + sage: x.convolution_product(Id, Id, Id) + 4*[1, 2, 3] + [1, 3, 2] + [2, 1, 3] + 3*[3, 2, 1] + sage: x.convolution_product([Id]*6) + 9*[1, 2, 3] + + TESTS:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + + :: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].convolution_product([Id, Id]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h.one().convolution_product([Id, Antipode]) + h[] + sage: h[3,2].convolution_product([Id, Antipode]) + 0 + sage: h.one().convolution_product([Id, Antipode]) == h.one().convolution_product() + True + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].convolution_product([Id]*5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].convolution_product([Antipode, Antipode]) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + sage: m[[]].convolution_product([]) + m{} + sage: m[[1,3],[2]].convolution_product([]) + 0 + + :: + + sage: QS = SymmetricGroupAlgebra(QQ, 5) + sage: x = QS.sum_of_terms(zip(Permutations(5)[3:6],[1,2,3])); x + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + 3*[1, 2, 5, 4, 3] + sage: x.convolution_product([Antipode, Id]) + 6*[1, 2, 3, 4, 5] + sage: x.convolution_product(Id, Antipode, Antipode, Antipode) + 3*[1, 2, 3, 4, 5] + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Antipode, Id) + 9*[1, 2, 3] + sage: x.convolution_product([Id, Antipode, Antipode, Antipode]) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + + :: + + sage: s[3,2].counit().parent() == s[3,2].convolution_product().parent() + False + """ + # Be flexible on how the maps are entered: accept a list/tuple of + # maps as well as multiple arguments + if len(maps) == 1 and isinstance(maps[0], (list, tuple)): + T = tuple(maps[0]) + else: + T = maps + + H = self.parent() + + n = len(T) + if n == 0: + return H.one() * self.counit() + if n == 1: + return T[0](self) + + # We apply the maps T_i and products concurrently with coproducts, as this + # seems to be faster than applying a composition of maps, e.g., (H.nfold_product) * tensor(T) * (H.nfold_coproduct). + + out = tensor((H.one(),self)) + HH = tensor((H,H)) + + for mor in T[:-1]: + #ALGORITHM: + #`split_convolve` moves terms of the form x # y to x*Ti(y1) # y2 in Sweedler notation. + split_convolve = lambda (x,y): ( ((xy1,y2),c*d) + for ((y1,y2),d) in H.term(y).coproduct() + for (xy1,c) in H.term(x)*mor(H.term(y1)) ) + out = HH.module_morphism(on_basis=lambda t: HH.sum_of_terms(split_convolve(t)), codomain=HH)(out) + + #Apply final map `T_n` to last term, `y`, and multiply. + return HH.module_morphism(on_basis=lambda (x,y): H.term(x)*T[-1](H.term(y)), codomain=H)(out) diff --git a/src/sage/categories/bimodules.py b/src/sage/categories/bimodules.py index 7971135a4f3..f778479bbc8 100644 --- a/src/sage/categories/bimodules.py +++ b/src/sage/categories/bimodules.py @@ -70,15 +70,21 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Bimodules(QQ,ZZ)._make_named_class_key('parent_class') - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) + sage: Bimodules(Fields(), ZZ)._make_named_class_key('element_class') (Category of fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) sage: Bimodules(QQ, Rings())._make_named_class_key('element_class') - (Category of quotient fields, Category of rings) + (Join of Category of quotient fields and Category of metric spaces, + Category of rings) sage: Bimodules(Fields(), Rings())._make_named_class_key('element_class') (Category of fields, Category of rings) diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index 3e0065247a3..6cf5370c87c 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -12,9 +12,13 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.lazy_import import lazy_import from sage.categories.covariant_functorial_construction import CovariantFunctorialConstruction, CovariantConstructionCategory +from sage.categories.pushout import MultivariateConstructionFunctor -class CartesianProductFunctor(CovariantFunctorialConstruction): +native_python_containers = set([tuple, list, set, frozenset]) + +class CartesianProductFunctor(CovariantFunctorialConstruction, MultivariateConstructionFunctor): """ A singleton class for the Cartesian product functor. @@ -105,17 +109,69 @@ class CartesianProductFunctor(CovariantFunctorialConstruction): _functor_category = "CartesianProducts" symbol = " (+) " -cartesian_product = CartesianProductFunctor() -""" -The cartesian product functorial construction. + def __init__(self): + r""" + Constructor. See :class:`CartesianProductFunctor` for details. -See :class:`CartesianProductFunctor` for more information. + TESTS:: -EXAMPLES:: + sage: from sage.categories.cartesian_product import CartesianProductFunctor + sage: CartesianProductFunctor() + The cartesian_product functorial construction + """ + CovariantFunctorialConstruction.__init__(self) + from sage.categories.sets_cat import Sets + MultivariateConstructionFunctor.__init__(self, Sets(), Sets()) - sage: cartesian_product - The cartesian_product functorial construction -""" + def __call__(self, args, **kwds): + r""" + Functorial construction application. + + This specializes the generic ``__call__`` from + :class:`CovariantFunctorialConstruction` to: + + - handle the following plain Python containers as input: + :class:`frozenset`, :class:`list`, :class:`set` and + :class:`tuple`. + + - handle the empty list of factors. + + See the examples below. + + EXAMPLES:: + + sage: cartesian_product([[0,1], ('a','b','c')]) + The cartesian product of ({0, 1}, {'a', 'b', 'c'}) + sage: _.category() + Category of Cartesian products of finite enumerated sets + + sage: cartesian_product([set([0,1,2]), [0,1]]) + The cartesian product of ({0, 1, 2}, {0, 1}) + sage: _.category() + Category of Cartesian products of sets + + Check that the empty product is handled correctly: + + sage: C = cartesian_product([]) + sage: C + The cartesian product of () + sage: C.cardinality() + 1 + sage: C.an_element() + () + sage: C.category() + Category of Cartesian products of sets + """ + if any(type(arg) in native_python_containers for arg in args): + from sage.categories.sets_cat import Sets + S = Sets() + args = [S(a, enumerated_set=True) for a in args] + elif not args: + from sage.categories.sets_cat import Sets + from sage.sets.cartesian_product import CartesianProduct + return CartesianProduct((), Sets().CartesianProducts()) + + return super(CartesianProductFunctor, self).__call__(args, **kwds) class CartesianProductsCategory(CovariantConstructionCategory): """ @@ -171,3 +227,17 @@ def base_ring(self): Integer Ring """ return self.base_category().base_ring() + +# Moved to avoid circular imports +lazy_import('sage.categories.sets_cat', 'cartesian_product') +""" +The cartesian product functorial construction + +See :class:`CartesianProductFunctor` for more information + +EXAMPLES:: + + sage: cartesian_product + The cartesian_product functorial construction +""" + diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index a43973e47f9..2d50eac827c 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -35,7 +35,7 @@ sage: V = VectorSpace(RationalField(), 3) sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces) sage: G = SymmetricGroup(9) sage: G.category() Join of Category of finite permutation groups and Category of finite weyl groups @@ -323,7 +323,8 @@ class inheritance from ``C.parent_class``. sage: Algebras(GF(5)).parent_class is Algebras(GF(7)).parent_class True - sage: Coalgebras(QQ).parent_class is Coalgebras(FractionField(QQ['x'])).parent_class + sage: F = FractionField(ZZ['t']) + sage: Coalgebras(F).parent_class is Coalgebras(FractionField(F['x'])).parent_class True We now construct a parent in the usual way:: @@ -2755,9 +2756,9 @@ def _make_named_class(self, name, method_provider, cache = False, **options): Similarly for ``QQ`` and ``RR``:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: RR.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: Modules(QQ).parent_class is Modules(RR).parent_class False @@ -2818,14 +2819,17 @@ def _make_named_class_key(self, name): sage: Algebras(ZZ)._make_named_class_key("parent_class") Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces The morphism class of a bimodule depends only on the category of the left and right base rings:: sage: Bimodules(QQ, ZZ)._make_named_class_key("morphism_class") - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) The element class of a join category depends only on the element class of its super categories:: @@ -2953,13 +2957,14 @@ def _make_named_class_key(self, name): sage: Modules(ZZ)._make_named_class_key('element_class') Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces """ return tuple(getattr(cat, name) for cat in self._super_categories) @@ -3005,7 +3010,8 @@ def _subcategory_hook_(self, category): EXAMPLE:: - sage: QQ['x'].category().is_subcategory(Category.join([Rings(), VectorSpaces(QuotientFields())])) # indirect doctest + sage: cat = Category.join([Rings(), VectorSpaces(QuotientFields().Metric())]) + sage: QQ['x'].category().is_subcategory(cat) # indirect doctest True """ return all(category.is_subcategory(X) for X in self._super_categories) diff --git a/src/sage/categories/category_cy_helper.pyx b/src/sage/categories/category_cy_helper.pyx index 2156bf3169b..213034df6fc 100644 --- a/src/sage/categories/category_cy_helper.pyx +++ b/src/sage/categories/category_cy_helper.pyx @@ -135,17 +135,16 @@ cpdef tuple join_as_tuple(tuple categories, tuple axioms, tuple ignore_axioms): (Category of algebras over Integer Ring, Category of finite monoids, Category of coalgebras over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,('WithBasis',),()) (Category of algebras with basis over Integer Ring, Category of finite monoids, Category of coalgebras with basis over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,(),((Monoids(),'Finite'),)) (Category of algebras over Integer Ring, Category of coalgebras over Rational Field, - Category of finite sets, - Category of simplicial complexes) + Category of finite simplicial complexes) """ cdef set axiomsS = set(axioms) for category in categories: diff --git a/src/sage/categories/category_singleton.pyx b/src/sage/categories/category_singleton.pyx index 29a9fb4f521..5da62ca3b07 100644 --- a/src/sage/categories/category_singleton.pyx +++ b/src/sage/categories/category_singleton.pyx @@ -94,7 +94,7 @@ class Category_singleton(Category): This is a subclass of :class:`Category`, with a couple optimizations for singleton categories. - The main purpose is to make the idioms: + The main purpose is to make the idioms:: sage: QQ in Fields() True @@ -287,7 +287,7 @@ class Category_singleton(Category): sage: MyRingsSingleton() Category of my rings singleton - Instanciating :class:`Category_singleton` triggers an assertion error:: + Instantiating :class:`Category_singleton` triggers an assertion error:: sage: Category_singleton() Traceback (most recent call last): @@ -319,7 +319,7 @@ class Category_singleton(Category): # TODO: find a better way to check that cls is an abstract class from sage.categories.category_with_axiom import CategoryWithAxiom_singleton assert (cls.__mro__[1] is Category_singleton or cls.__mro__[1] is CategoryWithAxiom_singleton), \ - "%s is not a direct subclass of %s"%(cls, Category_singleton) + "{} is not a direct subclass of {}".format(cls, Category_singleton) obj = super(Category_singleton, cls).__classcall__(cls, *args) cls._set_classcall(ConstantFunction(obj)) obj.__class__._set_classcall(ConstantFunction(obj)) diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index 435e6a5f980..3241fab5b10 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -193,13 +193,15 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ)._make_named_class_key('element_class') - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Algebras(Fields())._make_named_class_key('morphism_class') Category of fields """ @@ -513,34 +515,7 @@ def __call__(self, v): return v return self.ring().ideal(v) -############################################################# -# TODO: make those two into real categories (with super_category, ...) - -# SimplicialComplex -############################################################# -class SimplicialComplexes(Category): - """ - The category of simplicial complexes. - - EXAMPLES:: - - sage: SimplicialComplexes() - Category of simplicial complexes - - TESTS:: - - sage: TestSuite(SimplicialComplexes()).run() - """ - - def super_categories(self): - """ - EXAMPLES:: - - sage: SimplicialComplexes().super_categories() - [Category of objects] - """ - return [Objects()] # anything better? - +# TODO: make this into a better category ############################################################# # ChainComplex ############################################################# @@ -566,11 +541,11 @@ def super_categories(self): EXAMPLES:: sage: ChainComplexes(Integers(9)).super_categories() - [Category of modules with basis over Ring of integers modulo 9] + [Category of modules over Ring of integers modulo 9] """ - from sage.categories.all import Fields, FreeModules, VectorSpaces + from sage.categories.all import Fields, Modules, VectorSpaces base_ring = self.base_ring() if base_ring in Fields(): return [VectorSpaces(base_ring)] - return [FreeModules(base_ring)] + return [Modules(base_ring)] diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index f2356920f20..6fe7f5c42cf 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -1381,8 +1381,8 @@ class ``Sets.Finite``), or in a separate file (typically in a class .. _axioms-algorithmic: -Description of the algorithmic -============================== +Algorithms +========== Computing joins --------------- @@ -1673,8 +1673,11 @@ class ``Sets.Finite``), or in a separate file (typically in a class all_axioms = AxiomContainer() all_axioms += ("Flying", "Blue", + "Compact", + "Differentiable", "Smooth", "Analytic", "AlmostComplex", "FinitelyGeneratedAsMagma", "Facade", "Finite", "Infinite", + "Complete", "FiniteDimensional", "Connected", "WithBasis", "Irreducible", "Commutative", "Associative", "Inverse", "Unital", "Division", "NoZeroDivisors", @@ -2257,6 +2260,8 @@ def _repr_object_names_static(category, axioms): result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: result = result.replace("graded ", "graded connected ", 1) + elif axiom == "Connected" and "filtered " in result: + result = result.replace("filtered ", "filtered connected ", 1) elif axiom == "Endset" and "homsets" in result: # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) diff --git a/src/sage/categories/coalgebras.py b/src/sage/categories/coalgebras.py index 2bd98353394..a5fdc492111 100644 --- a/src/sage/categories/coalgebras.py +++ b/src/sage/categories/coalgebras.py @@ -13,6 +13,7 @@ from sage.categories.all import Modules from sage.categories.tensor import TensorProductsCategory, tensor from sage.categories.dual import DualObjectsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.categories.realizations import RealizationsCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.misc.abstract_method import abstract_method @@ -50,19 +51,6 @@ class ParentMethods: # # Will declare the coproduct of self to the coercion mechanism when it exists # pass - @cached_method - def tensor_square(self): - """ - Returns the tensor square of ``self`` - - EXAMPLES:: - - sage: A = HopfAlgebrasWithBasis(QQ).example() - sage: A.tensor_square() - An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field # An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field - """ - return tensor([self, self]) - @abstract_method def counit(self, x): """ @@ -192,6 +180,31 @@ def extra_super_categories(self): from sage.categories.algebras import Algebras return [Algebras(self.base_category().base_ring())] + class Super(SuperModulesCategory): + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Coalgebras(ZZ).Super().extra_super_categories() + [Join of Category of graded modules over Integer Ring + and Category of coalgebras over Integer Ring] + sage: Coalgebras(ZZ).Super().super_categories() + [Category of super modules over Integer Ring, + Category of coalgebras over Integer Ring] + + Compare this with the situation for bialgebras:: + + sage: Bialgebras(ZZ).Super().extra_super_categories() + [] + sage: Bialgebras(ZZ).Super().super_categories() + [Category of super algebras over Integer Ring, + Category of super coalgebras over Integer Ring] + + The category of bialgebras does not occur in these results, + since super bialgebras are not bialgebras. + """ + return [self.base_category().Graded()] + class WithRealizations(WithRealizationsCategory): class ParentMethods: @@ -274,7 +287,5 @@ def coproduct_by_coercion(self, x): sage: R[1].coproduct() R[] # R[1] + R[1] # R[] """ - from sage.categories.tensor import tensor R = self.realization_of().a_realization() - return self.tensor_square().sum(coeff * tensor([self(R[I]), self(R[J])]) - for ((I, J), coeff) in R(x).coproduct()) + return self.tensor_square()(R(x).coproduct()) diff --git a/src/sage/categories/coalgebras_with_basis.py b/src/sage/categories/coalgebras_with_basis.py index 0b9ff24f0ec..2ea398ecabc 100644 --- a/src/sage/categories/coalgebras_with_basis.py +++ b/src/sage/categories/coalgebras_with_basis.py @@ -13,6 +13,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.all import ModulesWithBasis, tensor, Hom +from sage.categories.super_modules import SuperModulesCategory class CoalgebrasWithBasis(CategoryWithAxiom_over_base_ring): """ @@ -127,5 +128,70 @@ def counit(self): return self.module_morphism(self.counit_on_basis,codomain=self.base_ring()) class ElementMethods: - pass + def coproduct_iterated(self, n=1): + r""" + Apply ``n`` coproducts to ``self``. + + .. TODO:: + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(2) + Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[2, 2] # Psi[] + 2*Psi[2] # Psi[] # Psi[2] + + 2*Psi[2] # Psi[2] # Psi[] + Psi[2, 2] # Psi[] # Psi[] + + TESTS:: + + sage: p = SymmetricFunctions(QQ).p() + sage: p[5,2,2].coproduct_iterated() + p[] # p[5, 2, 2] + 2*p[2] # p[5, 2] + p[2, 2] # p[5] + + p[5] # p[2, 2] + 2*p[5, 2] # p[2] + p[5, 2, 2] # p[] + sage: p([]).coproduct_iterated(3) + p[] # p[] # p[] # p[] + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(3) + Psi[] # Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[] # Psi[2, 2] # Psi[] + 2*Psi[] # Psi[2] # Psi[] # Psi[2] + + 2*Psi[] # Psi[2] # Psi[2] # Psi[] + Psi[] # Psi[2, 2] # Psi[] # Psi[] + + 2*Psi[2] # Psi[] # Psi[] # Psi[2] + 2*Psi[2] # Psi[] # Psi[2] # Psi[] + + 2*Psi[2] # Psi[2] # Psi[] # Psi[] + Psi[2, 2] # Psi[] # Psi[] # Psi[] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].coproduct_iterated(2) + m{} # m{} # m{{1, 3}, {2}} + m{} # m{{1}} # m{{1, 2}} + + m{} # m{{1, 2}} # m{{1}} + m{} # m{{1, 3}, {2}} # m{} + + m{{1}} # m{} # m{{1, 2}} + m{{1}} # m{{1, 2}} # m{} + + m{{1, 2}} # m{} # m{{1}} + m{{1, 2}} # m{{1}} # m{} + + m{{1, 3}, {2}} # m{} # m{} + sage: m[[]].coproduct_iterated(3), m[[1,3],[2]].coproduct_iterated(0) + (m{} # m{} # m{} # m{}, m{{1, 3}, {2}}) + """ + if n < 0: + raise ValueError("cannot take fewer than 0 coproduct iterations: %s < 0" % str(n)) + if n == 0: + return self + if n == 1: + return self.coproduct() + from sage.functions.all import floor, ceil + from sage.rings.all import Integer + + # Use coassociativity of `\Delta` to perform many coproducts simultaneously. + fn = floor(Integer(n-1)/2); cn = ceil(Integer(n-1)/2) + split = lambda a,b: tensor([a.coproduct_iterated(fn), b.coproduct_iterated(cn)]) + return self.coproduct().apply_multilinear_morphism(split) + + class Super(SuperModulesCategory): + pass diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 33fb4e36383..b41f3dfe5ab 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -12,6 +12,7 @@ #****************************************************************************** from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.cartesian_product import CartesianProductsCategory class CommutativeRings(CategoryWithAxiom): """ @@ -48,6 +49,15 @@ class ElementMethods: pass class Finite(CategoryWithAxiom): + r""" + Check that Sage knows that cartesian products of finite commutative + rings is a finite commutative ring. + + EXAMPLES:: + + sage: cartesian_product([Zmod(34), GF(5)]) in Rings().Commutative().Finite() + True + """ class ParentMethods: def cyclotomic_cosets(self, q, cosets=None): r""" @@ -134,8 +144,9 @@ def cyclotomic_cosets(self, q, cosets=None): Cyclotomic cosets of fields are useful in combinatorial design theory to provide so called difference families (see - :wikipedia:`Difference_set`). This is illustrated on the - following examples:: + :wikipedia:`Difference_set` and + :mod:`~sage.combinat.designs.difference_family`). This is + illustrated on the following examples:: sage: K = GF(5) sage: a = K.multiplicative_generator() @@ -150,6 +161,14 @@ def cyclotomic_cosets(self, q, cosets=None): [[1, 7, 9, 10, 12, 16, 26, 33, 34]] sage: sorted(x-y for D in H for x in D for y in D if x != y) [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, ..., 33, 34, 34, 35, 35, 36, 36] + + The method ``cyclotomic_cosets`` works on any finite commutative + ring:: + + sage: R = cartesian_product([GF(7), Zmod(14)]) + sage: a = R((3,5)) + sage: R.cyclotomic_cosets((3,5), [(1,1)]) + [[(1, 1), (3, 5), (2, 11), (6, 13), (4, 9), (5, 3)]] """ q = self(q) @@ -177,3 +196,18 @@ def cyclotomic_cosets(self, q, cosets=None): orbits.sort() return orbits + + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + r""" + Let Sage knows that cartesian products of commutative rings is a + commutative ring. + + EXAMPLES:: + + sage: CommutativeRings().Commutative().CartesianProducts().extra_super_categories() + [Category of commutative rings] + sage: cartesian_product([ZZ, Zmod(34), QQ, GF(5)]) in CommutativeRings() + True + """ + return [CommutativeRings()] diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index cc2ea36216e..47ed9d8e370 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -201,7 +201,7 @@ def _repr_(self): """ return "The %s functorial construction"%self._functor_name - def __call__(self, args): + def __call__(self, args, **kwargs): """ Functorial construction application @@ -220,7 +220,7 @@ def __call__(self, args): args = tuple(args) # a bit brute force; let's see if this becomes a bottleneck later assert(all( hasattr(arg, self._functor_name) for arg in args)) assert(len(args) > 0) - return getattr(args[0], self._functor_name)(*args[1:]) + return getattr(args[0], self._functor_name)(*args[1:], **kwargs) class FunctorialConstructionCategory(Category): # Should this be CategoryWithBase? """ diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 65d4b622394..a457d0aff48 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -1163,8 +1163,7 @@ def reduced_word_graph(self): if i == len(x): continue a, b = x[i], y[i] - I = P.index_set() - m = P.coxeter_matrix()[I.index(a),I.index(b)] + m = P.coxeter_matrix()[a,b] subword = [a,b] * (m // 2) subword2 = [b,a] * (m // 2) if m % 2 != 0: @@ -1203,10 +1202,13 @@ def length(self): sage: sum((x^w.length()) for w in W) - expand(prod(sum(x^i for i in range(j+1)) for j in range(4))) # This is scandalously slow!!! 0 - SEE ALSO: :meth:`.reduced_word` + .. SEEALSO:: + + :meth:`.reduced_word` - TODO: Should use reduced_word_iterator (or reverse_iterator) + .. TODO:: + Should use reduced_word_iterator (or reverse_iterator) """ return len(self.reduced_word()) @@ -1217,7 +1219,9 @@ def absolute_length(self): The absolute length is the length of the shortest expression of the element as a product of reflections. - .. SEEALSO:: :meth:`absolute_le`. + .. SEEALSO:: + + :meth:`absolute_le`. EXAMPLES:: diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 252f2e7bf84..ff3bb8a51e0 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -651,7 +651,7 @@ def crystal_morphism(self, on_gens, codomain=None, sage: psi(Bp.highest_weight_vector()) [[1, 1], [2]] - We can also use a dictonary to specify the generators and + We can also use a dictionary to specify the generators and their images:: sage: psi = Bp.crystal_morphism({Bp.lowest_weight_vectors()[0]: x}) diff --git a/src/sage/categories/cw_complexes.py b/src/sage/categories/cw_complexes.py new file mode 100644 index 00000000000..f7f4641d5cc --- /dev/null +++ b/src/sage/categories/cw_complexes.py @@ -0,0 +1,215 @@ +r""" +CW Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.sets_cat import Sets + +class CWComplexes(Category_singleton): + r""" + The category of CW complexes. + + A CW complex is a Closure-finite cell complex in the Weak toplogy. + + REFERENCES: + + - :wikipedia:`CW_complex` + + .. NOTE:: + + The notion of "finite" is that the number of cells is finite. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes(); C + Category of CW complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes() # indirect doctest + Category of CW complexes + """ + return "CW complexes" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Connected() + Category of connected CW complexes + + TESTS:: + + sage: TestSuite(CWComplexes().Connected()).run() + sage: CWComplexes().Connected.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional(); C + Category of finite dimensional CW complexes + + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional() + sage: TestSuite(C).run() + sage: CWComplexes().Connected().FiniteDimensional.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('FiniteDimensional') + + class Connected(CategoryWithAxiom): + """ + The category of connected CW complexes. + """ + + class FiniteDimensional(CategoryWithAxiom): + """ + Category of finite dimensional CW complexes. + """ + + class Finite(CategoryWithAxiom): + """ + Category of finite CW complexes. + + A finite CW complex is a CW complex with a finite number of cells. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A finite CW complex is a compact finite-dimensional CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().Finite() + sage: C.extra_super_categories() + [Category of finite dimensional CW complexes, + Category of compact topological spaces] + """ + return [CWComplexes().FiniteDimensional(), Sets().Topological().Compact()] + + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + C = self.cells() + return max(c.dimension() for d in C.keys() for c in C[d]) + + def Compact_extra_super_categories(self): + """ + Return extraneous super categories for ``CWComplexes().Compact()``. + + A compact CW complex is finite, see Proposition A.1 in [Hat]_. + + .. TODO:: + + Fix the name of finite CW complexes. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Compact() # indirect doctest + Category of finite finite dimensional CW complexes + sage: CWComplexes().Compact() is CWComplexes().Finite() + True + """ + return (Sets().Finite(),) + + class ElementMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element().dimension() + 2 + """ + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + + @abstract_method(optional=True) + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + diff --git a/src/sage/categories/distributive_magmas_and_additive_magmas.py b/src/sage/categories/distributive_magmas_and_additive_magmas.py index ebcb996bdaf..c0c3a53bb80 100644 --- a/src/sage/categories/distributive_magmas_and_additive_magmas.py +++ b/src/sage/categories/distributive_magmas_and_additive_magmas.py @@ -73,8 +73,8 @@ def _test_distributivity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(tester.some_elements(), 3, tester._max_runs): # left distributivity tester.assert_(x * (y + z) == (x * y) + (x * z)) # right distributivity diff --git a/src/sage/categories/domains.py b/src/sage/categories/domains.py index 2e9b9ec2db1..2cdbb4cd2ce 100644 --- a/src/sage/categories/domains.py +++ b/src/sage/categories/domains.py @@ -84,8 +84,8 @@ def _test_zero_divisors(self, **options): # Filter out zero S = [s for s in tester.some_elements() if not s.is_zero()] - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b tester.assertFalse(p.is_zero()) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 08ea8acc008..7b01e89d48a 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -23,7 +23,7 @@ class EnumeratedSets(Category_singleton): together with a canonical enumeration of its elements; conceptually, this is very similar to an immutable list. The main difference lies in the names and the return type of the methods, - and of course the fact that the list of element is not supposed to + and of course the fact that the list of elements is not supposed to be expanded in memory. Whenever possible one should use one of the two sub-categories :class:`FiniteEnumeratedSets` or :class:`InfiniteEnumeratedSets`. @@ -39,7 +39,7 @@ class EnumeratedSets(Category_singleton): - ``S.cardinality()``: the number of elements of the set. This is the equivalent for ``len`` on a list except that the return value is specified to be a Sage :class:`Integer` or - ``infinity``, instead of a Python ``int``; + ``infinity``, instead of a Python ``int``. - ``iter(S)``: an iterator for the elements of the set; @@ -48,15 +48,15 @@ class EnumeratedSets(Category_singleton): predictably too large to be expanded in memory. - ``S.unrank(n)``: the ``n-th`` element of the set when ``n`` is a sage - ``Integer``. This is the equivanlent for ``l[n]`` on a list. + ``Integer``. This is the equivalent for ``l[n]`` on a list. - ``S.rank(e)``: the position of the element ``e`` in the set; This is equivalent to ``l.index(e)`` for a list except that the return value is specified to be a Sage :class:`Integer`, - instead of a Python ``int``; + instead of a Python ``int``. - ``S.first()``: the first object of the set; it is equivalent to - ``S.unrank(0)``; + ``S.unrank(0)``. - ``S.next(e)``: the object of the set which follows ``e``; It is equivalent to ``S.unrank(S.rank(e)+1)``. @@ -148,12 +148,12 @@ def __iter__(self): An iterator for the enumerated set. ``iter(self)`` allows the combinatorial class to be treated as an - iterable. This if the default implementation from the category - ``EnumeratedSets()`` it just goes through the iterator of the set + iterable. This is the default implementation from the category + ``EnumeratedSets()``; it just goes through the iterator of the set to count the number of objects. By decreasing order of priority, the second column of the - following array shows which methods is used to define + following array shows which method is used to define ``__iter__``, when the methods of the first column are overloaded: +------------------------+---------------------------------+ @@ -166,53 +166,53 @@ def __iter__(self): | ``list` | ``_iterator_from_next`` | +------------------------+---------------------------------+ - If non of these are provided raise a ``NotImplementedError`` + If none of these are provided, raise a ``NotImplementedError``. EXAMPLES:: We start with an example where nothing is implemented:: sage: class broken(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: sage: it = iter(broken()); [next(it), next(it), next(it)] Traceback (most recent call last): ... NotImplementedError: iterator called but not implemented - Here is what happends when ``first`` and ``next`` are implemeted:: + Here is what happens when ``first`` and ``next`` are implemented:: sage: class set_first_next(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def first(self): - ... return 0 - ... def next(self, elt): - ... return elt+1 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def first(self): + ....: return 0 + ....: def next(self, elt): + ....: return elt+1 + ....: sage: it = iter(set_first_next()); [next(it), next(it), next(it)] [0, 1, 2] Let us try with ``unrank``:: sage: class set_unrank(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def unrank(self, i): - ... return i + 5 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def unrank(self, i): + ....: return i + 5 + ....: sage: it = iter(set_unrank()); [next(it), next(it), next(it)] [5, 6, 7] Let us finally try with ``list``:: sage: class set_list(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def list(self): - ... return [5, 6, 7] - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def list(self): + ....: return [5, 6, 7] + ....: sage: it = iter(set_list()); [next(it), next(it), next(it)] [5, 6, 7] @@ -257,7 +257,7 @@ def is_empty(self): def list(self): """ - Returns an error since the cardinality of self is not known. + Return an error since the cardinality of ``self`` is not known. EXAMPLES:: @@ -474,7 +474,7 @@ def _iterator_from_unrank(self): @cached_method def _an_element_from_iterator(self): """ - Returns the first element of ``self`` returned by :meth:`__iter__` + Return the first element of ``self`` returned by :meth:`__iter__` If ``self`` is empty, the exception :class:`~sage.categories.sets_cat.EmptySetError` is raised instead. @@ -511,7 +511,7 @@ def _an_element_from_iterator(self): #FIXME: use combinatorial_class_from_iterator once class_from_iterator.patch is in def _some_elements_from_iterator(self): """ - Returns some elements in ``self``. + Return some elements in ``self``. See :class:`TestSuite` for a typical use case. @@ -536,7 +536,7 @@ def _some_elements_from_iterator(self): def random_element(self): """ - Returns a random element in ``self``. + Return a random element in ``self``. Unless otherwise stated, and for finite enumerated sets, the probability is uniform. @@ -560,7 +560,7 @@ def random_element(self): def map(self, f, name=None): r""" - Returns the image `\{f(x) | x \in \text{self}\}` of this + Return the image `\{f(x) | x \in \text{self}\}` of this enumerated set by `f`, as an enumerated set. `f` is supposed to be injective. @@ -695,7 +695,7 @@ class ElementMethods: def rank(self): """ - Returns the rank of ``self`` in its parent. + Return the rank of ``self`` in its parent. See also :meth:`EnumeratedSets.ElementMethods.rank` @@ -715,98 +715,6 @@ def rank(self): class CartesianProducts(CartesianProductsCategory): class ParentMethods: - def __iter__(self): - r""" - Return a lexicographic iterator for the elements of this cartesian product. - - EXAMPLES:: - - sage: A = FiniteEnumeratedSets()(["a", "b"]) - sage: B = FiniteEnumeratedSets().example(); B - An example of a finite enumerated set: {1,2,3} - sage: C = cartesian_product([A, B, A]); C - The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) - sage: C in FiniteEnumeratedSets() - True - sage: list(C) - [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), - ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] - sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' - - sage: F22 = GF(2).cartesian_product(GF(2)) - sage: list(F22) - [(0, 0), (0, 1), (1, 0), (1, 1)] - - sage: C = cartesian_product([Permutations(10)]*4) - sage: it = iter(C) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) - - .. WARNING:: - - The elements are returned in lexicographic order, - which gives a valid enumeration only if all - factors, but possibly the first one, are - finite. So the following one is fine:: - - sage: it = iter(cartesian_product([ZZ, GF(2)])) - sage: [next(it) for _ in range(10)] - [(0, 0), (0, 1), (1, 0), (1, 1), - (-1, 0), (-1, 1), (2, 0), (2, 1), - (-2, 0), (-2, 1)] - - But this one is not:: - - sage: it = iter(cartesian_product([GF(2), ZZ])) - sage: [next(it) for _ in range(10)] - doctest:...: UserWarning: Sage is not able to determine - whether the factors of this cartesian product are - finite. The lexicographic ordering might not go through - all elements. - [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), - (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] - - .. NOTE:: - - Here it would be faster to use :func:`itertools.product` for sets - of small size. But the latter expands all factor in memory! - So we can not reasonably use it in general. - - ALGORITHM: - - Recipe 19.9 in the Python Cookbook by Alex Martelli - and David Ascher. - """ - if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): - from warnings import warn - warn("Sage is not able to determine whether the factors of " - "this cartesian product are finite. The lexicographic " - "ordering might not go through all elements.") - - # visualize an odometer, with "wheels" displaying "digits"...: - factors = list(self.cartesian_factors()) - wheels = map(iter, factors) - digits = [next(it) for it in wheels] - while True: - yield self._cartesian_product_of_elements(digits) - for i in range(len(digits)-1, -1, -1): - try: - digits[i] = next(wheels[i]) - break - except StopIteration: - wheels[i] = iter(factors[i]) - digits[i] = next(wheels[i]) - else: - break def first(self): r""" diff --git a/src/sage/categories/euclidean_domains.py b/src/sage/categories/euclidean_domains.py index dc7364599ad..b9d29a79a29 100644 --- a/src/sage/categories/euclidean_domains.py +++ b/src/sage/categories/euclidean_domains.py @@ -87,8 +87,8 @@ def _test_euclidean_degree(self, **options): tester.assertGreaterEqual(a.euclidean_degree(), min_degree) tester.assertEqual(a.euclidean_degree() == min_degree, a.is_unit()) - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b # For rings which are not exact, we might get something that # acts like a zero divisor. @@ -114,9 +114,8 @@ def _test_quo_rem(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): if b.is_zero(): tester.assertRaises(ZeroDivisionError, lambda: a.quo_rem(b)) else: diff --git a/src/sage/categories/examples/cw_complexes.py b/src/sage/categories/examples/cw_complexes.py new file mode 100644 index 00000000000..8c33b303e61 --- /dev/null +++ b/src/sage/categories/examples/cw_complexes.py @@ -0,0 +1,164 @@ +""" +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/ +#****************************************************************************** + +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. + + This class illustrates a minimal implementation of a CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example(); X + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + + sage: X.category() + Category of finite finite dimensional CW complexes + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(X).run() + """ + def __init__(self, bdy=(1, 2, 1, 2)): + r""" + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example((1, 2)); X + An example of a CW complex: the surface given by the boundary map (1, 2) + + TESTS:: + + sage: TestSuite(X).run() + """ + self._bdy = bdy + self._edges = frozenset(bdy) + Parent.__init__(self, category=CWComplexes().Finite()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().example() + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + """ + return "An example of a CW complex: the surface given by the boundary map {}".format(self._bdy) + + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + d = {0: (self.element_class(self, 0, 'v'),)} + d[1] = tuple([self.element_class(self, 0, 'e'+str(e)) for e in self._edges]) + d[2] = (self.an_element(),) + return Family(d) + + def an_element(self): + r""" + Return an element of the CW complex, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return self.element_class(self, 2, 'f') + + class Element(Element): + """ + A cell in a CW complex. + """ + def __init__(self, parent, dim, name): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: TestSuite(f).run() + """ + Element.__init__(self, parent) + self._dim = dim + self._name = name + + def _repr_(self): + """ + Return a string represention of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return "{}-cell {}".format(self._dim, self._name) + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f == X(2, 'f') + True + sage: e1 = X(1, 'e1') + sage: e1 == f + False + """ + return (isinstance(other, Surface.Element) + and self.parent() is other.parent() + and self._dim == other._dim + and self._name == other._name) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f.dimension() + 2 + """ + return self._dim + +Example = Surface + diff --git a/src/sage/categories/examples/filtered_algebras_with_basis.py b/src/sage/categories/examples/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..9e7765aefa8 --- /dev/null +++ b/src/sage/categories/examples/filtered_algebras_with_basis.py @@ -0,0 +1,178 @@ +r""" +Examples of filtered algebra with basis +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_algebras_with_basis import FilteredAlgebrasWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid +from sage.sets.family import Family + +class PBWBasisCrossProduct(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered algebra + with basis: the universal enveloping algebra of the Lie algebra + of `\RR^3` under the cross product. + + The Lie algebra is generated by `x,y,z` with brackets defined by + `[x, y] = z`, `[y, z] = x`, and `[x, z] = -y`. The universal enveloping + algebra has a (PBW) basis consisting of monomials `x^i y^j z^k`. + Despite these monomials not commuting with each other, we + nevertheless label them by the elements of the free abelian monoid + on three generators. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A set of algebra generators -- the set of generators `x,y,z`. + + - The index of the unit element -- the unit element in the monoid + of monomials. + + - A product -- this is given on basis elements by using + :meth:`product_on_basis`. + + - A degree function -- this is determined on the basis elements + by using :meth:`degree_on_basis` which returns the sum of exponents + of the monomial. + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: TestSuite(A).run(elements=[x*y+z]) + """ + I = IndexedFreeAbelianMonoid(['x', 'y', 'z'], prefix='U') + gen_cmp = lambda x,y: cmp((-len(x), x.to_word_list()), (-len(y), y.to_word_list())) + CombinatorialFreeModule.__init__(self, base_ring, I, bracket=False, prefix='', + generator_cmp=gen_cmp, + category=FilteredAlgebrasWithBasis(base_ring)) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered().example() + An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Rational Field + """ + return "An example of a filtered algebra with basis: the universal enveloping algebra of Lie algebra of RR^3 with cross product over {}".format(self.base_ring()) + + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: list(A.algebra_generators()) + [U['x'], U['y'], U['z']] + """ + G = self._indices.monoid_generators() + I = sorted(G.keys()) + return Family(I, lambda x: self.monomial(G[x])) + + def one_basis(self): + """ + Return the index of the unit of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: A.one_basis() + 1 + """ + return self._indices.one() + + def degree_on_basis(self, m): + """ + The degree of the basis element of ``self`` labelled by ``m``. + + INPUT: + + - ``m`` -- an element of the free abelian monoid + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x = A.algebra_generators()['x'] + sage: A.degree_on_basis((x^4).leading_support()) + 4 + sage: a = A.an_element(); a + U['x']^2*U['y']^2*U['z']^3 + sage: A.degree_on_basis(a.leading_support()) + 7 + """ + return len(m) + + # TODO: This is a general procedure of expanding multiplication defined + # on generators to arbitrary monomials and could likely be factored out + # and be useful elsewhere. + def product_on_basis(self, s, t): + """ + Return the product of two basis elements indexed by ``s`` and ``t``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: G = A.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: A.product_on_basis(x.leading_support(), y.leading_support()) + U['x']*U['y'] + sage: y*x + U['x']*U['y'] - U['z'] + sage: x*y*x + U['x']^2*U['y'] - U['x']*U['z'] + sage: z*y*x + U['x']*U['y']*U['z'] - U['x']^2 + U['y']^2 - U['z']^2 + """ + if len(s) == 0: + return self.monomial(t) + if len(t) == 0: + return self.monomial(s) + if s.trailing_support() <= t.leading_support(): + return self.monomial(s*t) + + if len(t) == 1: + if len(s) == 1: + # Do the product of the generators + a = s.leading_support() + b = t.leading_support() + cur = self.monomial(s*t) + if a <= b: + return cur + if a == 'z': + if b == 'y': + return cur - self.monomial(self._indices.gen('x')) + # b == 'x' + return cur + self.monomial(self._indices.gen('y')) + # a == 'y' and b == 'x' + return cur - self.monomial(self._indices.gen('z')) + + cur = self.monomial(t) + for a in reversed(s.to_word_list()): + cur = self.monomial(self._indices.gen(a)) * cur + return cur + + cur = self.monomial(s) + for a in t.to_word_list(): + cur = cur * self.monomial(self._indices.gen(a)) + return cur + +Example = PBWBasisCrossProduct + diff --git a/src/sage/categories/examples/filtered_modules_with_basis.py b/src/sage/categories/examples/filtered_modules_with_basis.py new file mode 100644 index 00000000000..9954d6a2ff5 --- /dev/null +++ b/src/sage/categories/examples/filtered_modules_with_basis.py @@ -0,0 +1,151 @@ +r""" +Examples of filtered modules with basis +""" +#***************************************************************************** +# Copyright (C) 2013 Frederic Chapoton +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.partition import Partitions + + +class FilteredPartitionModule(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered module + with basis: the free module on the set of all partitions. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A choice of how to represent elements. In this case, the basis + elements are partitions. The algebra is constructed as a + :class:`CombinatorialFreeModule + ` on the + set of partitions, so it inherits all of the methods for such + objects, and has operations like addition already defined. + + :: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + + - If the algebra is called ``A``, then its basis function is + stored as ``A.basis``. Thus the function can be used to + find a basis for the degree `d` piece: essentially, just call + ``A.basis(d)``. More precisely, call ``x`` for + each ``x`` in ``A.basis(d)``. + + :: + + sage: [m for m in A.basis(4)] + [P[4], P[3, 1], P[2, 2], P[2, 1, 1], P[1, 1, 1, 1]] + + - For dealing with basis elements: :meth:`degree_on_basis`, and + :meth:`_repr_term`. The first of these defines the degree of any + monomial, and then the :meth:`degree + ` method for elements -- + see the next item -- uses it to compute the degree for a linear + combination of monomials. The last of these determines the + print representation for monomials, which automatically produces + the print representation for general elements. + + :: + + sage: A.degree_on_basis(Partition([4,3])) + 7 + sage: A._repr_term(Partition([4,3])) + 'P[4, 3]' + + - There is a class for elements, which inherits from + :class:`CombinatorialFreeModuleElement + `. An + element is determined by a dictionary whose keys are partitions and whose + corresponding values are the coefficients. The class implements + two things: an :meth:`is_homogeneous + ` method and a + :meth:`degree ` method. + + :: + + sage: p = A.monomial(Partition([3,2,1])); p + P[3, 2, 1] + sage: p.is_homogeneous() + True + sage: p.degree() + 6 + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example(); A + An example of a filtered module with basis: the free module on partitions over Rational Field + sage: TestSuite(A).run() + """ + CombinatorialFreeModule.__init__(self, base_ring, Partitions(), + category=FilteredModulesWithBasis(base_ring)) + + # FIXME: this is currently required, because the implementation of ``basis`` + # in CombinatorialFreeModule overrides that of GradedModulesWithBasis + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] + + # This could be a default implementation + def degree_on_basis(self, t): + """ + The degree of the basis element indexed by the partition ``t`` + in this filtered module. + + INPUT: + + - ``t`` -- the index of an element of the basis of this module, + i.e. a partition + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + sage: type(A.degree_on_basis(Partition((1,1)))) + + """ + return t.size() + + def _repr_(self): + """ + Print representation of ``self``. + + EXAMPLES:: + + sage: ModulesWithBasis(QQ).Filtered().example() # indirect doctest + An example of a filtered module with basis: the free module on partitions over Rational Field + """ + return "An example of a filtered module with basis: the free module on partitions over %s" % self.base_ring() + + def _repr_term(self, t): + """ + Print representation for the basis element represented by the + partition ``t``. + + This governs the behavior of the print representation of all elements + of the algebra. + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A._repr_term(Partition((4,2,1))) + 'P[4, 2, 1]' + """ + return 'P' + t._repr_() + +Example = FilteredPartitionModule + diff --git a/src/sage/categories/examples/graded_connected_hopf_algebras_with_basis.py b/src/sage/categories/examples/graded_connected_hopf_algebras_with_basis.py new file mode 100644 index 00000000000..a663f8506e3 --- /dev/null +++ b/src/sage/categories/examples/graded_connected_hopf_algebras_with_basis.py @@ -0,0 +1,166 @@ +r""" +Examples of graded connected Hopf algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Jean-Baptiste Priez +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.graded_hopf_algebras_with_basis import GradedHopfAlgebrasWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.functions.other import binomial +from sage.misc.cachefunc import cached_method +from sage.sets.non_negative_integers import NonNegativeIntegers + + +class GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a graded Hopf algebra + with basis that has one primitive generator of degree 1 and basis + elements indexed by non-negative integers. + + This Hopf algebra example differs from what topologists refer to as + a graded Hopf algebra because the twist operation in the tensor rule + satisfies + + .. MATH:: + + (\mu \otimes \mu) \circ (id \otimes \tau \otimes id) \circ + (\Delta \otimes \Delta) = \Delta \circ \mu + + where `\tau(x\otimes y) = y\otimes x`. + + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: TestSuite(H).run() + + """ + CombinatorialFreeModule.__init__(self, base_ring, NonNegativeIntegers(), + category=GradedHopfAlgebrasWithBasis(base_ring).Connected()) + + + @cached_method + def one_basis(self): + """ + Returns 0, which index the unit of the Hopf algebra. + + OUTPUT: + + - the non-negative integer 0 + + EXAMPLES:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.one_basis() + 0 + sage: H.one() + P0 + + """ + return self.basis().keys()(0) + + def degree_on_basis(self, i): + """ + The degree of a non-negative integer is itself + + INPUT: + + - ``i`` -- a non-negative integer + + OUTPUT: + + - a non-negative integer + + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.degree_on_basis(45) + 45 + + """ + return i + + def _repr_(self): + """ + Representation of the graded connected Hopf algebra + + EXAMPLES:: + + sage: GradedHopfAlgebrasWithBasis(QQ).Connected().example() + An example of a graded connected Hopf algebra with basis over Rational Field + + """ + return "An example of a graded connected Hopf algebra with basis over %s" % self.base_ring() + + def _repr_term(self, i): + """ + Representation for the basis element indexed by the integer ``i``. + + EXAMPLES:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H._repr_term(45) + 'P45' + + """ + return 'P' + repr(i) + + def product_on_basis(self, i, j): + """ + The product of two basis elements. + + The product of elements of degree ``i`` and ``j`` is an element + of degree ``i+j``. + + INPUT: + + - ``i``, ``j`` -- non-negative integers + + OUPUT: + + - a basis element indexed by ``i+j`` + + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.monomial(4) * H.monomial(5) + P9 + + """ + return self.monomial(i+j) + + def coproduct_on_basis(self, i): + """ + The coproduct of a basis element. + + .. MATH:: + + \Delta(P_i) = \sum_{j=0}^i P_{i-j} \otimes P_j + + INPUT: + + - ``i`` -- a non-negative integer + + OUTPUT: + + - an element of the tensor square of ``self`` + + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.monomial(3).coproduct() + P0 # P3 + 3*P1 # P2 + 3*P2 # P1 + P3 # P0 + + """ + return self.sum_of_terms( + ((i-j, j), binomial(i, j)) + for j in range(i+1) + ) + +Example = GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator diff --git a/src/sage/categories/examples/graded_modules_with_basis.py b/src/sage/categories/examples/graded_modules_with_basis.py index ebd05587f55..e0f95880199 100644 --- a/src/sage/categories/examples/graded_modules_with_basis.py +++ b/src/sage/categories/examples/graded_modules_with_basis.py @@ -9,6 +9,7 @@ #***************************************************************************** from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.partition import Partitions @@ -20,7 +21,7 @@ class GradedPartitionModule(CombinatorialFreeModule): INPUT: - - ``R`` - base ring + - ``R`` -- base ring The implementation involves the following: @@ -106,7 +107,7 @@ def __init__(self, base_ring): # FIXME: this is currently required, because the implementation of ``basis`` # in CombinatorialFreeModule overrides that of GradedModulesWithBasis - basis = GradedModulesWithBasis.ParentMethods.__dict__['basis'] + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] # This could be a default implementation def degree_on_basis(self, t): diff --git a/src/sage/categories/examples/graphs.py b/src/sage/categories/examples/graphs.py new file mode 100644 index 00000000000..9c1e1eddb7e --- /dev/null +++ b/src/sage/categories/examples/graphs.py @@ -0,0 +1,122 @@ +""" +Examples of graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://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""" + An example of a graph: the cycle of length `n`. + + This class illustrates a minimal implementation of a graph. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(); C + An example of a graph: the 5-cycle + + sage: C.category() + Category of graphs + + We conclude by running systematic tests on this graph:: + + sage: TestSuite(C).run() + """ + def __init__(self, n=5): + r""" + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(6); C + An example of a graph: the 6-cycle + + TESTS:: + + sage: TestSuite(C).run() + """ + self._n = n + Parent.__init__(self, category=Graphs()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().example() + An example of a graph: the 5-cycle + """ + return "An example of a graph: the {}-cycle".format(self._n) + + def an_element(self): + r""" + Return an element of the graph, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.an_element() + 0 + """ + return self(0) + + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + return [self(i) for i in range(self._n)] + + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return [self( (i, (i+1) % self._n) ) for i in range(self._n)] + + class Element(ElementWrapper): + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: e = C.edges()[0] + sage: e.dimension() + 2 + sage: v = C.vertices()[0] + sage: v.dimension() + 1 + """ + if isinstance(self.value, tuple): + return 2 + return 1 + +Example = Cycle + diff --git a/src/sage/categories/examples/manifolds.py b/src/sage/categories/examples/manifolds.py new file mode 100644 index 00000000000..a71b7cf30e3 --- /dev/null +++ b/src/sage/categories/examples/manifolds.py @@ -0,0 +1,94 @@ +""" +Examples of manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://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""" + An example of a manifold: the `n`-dimensional plane. + + This class illustrates a minimal implementation of a manifold. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(); M + An example of a Rational Field manifold: the 3-dimensional plane + + sage: M.category() + Category of manifolds over Rational Field + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(M).run() + """ + + def __init__(self, n=3, base_ring=None): + r""" + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(6); M + An example of a Rational Field manifold: the 6-dimensional plane + + TESTS:: + + sage: TestSuite(M).run() + """ + self._n = n + Parent.__init__(self, base=base_ring, category=Manifolds(base_ring)) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(QQ).example() + An example of a Rational Field manifold: the 3-dimensional plane + """ + return "An example of a {} manifold: the {}-dimensional plane".format( + self.base_ring(), self._n) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.dimension() + 3 + """ + return self._n + + def an_element(self): + r""" + Return an element of the manifold, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.an_element() + (0, 0, 0) + """ + zero = self.base_ring().zero() + return self(tuple([zero]*self._n)) + + Element = ElementWrapper + +Example = Plane + diff --git a/src/sage/categories/fields.py b/src/sage/categories/fields.py index 95b83089269..9b941563492 100644 --- a/src/sage/categories/fields.py +++ b/src/sage/categories/fields.py @@ -91,17 +91,31 @@ def __contains__(self, x): sage: GR in Fields() True - The following tests against a memory leak fixed in :trac:`13370`:: + The following tests against a memory leak fixed in :trac:`13370`. In order + to prevent non-deterministic deallocation of fields that have been created + in other doctests, we introduced a strong reference to all previously created + uncollected objects in :trac:`19244`. :: sage: import gc sage: _ = gc.collect() - sage: n = len([X for X in gc.get_objects() if isinstance(X, sage.rings.finite_rings.integer_mod_ring.IntegerModRing_generic)]) + sage: permstore = [X for X in gc.get_objects() if isinstance(X, sage.rings.finite_rings.integer_mod_ring.IntegerModRing_generic)] + sage: n = len(permstore) sage: for i in prime_range(100): - ... R = ZZ.quotient(i) - ... t = R in Fields() + ....: R = ZZ.quotient(i) + ....: t = R in Fields() + + First, we show that there are now more quotient rings in cache than before:: + + sage: len([X for X in gc.get_objects() if isinstance(X, sage.rings.finite_rings.integer_mod_ring.IntegerModRing_generic)]) > n + True + + When we delete the last quotient ring created in the loop and then do a garbage + collection, all newly created rings vanish:: + + sage: del R sage: _ = gc.collect() sage: len([X for X in gc.get_objects() if isinstance(X, sage.rings.finite_rings.integer_mod_ring.IntegerModRing_generic)]) - n - 1 + 0 """ try: diff --git a/src/sage/categories/filtered_algebras.py b/src/sage/categories/filtered_algebras.py new file mode 100644 index 00000000000..321dd604ed3 --- /dev/null +++ b/src/sage/categories/filtered_algebras.py @@ -0,0 +1,62 @@ +r""" +Filtered Algebras +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebras(FilteredModulesCategory): + r""" + The category of filtered algebras. + + An algebra `A` over a commutative ring `R` is *filtered* if + `A` is endowed with a structure of a filtered `R`-module + (whose underlying `R`-module structure is identical with + that of the `R`-algebra `A`) such that the indexing set `I` + (typically `I = \NN`) is also an additive abelian monoid, + the unity `1` of `A` belongs to `F_0`, and we have + `F_i \cdot F_j \subseteq F_{i+j}` for all `i, j \in I`. + + EXAMPLES:: + + sage: Algebras(ZZ).Filtered() + Category of filtered algebras over Integer Ring + sage: Algebras(ZZ).Filtered().super_categories() + [Category of algebras over Integer Ring, + Category of filtered modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtered_algebra` + """ + class ParentMethods: + @abstract_method(optional=True) + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + .. TODO:: + + Implement a version of the associated graded algebra + which does not require ``self`` to have a + distinguished basis. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + diff --git a/src/sage/categories/filtered_algebras_with_basis.py b/src/sage/categories/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..053d119a040 --- /dev/null +++ b/src/sage/categories/filtered_algebras_with_basis.py @@ -0,0 +1,541 @@ +r""" +Filtered Algebras With Basis + +A filtered algebra with basis over a commutative ring `R` +is a filtered algebra over `R` endowed with the structure +of a filtered module with basis (with the same underlying +filtered-module structure). See +:class:`~sage.categories.filtered_algebras.FilteredAlgebras` and +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for these two notions. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebrasWithBasis(FilteredModulesCategory): + """ + The category of filtered algebras with a distinguished + homogeneous basis. + + A filtered algebra with basis over a commutative ring `R` + is a filtered algebra over `R` endowed with the structure + of a filtered module with basis (with the same underlying + filtered-module structure). See + :class:`~sage.categories.filtered_algebras.FilteredAlgebras` and + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` + for these two notions. + + EXAMPLES:: + + sage: C = AlgebrasWithBasis(ZZ).Filtered(); C + Category of filtered algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of algebras with basis over Integer Ring, + Category of filtered algebras over Integer Ring, + Category of filtered modules with basis over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered algebra ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded algebra -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But many instances of :class:`FilteredAlgebrasWithBasis` + override this method, as the associated graded algebra + often is (isomorphic) to a simpler object (for instance, + the associated graded algebra of a graded algebra can be + identified with the graded algebra itself). Generic code + that uses associated graded algebras (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion`, and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + .. TODO:: + + Maybe the thing about the conversion from ``self`` + to ``self.graded_algebra()`` on the Clifford at least + could be made to work? (I would still warn the user + against ASSUMING that it must work -- as there is + probably no way to guarantee it in all cases, and + we shouldn't require users to mess with + element constructors.) + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p) + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.projection(7)(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(8)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + algebras of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered algebras with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the universal enveloping algebra of the + Lie algebra `\RR^3` (with the cross product serving as + Lie bracket):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example(); A + An example of a filtered algebra with basis: the + universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + sage: M = A.indices(); M + Free abelian monoid indexed by {'x', 'y', 'z'} + sage: x,y,z = [A.basis()[M.gens()[i]] for i in "xyz"] + + Let us define a stupid filtered map from ``A`` to + itself:: + + sage: def map_on_basis(m): + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (y ** (i+j)) * (z ** k) + ....: if i > 0: + ....: g += i * (x ** (i-1)) * (y ** j) * (z ** k) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(x) + U['y'] + 1 + sage: f(x*y*z) + U['y']^2*U['z'] + U['y']*U['z'] + sage: f(x*x*y*z) + U['y']^3*U['z'] + 2*U['x']*U['y']*U['z'] + sage: f(A.one()) + 1 + sage: f(y*z) + U['y']*U['z'] + + (There is nothing here that is peculiar to this + universal enveloping algebra; we are only using its + module structure, and we could just as well be using + a polynomial algebra in its stead.) + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Algebra of An example of a filtered algebra with + basis: the universal enveloping algebra of Lie algebra + of RR^3 with cross product over Rational Field + sage: xx, yy, zz = [A.to_graded_conversion()(i) for i in [x, y, z]] + sage: xx+yy*zz + bar(U['y']*U['z']) + bar(U['x']) + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Algebra of An example + of a filtered algebra with basis: the universal + enveloping algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(xx) + bar(U['y']) + sage: grf(xx*yy*zz) + bar(U['y']^2*U['z']) + sage: grf(xx*xx*yy*zz) + bar(U['y']^3*U['z']) + sage: grf(grA.one()) + bar(1) + sage: grf(yy*zz) + bar(U['y']*U['z']) + sage: grf(yy*zz-2*yy) + bar(U['y']*U['z']) - 2*bar(U['y']) + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(m): # redefining map_on_basis + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (h[1] ** i) * (h[2] ** (floor(j/2))) * (h[3] ** (floor(k/3))) + ....: g += i * (h[1] ** (i+j+k)) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(x) + 2*h[1] + sage: f(y) + h[] + sage: f(z) + h[] + sage: f(y**2) + h[2] + sage: f(x**2) + 3*h[1, 1] + sage: f(x*y*z) + h[1] + h[1, 1, 1] + sage: f(x*x*y*y*z) + 2*h[1, 1, 1, 1, 1] + h[2, 1, 1] + sage: f(A.one()) + h[] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(xx) + 2*h[1] + sage: grf(yy) + 0 + sage: grf(zz) + 0 + sage: grf(yy**2) + h[2] + sage: grf(xx**2) + 3*h[1, 1] + sage: grf(xx*yy*zz) + h[1, 1, 1] + sage: grf(xx*xx*yy*yy*zz) + 2*h[1, 1, 1, 1, 1] + sage: grf(grA.one()) + h[] + + **Example 3.** + + After having had a graded algebra as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return x ** (sum(lam)) + y ** (len(lam)) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + U['x'] + U['y'] + sage: f(h[2]) + U['x']^2 + U['y'] + sage: f(h[1, 1]) + U['x']^2 + U['y']^2 + sage: f(h[2, 2]) + U['x']^4 + U['y']^2 + sage: f(h[3, 2, 1]) + U['x']^6 + U['y']^3 + sage: f(h.one()) + 2 + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(h[1]) + bar(U['x']) + bar(U['y']) + sage: grf(h[2]) + bar(U['x']^2) + sage: grf(h[1, 1]) + bar(U['x']^2) + bar(U['y']^2) + sage: grf(h[2, 2]) + bar(U['x']^4) + sage: grf(h[3, 2, 1]) + bar(U['x']^6) + sage: grf(h.one()) + 2*bar(1) + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded algebras. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h[lam] + h[len(lam)] + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + 2*h[1] + sage: f(h[2]) + h[1] + h[2] + sage: f(h[1, 1]) + h[1, 1] + h[2] + sage: f(h[2, 1]) + h[2] + h[2, 1] + sage: f(h.one()) + 2*h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + 2*h[1] + sage: grf(h[2]) + h[2] + sage: grf(h[1, 1]) + h[1, 1] + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + 2*h[] + + **Example 5.** + + For another example, let us compute `\operatorname{gr} f` for a + map `f` between two Clifford algebras:: + + sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) + sage: B = CliffordAlgebra(Q, names=['u','v']); B + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 1 2 ] + [ * 3 ] + sage: m = Matrix(ZZ, [[1, 2], [1, -1]]) + sage: f = B.lift_module_morphism(m, names=['x','y']) + sage: A = f.domain(); A + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 6 0 ] + [ * 3 ] + sage: x, y = A.gens() + sage: f(x) + u + v + sage: f(y) + 2*u - v + sage: f(x**2) + 6 + sage: f(x*y) + -3*u*v + 3 + sage: grA = A.graded_algebra(); grA + The exterior algebra of rank 2 over Integer Ring + sage: A.to_graded_conversion()(x) + x + sage: A.to_graded_conversion()(y) + y + sage: A.to_graded_conversion()(x*y) + x^y + sage: u = A.to_graded_conversion()(x*y+1); u + x^y + 1 + sage: A.from_graded_conversion()(u) + x*y + 1 + sage: A.projection(2)(x*y+1) + x^y + sage: A.projection(1)(x+2*y-2) + x + 2*y + sage: grf = A.induced_graded_map(B, f); grf + Generic morphism: + From: The exterior algebra of rank 2 over Integer Ring + To: The exterior algebra of rank 2 over Integer Ring + sage: grf(A.to_graded_conversion()(x)) + u + v + sage: grf(A.to_graded_conversion()(y)) + 2*u - v + sage: grf(A.to_graded_conversion()(x**2)) + 6 + sage: grf(A.to_graded_conversion()(x*y)) + -3*u^v + sage: grf(grA.one()) + 1 + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + pass + diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py new file mode 100644 index 00000000000..8a9a79d6c08 --- /dev/null +++ b/src/sage/categories/filtered_modules.py @@ -0,0 +1,161 @@ +r""" +Filtered Modules + +A *filtered module* over a ring `R` with a totally ordered +indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped +with a family `(F_i)_{i \in I}` of `R`-submodules satisfying +`F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and +`M = \bigcup_{i \in I} F_i`. This family is called a *filtration* +of the given module `M`. + +.. TODO:: + + Implement a notion for decreasing filtrations: where `F_j \subseteq F_i` + when `i \leq j`. + +.. TODO:: + + Implement filtrations for all concrete categories. + +.. TODO:: + + Implement `\operatorname{gr}` as a functor. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory + +class FilteredModulesCategory(RegressiveCovariantConstructionCategory, Category_over_base_ring): + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Filtered() + sage: C + Category of filtered algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of algebras over Rational Field, + Category of filtered modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + """ + super(FilteredModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Filtered" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered() # indirect doctest + Category of filtered algebras with basis over Rational Field + """ + return "filtered {}".format(self.base_category()._repr_object_names()) + +class FilteredModules(FilteredModulesCategory): + r""" + The category of filtered modules over a given ring `R`. + + A *filtered module* over a ring `R` with a totally ordered + indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped + with a family `(F_i)_{i \in I}` of `R`-submodules satisfying + `F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and + `M = \bigcup_{i \in I} F_i`. This family is called a *filtration* + of the given module `M`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + sage: Modules(ZZ).Filtered().super_categories() + [Category of modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtration_(mathematics)` + """ + def extra_super_categories(self): + r""" + Add :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Filtered().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Filtered().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Filtered()`` returns an + instance of :class:`FilteredModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Filtered()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class SubcategoryMethods: + + @cached_method + def Connected(self): + r""" + Return the full subcategory of the connected objects of ``self``. + + A filtered `R`-module `M` with filtration + `(F_0, F_1, F_2, \ldots)` (indexed by `\NN`) + is said to be *connected* if `F_0` is isomorphic + to `R`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered().Connected() + Category of filtered connected modules over Integer Ring + sage: Coalgebras(QQ).Filtered().Connected() + Join of Category of filtered connected modules over Rational Field + and Category of coalgebras over Rational Field + sage: AlgebrasWithBasis(QQ).Filtered().Connected() + Category of filtered connected algebras with basis over Rational Field + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered().Connected()).run() + sage: Coalgebras(QQ).Filtered().Connected.__module__ + 'sage.categories.filtered_modules' + """ + return self._with_axiom("Connected") + + class Connected(CategoryWithAxiom_over_base_ring): + pass + diff --git a/src/sage/categories/filtered_modules_with_basis.py b/src/sage/categories/filtered_modules_with_basis.py new file mode 100644 index 00000000000..7f2320f7f19 --- /dev/null +++ b/src/sage/categories/filtered_modules_with_basis.py @@ -0,0 +1,928 @@ +r""" +Filtered Modules With Basis + +A *filtered module with basis* over a ring `R` means +(for the purpose of this code) a filtered `R`-module `M` +with filtration `(F_i)_{i \in I}` (typically `I = \NN`) +endowed with a basis `(b_j)_{j \in J}` of `M` and a partition +`J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed +that some `J_i` are empty) such that for every `n \in I`, +the subfamily `(b_j)_{j \in U_n}`, where +`U_n = \bigcup_{i \leq n} J_i`, is a basis of the +`R`-submodule `F_n`. + +For every `i \in I`, the `R`-submodule of `M` spanned by +`(b_j)_{j \in J_i}` is called the `i`-*th graded component* +(aka the `i`-*th homogeneous component*) of the filtered +module with basis `M`; the elements of this submodule are +referred to as *homogeneous elements of degree* `i`. + +See the class documentation +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for further details. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory +from sage.misc.abstract_method import abstract_method + +class FilteredModulesWithBasis(FilteredModulesCategory): + r""" + The category of filtered modules with a distinguished basis. + + A *filtered module with basis* over a ring `R` means + (for the purpose of this code) a filtered `R`-module `M` + with filtration `(F_i)_{i \in I}` (typically `I = \NN`) + endowed with a basis `(b_j)_{j \in J}` of `M` and a partition + `J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed + that some `J_i` are empty) such that for every `n \in I`, + the subfamily `(b_j)_{j \in U_n}`, where + `U_n = \bigcup_{i \leq n} J_i`, is a basis of the + `R`-submodule `F_n`. + + For every `i \in I`, the `R`-submodule of `M` spanned by + `(b_j)_{j \in J_i}` is called the `i`-*th graded component* + (aka the `i`-*th homogeneous component*) of the filtered + module with basis `M`; the elements of this submodule are + referred to as *homogeneous elements of degree* `i`. + The `R`-module `M` is the direct sum of its `i`-th graded + components over all `i \in I`, and thus becomes a graded + `R`-module with basis. + Conversely, any graded `R`-module with basis canonically + becomes a filtered `R`-module with basis (by defining + `F_n = \bigoplus_{i \leq n} G_i` where `G_i` is the `i`-th + graded component, and defining `J_i` as the indexing set + of the basis of the `i`-th graded component). Hence, the + notion of a filtered `R`-module with basis is equivalent + to the notion of a graded `R`-module with basis. + + However, the *category* of filtered `R`-modules with basis is not + the category of graded `R`-modules with basis. Indeed, the *morphisms* + of filtered `R`-modules with basis are defined to be morphisms of + `R`-modules which send each `F_n` of the domain to the corresponding + `F_n` of the target; in contrast, the morphisms of graded `R`-modules + with basis must preserve each homogeneous component. Also, + the notion of a filtered algebra with basis differs from + that of a graded algebra with basis. + + .. NOTE:: + + Currently, to make use of the functionality of this class, + an instance of ``FilteredModulesWithBasis`` should fulfill + the contract of a :class:`CombinatorialFreeModule` (most + likely by inheriting from it). It should also have the + indexing set `J` encoded as its ``_indices`` attribute, + and ``_indices.subset(size=i)`` should yield the subset + `J_i` (as an iterable). If the latter conditions are not + satisfied, then :meth:`basis` must be overridden. + + .. NOTE:: + + One should implement a ``degree_on_basis`` method in the parent + class in order to fully utilize the methods of this category. + This might become a required abstract method in the future. + + EXAMPLES:: + + sage: C = ModulesWithBasis(ZZ).Filtered(); C + Category of filtered modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules over Integer Ring, + Category of modules with basis over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Filtered() + True + + TESTS:: + + sage: C = ModulesWithBasis(ZZ).Filtered() + sage: TestSuite(C).run() + sage: C = ModulesWithBasis(QQ).Filtered() + sage: TestSuite(C).run() + """ + class ParentMethods: + + # TODO: which syntax do we prefer? + # A.basis(degree = 3) + # A.basis().subset(degree=3) + + # This is related to the following design question: + # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` + # be the elements of F of degree 3 or those whose index is of degree 3? + + def basis(self, d=None): + r""" + Return the basis for (the ``d``-th homogeneous component + of) ``self``. + + INPUT: + + - ``d`` -- (optional, default ``None``) nonnegative integer + or ``None`` + + OUTPUT: + + If ``d`` is ``None``, returns the basis of the module. + Otherwise, returns the basis of the homogeneous component + of degree ``d`` (i.e., the subfamily of the basis of the + whole module which consists only of the basis vectors + lying in `F_d \setminus \bigcup_{i = ExteriorAlgebra(QQ) + sage: E.basis() + Lazy family (Term map from Subsets of {0, 1} to + The exterior algebra of rank 2 over Rational Field(i))_{i in + Subsets of {0, 1}} + """ + from sage.sets.family import Family + if d is None: + return Family(self._indices, self.monomial) + else: + return Family(self._indices.subset(size=d), self.monomial) + + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered module ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded module -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But some instances of :class:`FilteredModulesWithBasis` + override this method, as the associated graded module + often is (isomorphic) to a simpler object (for instance, + the associated graded module of a graded module can be + identified with the graded module itself). Generic code + that uses associated graded modules (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion` and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Module of An example of a filtered module with basis: + the free module on partitions over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Modules(ZZ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.projection(2)(p); q + -6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(3)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + modules of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered modules with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the free `\QQ`-module with basis the set of all + partitions:: + + sage: A = Modules(QQ).WithBasis().Filtered().example(); A + An example of a filtered module with basis: the free module + on partitions over Rational Field + sage: M = A.indices(); M + Partitions + sage: p1, p2, p21, p321 = [A.basis()[Partition(i)] for i in [[1], [2], [2,1], [3,2,1]]] + + Let us define a map from ``A`` to itself which acts on the + basis by sending every partition `\lambda` to the sum of + the conjugates of all partitions `\mu` for which + `\lambda / \mu` is a horizontal strip:: + + sage: def map_on_basis(lam): + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(p1) + P[] + P[1] + sage: f(p2) + P[] + P[1] + P[1, 1] + sage: f(p21) + P[1] + P[1, 1] + P[2] + P[2, 1] + sage: f(p21 - p1) + -P[] + P[1, 1] + P[2] + P[2, 1] + sage: f(p321) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Module of An example of a filtered module with basis: + the free module on partitions over Rational Field + sage: pp1, pp2, pp21, pp321 = [A.to_graded_conversion()(i) for i in [p1, p2, p21, p321]] + sage: pp2 + 4 * pp21 + Bbar[[2]] + 4*Bbar[[2, 1]] + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Module of An example of a + filtered module with basis: + the free module on partitions over Rational Field + sage: grf(pp1) + Bbar[[1]] + sage: grf(pp2 + 4 * pp21) + Bbar[[1, 1]] + 4*Bbar[[2, 1]] + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(p1) + h[] + h[1] + sage: f(p2) + h[] + h[1] + h[1, 1] + sage: f(A.zero()) + 0 + sage: f(p2 - 3*p1) + -2*h[] - 2*h[1] + h[1, 1] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(pp1) + h[1] + sage: grf(pp2) + h[1, 1] + sage: grf(pp321) + h[3, 2, 1] + sage: grf(pp2 - 3*pp1) + -3*h[1] + h[1, 1] + sage: grf(pp21) + h[2, 1] + sage: grf(grA.zero()) + 0 + + **Example 3.** + + After having had a graded module as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + P[] + P[1] + sage: f(h[2]) + P[] + P[1] + P[1, 1] + sage: f(h[1, 1]) + P[1] + P[2] + sage: f(h[2, 2]) + P[1, 1] + P[2, 1] + P[2, 2] + sage: f(h[3, 2, 1]) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + sage: f(h.one()) + P[] + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + sage: grf(h[1]) + Bbar[[1]] + sage: grf(h[2]) + Bbar[[1, 1]] + sage: grf(h[1, 1]) + Bbar[[2]] + sage: grf(h[2, 2]) + Bbar[[2, 2]] + sage: grf(h[3, 2, 1]) + Bbar[[3, 2, 1]] + sage: grf(h.one()) + Bbar[[]] + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded modules. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + h[] + h[1] + sage: f(h[2]) + h[] + h[1] + h[1, 1] + sage: f(h[1, 1]) + h[1] + h[2] + sage: f(h[2, 1]) + h[1] + h[1, 1] + h[2] + h[2, 1] + sage: f(h.one()) + h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + h[1] + sage: grf(h[2]) + h[1, 1] + sage: grf(h[1, 1]) + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + h[] + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + + def is_homogeneous(self): + r""" + Return whether the element ``self`` is homogeneous. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x=A(Partition((3,2,1))) + sage: y=A(Partition((4,4,1))) + sage: z=A(Partition((2,2,2))) + sage: (3*x).is_homogeneous() + True + sage: (x - y).is_homogeneous() + False + sage: (x+2*z).is_homogeneous() + True + + Here is an example with a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: (3*x).is_homogeneous() + True + sage: (x^3 - y^2).is_homogeneous() + True + sage: ((x + y)^2).is_homogeneous() + False + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis, or at least on a definition of homogeneous + components):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).is_homogeneous() + True + sage: (y*x).is_homogeneous() + False + sage: A.one().is_homogeneous() + True + sage: A.zero().is_homogeneous() + True + sage: (A.one()+x).is_homogeneous() + False + """ + degree_on_basis = self.parent().degree_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = degree_on_basis(m) + else: + if degree != degree_on_basis(m): + return False + return True + + @abstract_method(optional=True) + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m`` + in ``self``. + + EXAMPLES:: + + sage: A = GradedModulesWithBasis(QQ).example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + """ + + def homogeneous_degree(self): + r""" + The degree of a nonzero homogeneous element ``self`` in the + filtered module. + + .. NOTE:: + + This raises an error if the element is not homogeneous. + To compute the maximum of the degrees of the homogeneous + summands of a (not necessarily homogeneous) element, use + :meth:`maximal_degree` instead. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.degree() + 6 + sage: (x + 2*z).degree() + 6 + sage: (y - x).degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + An example in a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.homogeneous_degree() + 2 + sage: (x^3 + 4*y^2).homogeneous_degree() + 6 + sage: ((1 + x)^3).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).homogeneous_degree() + 2 + sage: (y*x).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + sage: A.one().homogeneous_degree() + 0 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent().degree_on_basis(self.leading_support()) + + # default choice for degree; will be overridden as necessary + degree = homogeneous_degree + + def maximal_degree(self): + """ + The maximum of the degrees of the homogeneous components + of ``self``. + + This is also the smallest `i` such that ``self`` belongs + to `F_i`. Hence, it does not depend on the basis of the + parent of ``self``. + + .. SEEALSO:: :meth:`homogeneous_degree` + + EXAMPLES: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.maximal_degree() + 6 + sage: (x + 2*z).maximal_degree() + 6 + sage: (y - x).maximal_degree() + 9 + sage: (3*z).maximal_degree() + 6 + + Now, we test this on a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.maximal_degree() + 2 + sage: (x^3 + 4*y^2).maximal_degree() + 6 + sage: ((1 + x)^3).maximal_degree() + 6 + + Let us now test a filtered algebra:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).maximal_degree() + 2 + sage: (y*x).maximal_degree() + 2 + sage: A.one().maximal_degree() + 0 + sage: A.zero().maximal_degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + sage: (A.one()+x).maximal_degree() + 1 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if self.is_zero(): + raise ValueError("the zero element does not have a well-defined degree") + degree_on_basis = self.parent().degree_on_basis + return max(degree_on_basis(m) for m in self.support()) + + def homogeneous_component(self, n): + """ + Return the homogeneous component of degree ``n`` of the + element ``self``. + + Let `m` be an element of a filtered `R`-module `M` with + basis. Then, `m` can be uniquely written in the form + `m = \sum_{i \in I} m_i`, where each `m_i` is a + homogeneous element of degree `i`. For `n \in I`, we + define the homogeneous component of degree `n` of the + element `m` to be `m_n`. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.homogeneous_component(-1) + 0 + sage: g.homogeneous_component(0) + 0 + sage: g.homogeneous_component(2) + -2*U['x']*U['y'] + sage: g.homogeneous_component(5) + 0 + sage: g.homogeneous_component(7) + U['x']^2*U['y']^2*U['z']^3 + sage: g.homogeneous_component(8) + 0 + + TESTS: + + Check that this really returns ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.homogeneous_component(3).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if degree_on_basis(i) == n) + + def truncate(self, n): + """ + Return the sum of the homogeneous components of degree + strictly less than ``n`` of ``self``. + + See :meth:`homogeneous_component` for the notion of a + homogeneous component. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.truncate(-1) + 0 + sage: g.truncate(0) + 0 + sage: g.truncate(2) + 0 + sage: g.truncate(3) + -2*U['x']*U['y'] + sage: g.truncate(5) + -2*U['x']*U['y'] + sage: g.truncate(7) + -2*U['x']*U['y'] + sage: g.truncate(8) + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.truncate(0).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) for (i, c) in self + if degree_on_basis(i) < n) + diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 862540e3bc3..b4378371526 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -948,6 +948,27 @@ def is_identity_decomposition_into_orthogonal_idempotents(self, l): and all(e*e == e for e in l) and all(e*f == 0 for e in l for f in l if f != e)) + @cached_method + def is_commutative(self): + """ + Return whether ``self`` is a commutative algebra. + + EXAMPLES:: + + sage: S4 = SymmetricGroupAlgebra(QQ, 4) + sage: S4.is_commutative() + False + sage: S2 = SymmetricGroupAlgebra(QQ, 2) + sage: S2.is_commutative() + True + """ + B = list(self.basis()) + try: # See if 1 is a basis element, if so, remove it + B.remove(self.one()) + except ValueError: + pass + return all(b*bp == bp*b for i,b in enumerate(B) for bp in B[i+1:]) + class ElementMethods: def to_matrix(self, base_ring=None, action=operator.mul, side='left'): diff --git a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py index f276371d25f..086892237e6 100644 --- a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def FiniteDimensionalBialgebrasWithBasis(base_ring): sage: C = FiniteDimensionalBialgebrasWithBasis(QQ); C Category of finite dimensional bialgebras with basis over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of finite dimensional algebras with basis over Rational Field] sage: C is Bialgebras(QQ).WithBasis().FiniteDimensional() True diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index 9a3264a4bc6..5dccfac10ee 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -265,7 +265,33 @@ def quotient_module(self, submodule, check=True, already_echelonized=False, cate return QuotientModuleWithBasis(submodule, category=category) class ElementMethods: - pass + def dense_coefficient_list(self, order=None): + """ + Return a list of *all* coefficients of ``self``. + + By default, this list is ordered in the same way as the + indexing set of the basis of the parent of ``self``. + + INPUT: + + - ``order`` -- (optional) an ordering of the basis indexing set + + EXAMPLES:: + + sage: v = vector([0, -1, -3]) + sage: v.dense_coefficient_list() + [0, -1, -3] + sage: v.dense_coefficient_list([2,1,0]) + [-3, -1, 0] + sage: sorted(v.coefficients()) + [-3, -1] + """ + if order is None: + try: + order = sorted(self.parent().basis().keys()) + except AttributeError: # Not a family, assume it is list-like + order = range(self.parent().dimension()) + return [self[i] for i in order] class MorphismMethods: def matrix(self, base_ring=None, side="left"): diff --git a/src/sage/categories/finite_enumerated_sets.py b/src/sage/categories/finite_enumerated_sets.py index 1cce141f25e..1c795543d62 100644 --- a/src/sage/categories/finite_enumerated_sets.py +++ b/src/sage/categories/finite_enumerated_sets.py @@ -496,14 +496,14 @@ class ParentMethods: 'sage.categories.sets_cat' sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' + 'sage.categories.sets_cat' """ # Ambiguity resolution between methods inherited from # Sets.CartesianProducts and from EnumeratedSets.Finite. random_element = Sets.CartesianProducts.ParentMethods.random_element.__func__ cardinality = Sets.CartesianProducts.ParentMethods.cardinality.__func__ - __iter__ = EnumeratedSets.CartesianProducts.ParentMethods.__iter__.__func__ + __iter__ = Sets.CartesianProducts.ParentMethods.__iter__.__func__ def last(self): r""" diff --git a/src/sage/categories/finite_lattice_posets.py b/src/sage/categories/finite_lattice_posets.py index 57653c6abe8..f149cee0524 100644 --- a/src/sage/categories/finite_lattice_posets.py +++ b/src/sage/categories/finite_lattice_posets.py @@ -164,6 +164,7 @@ def is_lattice_morphism(self, f, codomain): False .. seealso:: + :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_poset_morphism` """ # Note: in a lattice, x <= y iff join(x,y) = y . diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index 8bba29f47bc..06e741dd0fa 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -910,7 +910,7 @@ def birational_toggle(self, v, labelling): sage: t1 = V.birational_toggle(1, t) Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero We don't get into zero-division issues in the tropical semiring (unless the zero of the tropical semiring appears @@ -1678,7 +1678,7 @@ def toggling_orbit_iter(self, vs, oideal, element_constructor=set, stop=True, ch next = self.order_ideal_toggles(next, vs) yield element_constructor(next) - def order_ideals_lattice(self, as_ideals=True, facade=False): + def order_ideals_lattice(self, as_ideals=True, facade=None): r""" Return the lattice of order ideals of a poset ``self``, ordered by inclusion. @@ -1700,10 +1700,13 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): - ``as_ideals`` -- Boolean, if ``True`` (default) returns a poset on the set of order ideals, otherwise on the set of antichains + - ``facade`` -- Boolean or ``None`` (default). Whether to + return a facade lattice or not. By default return facade + lattice if the poset is a facade poset. EXAMPLES:: - sage: P = Posets.PentagonPoset(facade = True) + sage: P = Posets.PentagonPoset() sage: P.cover_relations() [[0, 1], [0, 2], [1, 4], [2, 3], [3, 4]] sage: J = P.order_ideals_lattice(); J @@ -1727,10 +1730,15 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): sage: J.cover_relations() [[{}, {0}], [{0}, {0, 2}], [{0}, {0, 1}], [{0, 2}, {0, 1, 2}], [{0, 1}, {0, 1, 2}], [{0, 1, 2}, {0, 1, 2, 3}]] - .. NOTE:: we use facade posets in the examples above just - to ensure a nicer ordering in the output. + sage: P = Poset({1:[2]}) + sage: J_facade = P.order_ideals_lattice() + sage: J_nonfacade = P.order_ideals_lattice(facade=False) + sage: type(J_facade[0]) == type(J_nonfacade[0]) + False """ from sage.combinat.posets.lattices import LatticePoset + if facade is None: + facade = self._is_facade if as_ideals: from sage.misc.misc import attrcall from sage.sets.set import Set diff --git a/src/sage/categories/finite_semigroups.py b/src/sage/categories/finite_semigroups.py index fbbcdaa09f1..190751ffbde 100644 --- a/src/sage/categories/finite_semigroups.py +++ b/src/sage/categories/finite_semigroups.py @@ -117,7 +117,7 @@ def j_transversal_of_idempotents(self): sage: S = FiniteSemigroups().example(alphabet=('a','b', 'c')) sage: sorted(S.j_transversal_of_idempotents()) - ['a', 'ab', 'ac', 'acb', 'b', 'c', 'cb'] + ['a', 'ab', 'abc', 'ac', 'b', 'bc', 'c'] """ def first_idempotent(l): for x in l: diff --git a/src/sage/categories/functor.pyx b/src/sage/categories/functor.pyx index eedbab82ffe..033fd461d68 100644 --- a/src/sage/categories/functor.pyx +++ b/src/sage/categories/functor.pyx @@ -181,6 +181,17 @@ cdef class Functor(SageObject): self.__domain = domain self.__codomain = codomain + def __hash__(self): + r""" + TESTS:: + + sage: from sage.categories.functor import Functor + sage: F = Functor(Rings(), Fields()) + sage: hash(F) # random + 42 + """ + return hash(self.__domain) ^ hash(self.__codomain) + def __reduce__(self): """ Generic pickling of functors. diff --git a/src/sage/categories/graded_algebras.py b/src/sage/categories/graded_algebras.py index 98e89f81993..d03f648d499 100644 --- a/src/sage/categories/graded_algebras.py +++ b/src/sage/categories/graded_algebras.py @@ -20,16 +20,29 @@ class GradedAlgebras(GradedModulesCategory): sage: GradedAlgebras(ZZ) Category of graded algebras over Integer Ring sage: GradedAlgebras(ZZ).super_categories() - [Category of algebras over Integer Ring, + [Category of filtered algebras over Integer Ring, Category of graded modules over Integer Ring] TESTS:: sage: TestSuite(GradedAlgebras(ZZ)).run() """ - class ParentMethods: - pass + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + Since ``self`` is already graded, this just returns + ``self``. + + EXAMPLES:: + + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True + """ + return self class ElementMethods: pass + diff --git a/src/sage/categories/graded_algebras_with_basis.py b/src/sage/categories/graded_algebras_with_basis.py index 66a608fad2e..949fceddfb3 100644 --- a/src/sage/categories/graded_algebras_with_basis.py +++ b/src/sage/categories/graded_algebras_with_basis.py @@ -20,7 +20,7 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: C = GradedAlgebrasWithBasis(ZZ); C Category of graded algebras with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of algebras with basis over Integer Ring, + [Category of filtered algebras with basis over Integer Ring, Category of graded algebras over Integer Ring, Category of graded modules with basis over Integer Ring] @@ -28,107 +28,60 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ - class ParentMethods: - pass - - class ElementMethods: - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: (3*x).is_homogeneous() - True - sage: (x^3 - y^2).is_homogeneous() - True - sage: ((x + y)^2).is_homogeneous() - False + # This needs to be copied in GradedAlgebras because we need to have + # FilteredAlgebrasWithBasis as an extra super category + def graded_algebra(self): """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def homogeneous_degree(self): - """ - The degree of this element. - - .. note:: - - This raises an error if the element is not homogeneous. - To obtain the maximum of the degrees of the homogeneous - summands, use :meth:`maximal_degree` + Return the associated graded algebra to ``self``. - .. seealso: :meth:`maximal_degree` + This is ``self``, because ``self`` is already graded. + See :meth:`~sage.categories.filtered_algebras_with_basis.FilteredAlgebrasWithBasis.graded_algebra` + for the general behavior of this method, and see + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and properties of associated graded + algebras. EXAMPLES:: - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.homogeneous_degree() - 2 - sage: (x^3 + 4*y^2).homogeneous_degree() - 6 - sage: ((1 + x)^3).homogeneous_degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - - TESTS:: + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - try: - assert self.is_homogeneous() - return self.parent().degree_on_basis(self.leading_support()) - except AssertionError: - raise ValueError("Element is not homogeneous.") + TESTS: - # default choice for degree; will be overridden as necessary - degree = homogeneous_degree + Let us check that the three methods + :meth:`to_graded_conversion`, :meth:`from_graded_conversion` + and :meth:`projection` (which form the interface of the + associated graded algebra) work correctly here:: - def maximal_degree(self): + sage: to_gr = m.to_graded_conversion() + sage: from_gr = m.from_graded_conversion() + sage: m[2] == to_gr(m[2]) == from_gr(m[2]) + True + sage: u = 3*m[1] - (1/2)*m[3] + sage: u == to_gr(u) == from_gr(u) + True + sage: m.zero() == to_gr(m.zero()) == from_gr(m.zero()) + True + sage: p2 = m.projection(2) + sage: p2(m[2] - 4*m[1,1] + 3*m[1] - 2*m[[]]) + -4*m[1, 1] + m[2] + sage: p2(4*m[1]) + 0 + sage: p2(m.zero()) == m.zero() + True """ - The maximum of the degrees of the homogeneous summands. + return self - .. seealso: :meth:`homogeneous_degree` + # .. TODO:: + # Possibly override ``to_graded_conversion`` and + # ``from_graded_conversion`` with identity morphisms? + # I have to admit I don't know the right way to construct + # identity morphisms other than using the identity matrix. + # Also, ``projection`` could be overridden by, well, a + # projection. - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.maximal_degree() - 2 - sage: (x^3 + 4*y^2).maximal_degree() - 6 - sage: ((1 + x)^3).maximal_degree() - 6 - - TESTS:: + class ElementMethods: + pass - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - else: - degree_on_basis = self.parent().degree_on_basis - return max(degree_on_basis(m) for m in self.support()) diff --git a/src/sage/categories/graded_bialgebras_with_basis.py b/src/sage/categories/graded_bialgebras_with_basis.py index 171de5b99e7..8b9ff21acdf 100644 --- a/src/sage/categories/graded_bialgebras_with_basis.py +++ b/src/sage/categories/graded_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def GradedBialgebrasWithBasis(base_ring): sage: C = GradedBialgebrasWithBasis(QQ); C Join of Category of ... sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of graded algebras with basis over Rational Field] TESTS:: diff --git a/src/sage/categories/graded_hopf_algebras_with_basis.py b/src/sage/categories/graded_hopf_algebras_with_basis.py index 6c9132d85c0..506ffbcfdc8 100644 --- a/src/sage/categories/graded_hopf_algebras_with_basis.py +++ b/src/sage/categories/graded_hopf_algebras_with_basis.py @@ -8,7 +8,8 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #****************************************************************************** - +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.tensor import tensor from sage.categories.graded_modules import GradedModulesCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.misc.cachefunc import cached_method @@ -35,6 +36,18 @@ class GradedHopfAlgebrasWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ + def example(self): + """ + TESTS:: + + sage: GradedHopfAlgebrasWithBasis(QQ).example() + An example of a graded connected Hopf algebra with basis over Rational Field + + """ + from sage.categories.examples.graded_connected_hopf_algebras_with_basis import \ + GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator + return GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator(self.base()) + class ParentMethods: pass @@ -55,8 +68,131 @@ def super_categories(self): TESTS:: sage: TestSuite(GradedHopfAlgebrasWithBasis(QQ).WithRealizations()).run() + """ from sage.categories.graded_hopf_algebras import GradedHopfAlgebras R = self.base_category().base_ring() return [GradedHopfAlgebras(R)] + + class Connected(CategoryWithAxiom_over_base_ring): + + + def example(self): + """ + TESTS:: + + sage: GradedHopfAlgebrasWithBasis(QQ).Connected().example() + An example of a graded connected Hopf algebra with basis over Rational Field + + """ + from sage.categories.examples.graded_connected_hopf_algebras_with_basis import \ + GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator + return GradedConnectedCombinatorialHopfAlgebraWithPrimitiveGenerator(self.base()) + + class ParentMethods: + + def counit_on_basis(self, i): + r""" + The default counit of a graded connected Hopf algebra. + + INPUT: + + - ``i`` -- an element of the index set + + OUTPUT: + + - an element of the base ring + + .. MATH:: + + c(i) := \begin{cases} + 1 & \hbox{if $i$ is the unique element of degree $0$}\\ + 0 & \hbox{otherwise}. + \end{cases} + + EXAMPLES:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.monomial(4).counit() # indirect doctest + 0 + sage: H.monomial(0).counit() # indirect doctest + 1 + + """ + if i == self.one_basis(): + return self.base_ring().one() + return self.base_ring().zero() + + @cached_method + def antipode_on_basis(self, index): + r""" + The antipode on the basis element indexed by ``index``. + + INPUT: + + - ``index`` -- an element of the index set + + .. MATH:: + + S(x) := -\sum_{x^L\neq x} S(x^L) \times x^R + + in general or `x` if `|x| = 0`. + + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.monomial(0).antipode() #indirect doctest + P0 + sage: H.monomial(1).antipode() #indirect doctest + -P1 + sage: H.monomial(2).antipode() #indirect doctest + P2 + sage: H.monomial(3).antipode() #indirect doctest + -P3 + + """ + if self.monomial(index) == self.one(): + return self.one() + else: + S = self.antipode_on_basis + x__S_Id = tensor([self, self]).module_morphism( + lambda (a, b): S(a) * self.monomial(b), + codomain=self) + return -x__S_Id( + self.monomial(index).coproduct() + - tensor([self.monomial(index), self.one()]) + ) + + def antipode(self, elem): + r""" + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.antipode(H.monomial(140)) + P140 + + """ + import itertools + return self.linear_combination(itertools.imap( + lambda (mon, coeff): \ + (self.antipode_on_basis(mon), coeff), + elem.monomial_coefficients().iteritems() + )) + + class ElementMethods: + + def antipode(self): + r""" + TESTS:: + + sage: H = GradedHopfAlgebrasWithBasis(QQ).Connected().example() + sage: H.monomial(0).antipode() + P0 + sage: H.monomial(2).antipode() + P2 + sage: (2*H.monomial(1) + 3*H.monomial(4)).antipode() + -2*P1 + 3*P4 + + """ + return self.parent().antipode(self) diff --git a/src/sage/categories/graded_modules.py b/src/sage/categories/graded_modules.py index 65f8a6ba7bb..ca31efe005b 100644 --- a/src/sage/categories/graded_modules.py +++ b/src/sage/categories/graded_modules.py @@ -11,6 +11,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category from sage.categories.category_types import Category_over_base_ring from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory @@ -26,7 +27,7 @@ def __init__(self, base_category): sage: C.base_category() Category of algebras over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of algebras over Rational Field, + [Category of filtered algebras over Rational Field, Category of graded modules over Rational Field] sage: AlgebrasWithBasis(QQ).Graded().base_ring() @@ -56,16 +57,64 @@ def _repr_object_names(self): """ return "graded {}".format(self.base_category()._repr_object_names()) + @classmethod + def default_super_categories(cls, category, *args): + r""" + Return the default super categories of ``category.Graded()``. + + Mathematical meaning: every graded object (module, algebra, + etc.) is a filtered object with the (implicit) filtration + defined by `F_i = \bigoplus_{j \leq i} G_j`. + + INPUT: + + - ``cls`` -- the class ``GradedModulesCategory`` + - ``category`` -- a category + + OUTPUT: a (join) category + + In practice, this returns ``category.Filtered()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() ` + (that is the join of ``category.Filtered()`` and ``cat`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Algebras()``, which has ``cat=Modules()`` + as super category. Then, a grading of an algebra `G` + is also a filtration of `G`:: + + sage: Algebras(QQ).Graded().super_categories() + [Category of filtered algebras over Rational Field, + Category of graded modules over Rational Field] + + This resulted from the following call:: + + sage: sage.categories.graded_modules.GradedModulesCategory.default_super_categories(Algebras(QQ)) + Join of Category of filtered algebras over Rational Field + and Category of graded modules over Rational Field + """ + cat = super(GradedModulesCategory, cls).default_super_categories(category, *args) + return Category.join([category.Filtered(), cat]) + class GradedModules(GradedModulesCategory): - """ + r""" The category of graded modules. + We consider every graded module `M = \bigoplus_i M_i` as a + filtered module under the (natural) filtration given by + + .. MATH:: + + F_i = \bigoplus_{j < i} M_j. + EXAMPLES:: sage: GradedModules(ZZ) Category of graded modules over Integer Ring sage: GradedModules(ZZ).super_categories() - [Category of modules over Integer Ring] + [Category of filtered modules over Integer Ring] The category of graded modules defines the graded structure which shall be preserved by morphisms:: @@ -77,71 +126,9 @@ class GradedModules(GradedModulesCategory): sage: TestSuite(GradedModules(ZZ)).run() """ - - def extra_super_categories(self): - r""" - Adds :class:`VectorSpaces` to the super categories of ``self`` if - the base ring is a field. - - EXAMPLES:: - - sage: Modules(QQ).Graded().extra_super_categories() - [Category of vector spaces over Rational Field] - sage: Modules(ZZ).Graded().extra_super_categories() - [] - - This makes sure that ``Modules(QQ).Graded()`` returns an - instance of :class:`GradedModules` and not a join category of - an instance of this class and of ``VectorSpaces(QQ)``:: - - sage: type(Modules(QQ).Graded()) - - - .. TODO:: - - Get rid of this workaround once there is a more systematic - approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. - Probably the later should be a category with axiom, and - covariant constructions should play well with axioms. - """ - from sage.categories.modules import Modules - from sage.categories.fields import Fields - base_ring = self.base_ring() - if base_ring in Fields: - return [Modules(base_ring)] - else: - return [] - - class SubcategoryMethods: - - @cached_method - def Connected(self): - r""" - Return the full subcategory of the connected objects of ``self``. - - EXAMPLES:: - - sage: Modules(ZZ).Graded().Connected() - Category of graded connected modules over Integer Ring - sage: Coalgebras(QQ).Graded().Connected() - Join of Category of graded connected modules over Rational Field - and Category of coalgebras over Rational Field - sage: GradedAlgebrasWithBasis(QQ).Connected() - Category of graded connected algebras with basis over Rational Field - - TESTS:: - - sage: TestSuite(Modules(ZZ).Graded().Connected()).run() - sage: Coalgebras(QQ).Graded().Connected.__module__ - 'sage.categories.graded_modules' - """ - return self._with_axiom("Connected") - - class Connected(CategoryWithAxiom_over_base_ring): - pass - class ParentMethods: pass class ElementMethods: pass + diff --git a/src/sage/categories/graded_modules_with_basis.py b/src/sage/categories/graded_modules_with_basis.py index 9bb99fc5a95..a1c4a5d5c2e 100644 --- a/src/sage/categories/graded_modules_with_basis.py +++ b/src/sage/categories/graded_modules_with_basis.py @@ -20,8 +20,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: C = GradedModulesWithBasis(ZZ); C Category of graded modules with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of graded modules over Integer Ring, - Category of modules with basis over Integer Ring] + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] sage: C is ModulesWithBasis(ZZ).Graded() True @@ -30,164 +30,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ class ParentMethods: - - # TODO: which syntax do we prefer? - # A.basis(degree = 3) - # A.basis().subset(degree=3) - - # This is related to the following design question: - # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` - # be the elements of F of degree 3 or those whose index is of degree 3? - - def basis(self, d=None): - """ - Returns the basis for (an homogeneous component of) this graded module - - INPUT: - - - `d` -- non negative integer or ``None``, optional (default: ``None``) - - If `d` is None, returns a basis of the module. - Otherwise, returns the basis of the homogeneous component of degree `d`. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: A.basis(4) - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions of the integer 4} - - Without arguments, the full basis is returned:: - - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - """ - from sage.sets.family import Family - if d is None: - return Family(self._indices, self.monomial) - else: - return Family(self._indices.subset(size=d), self.monomial) + pass class ElementMethods: + pass - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x=A(Partition((3,2,1))) - sage: y=A(Partition((4,4,1))) - sage: z=A(Partition((2,2,2))) - sage: (3*x).is_homogeneous() - True - sage: (x - y).is_homogeneous() - False - sage: (x+2*z).is_homogeneous() - True - """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def degree(self): - """ - The degree of this element in the graded module. - - .. note:: - - This raises an error if the element is not homogeneous. - Another implementation option would be to return the - maximum of the degrees of the homogeneous summands. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A(Partition((3,2,1))) - sage: y = A(Partition((4,4,1))) - sage: z = A(Partition((2,2,2))) - sage: x.degree() - 6 - sage: (x + 2*z).degree() - 6 - sage: (y - x).degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - """ - if not self.support(): - raise ValueError("The zero element does not have a well-defined degree.") - if self.is_homogeneous(): - return self.parent().degree_on_basis(self.leading_support()) - else: - raise ValueError("Element is not homogeneous.") - - def homogeneous_component(self, n): - """ - Return the homogeneous component of degree ``n`` of this - element. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.homogeneous_component(-1) - 0 - sage: x.homogeneous_component(0) - 2*P[] - sage: x.homogeneous_component(1) - 2*P[1] - sage: x.homogeneous_component(2) - 3*P[2] - sage: x.homogeneous_component(3) - 0 - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.homogeneous_component(3).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) - for (i, c) in self - if degree_on_basis(i) == n) - - def truncate(self, n): - """ - Return the sum of the homogeneous components of degree ``< n`` of this element - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.truncate(0) - 0 - sage: x.truncate(1) - 2*P[] - sage: x.truncate(2) - 2*P[] + 2*P[1] - sage: x.truncate(3) - 2*P[] + 2*P[1] + 3*P[2] - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.truncate(0).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) for (i, c) in self - if degree_on_basis(i) < n) diff --git a/src/sage/categories/graphs.py b/src/sage/categories/graphs.py new file mode 100644 index 00000000000..1be42bb94a5 --- /dev/null +++ b/src/sage/categories/graphs.py @@ -0,0 +1,108 @@ +""" +Graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.simplicial_complexes import SimplicialComplexes + +class Graphs(Category_singleton): + r""" + The category of graphs. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs(); C + Category of graphs + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().super_categories() + [Category of simplicial complexes] + """ + return [SimplicialComplexes()] + + class ParentMethods: + @abstract_method + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + + @abstract_method + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + + def dimension(self): + """ + Return the dimension of ``self`` as a CW complex. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.dimension() + 1 + """ + if self.edges(): + return 1 + return 0 + + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.facets() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return self.edges() + + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: sorted(C.faces(), key=lambda x: (x.dimension(), x.value)) + [0, 1, 2, 3, 4, (0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return set(self.edges()).union(self.vertices()) + diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index eaa0ed868e8..59d32d80c03 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -19,6 +19,7 @@ from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.cartesian_product import CartesianProductsCategory, cartesian_product from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.topological_spaces import TopologicalSpacesCategory class Groups(CategoryWithAxiom): """ @@ -480,6 +481,7 @@ def conjugacy_class(self): return self.parent().conjugacy_class(self) Finite = LazyImport('sage.categories.finite_groups', 'FiniteGroups') + Lie = LazyImport('sage.categories.lie_groups', 'LieGroups', 'Lie') #Algebras = LazyImport('sage.categories.group_algebras', 'GroupAlgebras') class Commutative(CategoryWithAxiom): @@ -914,6 +916,7 @@ def lift(i, gen): # Infinitely generated # This does not return a good output, but it is "correct" # TODO: Figure out a better way to do things + from sage.categories.cartesian_product import cartesian_product gens_prod = cartesian_product([Family(G.group_generators(), lambda g: (i, g)) for i,G in enumerate(F)]) @@ -942,3 +945,15 @@ def order(self): """ from sage.misc.misc_c import prod return prod(c.cardinality() for c in self.cartesian_factors()) + + class Topological(TopologicalSpacesCategory): + """ + Category of topological groups. + + A topological group `G` is a group which has a topology such that + multiplication and taking inverses are continuous functions. + + REFERENCES: + + - :wikipedia:`Topological_group` + """ diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 25b64b2e993..d2b99a7cbbc 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -428,11 +428,22 @@ def _Hom_(self, Y, category=None, **options): to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] sage: type(H) + + TESTS: + + Check that we fallback first to trying a crystal homset + (:trac:`19458`):: + + sage: Binf = crystals.infinity.Tableaux(['A',2]) + sage: Bi = crystals.elementary.Elementary(Binf.cartan_type(), 1) + sage: tens = Bi.tensor(Binf) + sage: Hom(Binf, tens) + Set of Crystal Morphisms from ... """ if category is None: category = self.category() - elif not category.is_subcategory(HighestWeightCrystals()): - raise TypeError("{} is not a subcategory of HighestWeightCrystals()".format(category)) + elif not category.is_subcategory(Crystals()): + raise TypeError("{} is not a subcategory of Crystals()".format(category)) if Y not in Crystals(): raise TypeError("{} is not a crystal".format(Y)) return HighestWeightCrystalHomset(self, Y, category=category, **options) diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 412352a9035..17721fb54e7 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -283,17 +283,13 @@ def Hom(X, Y, category=None, check=True): sage: S = SimplicialComplex([[1,2], [1,4]]); S.rename("S") sage: Hom(S, S, SimplicialComplexes()) - Set of Morphisms from S to S in Category of simplicial complexes + Set of Morphisms from S to S in Category of finite simplicial complexes - sage: H = Hom(Set(), S, Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(Set(), S, Sets()) + Set of Morphisms from {} to S in Category of sets - sage: H = Hom(S, Set(), Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(S, Set(), Sets()) + Set of Morphisms from S to {} in Category of sets sage: H = Hom(S, S, ChainComplexes(QQ)) Traceback (most recent call last): @@ -651,7 +647,7 @@ def __reduce__(self): (, (Vector space of dimension 2 over Rational Field, Vector space of dimension 3 over Rational Field, - Category of vector spaces with basis over quotient fields, + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces), False)) TESTS:: @@ -859,7 +855,7 @@ def __call__(self, x=None, y=None, check=True, **options): sage: H = Hom(Set([1,2,3]), Set([1,2,3])) sage: f = H( lambda x: 4-x ) sage: f.parent() - Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of sets + Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of finite sets sage: f(1), f(2), f(3) # todo: not implemented sage: H = Hom(ZZ, QQ, Sets()) @@ -1168,50 +1164,23 @@ def reversed(self): sage: H = Hom(ZZ^2, ZZ^3); H Set of Morphisms from Ambient free module of rank 2 over - the principal ideal domain Integer Ring to Ambient free - module of rank 3 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 3 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H) sage: H.reversed() Set of Morphisms from Ambient free module of rank 3 over - the principal ideal domain Integer Ring to Ambient free - module of rank 2 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 2 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H.reversed()) """ return Hom(self.codomain(), self.domain(), category = self.homset_category()) - ############### For compatibility with old coercion model ####################### - - def get_action_c(self, R, op, self_on_left): - """ - .. WARNING:: - - For compatibility with old coercion model. DO NOT USE! - - TESTS:: - - sage: H = Hom(ZZ^2, ZZ^3) - sage: H.get_action_c(ZZ, operator.add, ZZ) - """ - return None - - def coerce_map_from_c(self, R): - """ - .. WARNING:: - - For compatibility with old coercion model. DO NOT USE! - - TESTS:: - - sage: H = Hom(ZZ^2, ZZ^3) - sage: H.coerce_map_from_c(ZZ) - """ - return None # Really needed??? class HomsetWithBase(Homset): diff --git a/src/sage/categories/hopf_algebras.py b/src/sage/categories/hopf_algebras.py index eba5add5d9a..46c5dd8fccc 100644 --- a/src/sage/categories/hopf_algebras.py +++ b/src/sage/categories/hopf_algebras.py @@ -8,13 +8,13 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #****************************************************************************** - from sage.misc.lazy_import import LazyImport from category import Category from category_types import Category_over_base_ring from sage.categories.bialgebras import Bialgebras from sage.categories.tensor import TensorProductsCategory # tensor from sage.categories.realizations import RealizationsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.misc.cachefunc import cached_method #from sage.misc.lazy_attribute import lazy_attribute @@ -46,7 +46,7 @@ def super_categories(self): def dual(self): """ - Returns the dual category + Return the dual category EXAMPLES: @@ -61,9 +61,10 @@ def dual(self): WithBasis = LazyImport('sage.categories.hopf_algebras_with_basis', 'HopfAlgebrasWithBasis') class ElementMethods: + def antipode(self): """ - Returns the antipode of self. + Return the antipode of self EXAMPLES:: @@ -104,6 +105,8 @@ class Morphism(Category): """ pass + class Super(SuperModulesCategory): + pass class TensorProducts(TensorProductsCategory): """ diff --git a/src/sage/categories/hopf_algebras_with_basis.py b/src/sage/categories/hopf_algebras_with_basis.py index 3a2f8a1dbd2..fb01c14d369 100644 --- a/src/sage/categories/hopf_algebras_with_basis.py +++ b/src/sage/categories/hopf_algebras_with_basis.py @@ -27,8 +27,7 @@ class HopfAlgebrasWithBasis(CategoryWithAxiom_over_base_ring): Category of hopf algebras with basis over Rational Field sage: C.super_categories() [Category of hopf algebras over Rational Field, - Category of algebras with basis over Rational Field, - Category of coalgebras with basis over Rational Field] + Category of bialgebras with basis over Rational Field] We now show how to use a simple Hopf algebra, namely the group algebra of the dihedral group (see also AlgebrasWithBasis):: @@ -147,6 +146,7 @@ def example(self, G = None): FiniteDimensional = LazyImport('sage.categories.finite_dimensional_hopf_algebras_with_basis', 'FiniteDimensionalHopfAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_hopf_algebras_with_basis', 'GradedHopfAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_hopf_algebras_with_basis', 'SuperHopfAlgebrasWithBasis') class ParentMethods: diff --git a/src/sage/categories/lie_groups.py b/src/sage/categories/lie_groups.py new file mode 100644 index 00000000000..3883822f105 --- /dev/null +++ b/src/sage/categories/lie_groups.py @@ -0,0 +1,72 @@ +r""" +Lie Groups +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +#from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.groups import Groups +from sage.categories.manifolds import Manifolds + +class LieGroups(Category_over_base_ring): + r""" + The category of Lie groups. + + A Lie group is a topological group with a smooth manifold structure. + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: C = LieGroups(QQ); C + Category of Lie groups over Rational Field + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).super_categories() + [Category of topological groups, + Category of smooth manifolds over Rational Field] + """ + return [Groups().Topological(), Manifolds(self.base()).Smooth()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of Lie groups defines no new + structure: a morphism of topological spaces and of smooth + manifolds is a morphism as Lie groups. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).additional_structure() + """ + return None + + # Because Lie is a name that deserves to be capitalized + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ) # indirect doctest + Category of Lie groups over Rational Field + """ + return "Lie groups over {}".format(self.base_ring()) + diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index bf16fad06d0..42f03fc918a 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -378,6 +378,22 @@ def extra_super_categories(self): """ return [Magmas().Commutative()] + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + r""" + Implement the fact that a cartesian product of commutative + additive magmas is still an commutative additive magmas. + + EXAMPLES:: + + sage: C = Magmas().Commutative().CartesianProducts() + sage: C.extra_super_categories() + [Category of commutative magmas] + sage: C.axioms() + frozenset({'Commutative'}) + """ + return [Magmas().Commutative()] + class Unital(CategoryWithAxiom): def additional_structure(self): @@ -452,9 +468,11 @@ def _test_one(self, **options): for x in tester.some_elements(): tester.assert_(x * one == x) tester.assert_(one * x == x) - # Check that one is immutable by asking its hash; - tester.assertEqual(type(one.__hash__()), int) - tester.assertEqual(one.__hash__(), one.__hash__()) + # Check that one is immutable if it looks like we can test this + if hasattr(one,"is_immutable"): + tester.assertEqual(one.is_immutable(),True) + if hasattr(one,"is_mutable"): + tester.assertEqual(one.is_mutable(),False) def is_empty(self): r""" @@ -1020,9 +1038,11 @@ def example(self): sage: C = Magmas().CartesianProducts().example(); C The cartesian product of (Rational Field, Integer Ring, Integer Ring) sage: C.category() - Join of Category of rings ... + Category of Cartesian products of commutative rings sage: sorted(C.category().axioms()) - ['AdditiveAssociative', 'AdditiveCommutative', 'AdditiveInverse', 'AdditiveUnital', 'Associative', 'Distributive', 'Unital'] + ['AdditiveAssociative', 'AdditiveCommutative', 'AdditiveInverse', + 'AdditiveUnital', 'Associative', 'Commutative', + 'Distributive', 'Unital'] sage: TestSuite(C).run() """ diff --git a/src/sage/categories/magmas_and_additive_magmas.py b/src/sage/categories/magmas_and_additive_magmas.py index d9d6c93c46d..86d378191f7 100644 --- a/src/sage/categories/magmas_and_additive_magmas.py +++ b/src/sage/categories/magmas_and_additive_magmas.py @@ -11,6 +11,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport from sage.categories.category_singleton import Category_singleton +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.additive_magmas import AdditiveMagmas from sage.categories.magmas import Magmas @@ -131,3 +132,16 @@ def additional_structure(self): Distributive = LazyImport('sage.categories.distributive_magmas_and_additive_magmas', 'DistributiveMagmasAndAdditiveMagmas', at_startup=True) + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + r""" + Implement the fact that this structure is stable under cartesian + products. + + TESTS:: + + sage: from sage.categories.magmas_and_additive_magmas import MagmasAndAdditiveMagmas + sage: MagmasAndAdditiveMagmas().CartesianProducts().extra_super_categories() + [Category of magmas and additive magmas] + """ + return [MagmasAndAdditiveMagmas()] diff --git a/src/sage/categories/magmatic_algebras.py b/src/sage/categories/magmatic_algebras.py index 48efdf0fd8c..e49a124bfd0 100644 --- a/src/sage/categories/magmatic_algebras.py +++ b/src/sage/categories/magmatic_algebras.py @@ -137,7 +137,9 @@ def algebra_generators(self): sage: R. = ZZ[] sage: P = PartitionAlgebra(1, x, R) sage: P.algebra_generators() - Finite family {{{-1, 1}}: P[{{-1, 1}}], {{-1}, {1}}: P[{{-1}, {1}}]} + Lazy family (Term map from Partition diagrams of order 1 to + Partition Algebra of rank 1 with parameter x over Univariate Polynomial Ring in x + over Integer Ring(i))_{i in Partition diagrams of order 1} """ return self.basis() diff --git a/src/sage/categories/manifolds.py b/src/sage/categories/manifolds.py new file mode 100644 index 00000000000..4a6433b95ec --- /dev/null +++ b/src/sage/categories/manifolds.py @@ -0,0 +1,349 @@ +r""" +Manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.sets_cat import Sets +from sage.categories.fields import Fields + +class Manifolds(Category_over_base_ring): + r""" + The category of manifolds over any topological field. + + Let `k` be a topological field. A `d`-dimensional `k`-*manifold* `M` + is a second countable Hausdorff space such that the neighborhood of + any point `x \in M` is homeomorphic to `k^d`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR); C + Category of manifolds over Real Field with 53 bits of precision + sage: C.super_categories() + [Category of topological spaces] + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + def __init__(self, base, name=None): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR) + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + if base not in Fields().Topological(): + raise ValueError("base must be a topological field") + Category_over_base_ring.__init__(self, base, name) + + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of manifolds defines no new + structure: a morphism of topological spaces between + manifolds is a manifold morphism. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).additional_structure() + """ + return None + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(RR).example() + sage: M.dimension() + 3 + """ + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected() + Category of connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: Manifolds(RR).Connected.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected().FiniteDimensional(); C + Category of finite dimensional connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected().FiniteDimensional.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('FiniteDimensional') + + @cached_method + def Differentiable(self): + """ + Return the subcategory of the differentiable objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Differentiable() + Category of differentiable manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Differentiable()).run() + sage: Manifolds(RR).Differentiable.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Differentiable') + + @cached_method + def Smooth(self): + """ + Return the subcategory of the smooth objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth() + Category of smooth manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Smooth()).run() + sage: Manifolds(RR).Smooth.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Smooth') + + @cached_method + def Analytic(self): + """ + Return the subcategory of the analytic objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic() + Category of analytic manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Analytic()).run() + sage: Manifolds(RR).Analytic.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Analytic') + + @cached_method + def AlmostComplex(self): + """ + Return the subcategory of the almost complex objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex() + Category of almost complex manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).AlmostComplex()).run() + sage: Manifolds(RR).AlmostComplex.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('AlmostComplex') + + @cached_method + def Complex(self): + """ + Return the subcategory of manifolds over `\CC` of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(CC).Complex() + Category of complex manifolds over + Complex Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(CC).Complex()).run() + sage: Manifolds(CC).Complex.__module__ + 'sage.categories.manifolds' + """ + return ComplexManifolds(self.base())._with_axioms(self.axioms()) + + class Differentiable(CategoryWithAxiom_over_base_ring): + """ + The category of differentiable manifolds. + + A differentiable manifold is a manifold with a differentiable atlas. + """ + + class Smooth(CategoryWithAxiom_over_base_ring): + """ + The category of smooth manifolds. + + A smooth manifold is a manifold with a smooth atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A smooth manifold is differentiable. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth().super_categories() # indirect doctest + [Category of differentiable manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Differentiable()] + + class Analytic(CategoryWithAxiom_over_base_ring): + r""" + The category of complex manifolds. + + An analytic manifold is a manifold with an analytic atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An analytic manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class AlmostComplex(CategoryWithAxiom_over_base_ring): + r""" + The category of almost complex manifolds. + + An *almost complex manifold* `M` is a manifold with a smooth tensor + field `J` of rank `(1, 1)` such that `J^2 = -1` when regarded as a + vector bundle isomorphism `J : TM \to TM` on the tangent bundle. + The tensor field `J` is called the *almost complex structure* of `M`. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An almost complex manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class FiniteDimensional(CategoryWithAxiom_over_base_ring): + """ + Category of finite dimensional manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).FiniteDimensional() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + + class Connected(CategoryWithAxiom_over_base_ring): + """ + The category of connected manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + +class ComplexManifolds(Category_over_base_ring): + r""" + The category of complex manifolds. + + A `d`-dimensional complex manifold is a manifold whose underlying + vector space is `\CC^d` and has a holomorphic atlas. + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Manifolds(self.base()).Analytic()] + diff --git a/src/sage/categories/map.pxd b/src/sage/categories/map.pxd index 4d2b0d7fe63..33b994119d6 100644 --- a/src/sage/categories/map.pxd +++ b/src/sage/categories/map.pxd @@ -2,6 +2,8 @@ from sage.structure.parent cimport Parent from sage.structure.element cimport Element cdef class Map(Element): + cdef object __weakref__ + cdef public int _coerce_cost # a rough measure of the cost of using this morphism in the coercion system. # 10 by default, 100 if a DefaultCoercionMorphism, 10000 if inexact. diff --git a/src/sage/categories/map.pyx b/src/sage/categories/map.pyx index 499b1b22486..00fc6ec3d61 100644 --- a/src/sage/categories/map.pyx +++ b/src/sage/categories/map.pyx @@ -628,9 +628,13 @@ cdef class Map(Element): sage: R. = QQ[] sage: f = R.hom([x+y, x-y], R) sage: f.category_for() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: f.category() - Category of endsets of unital magmas and right modules over quotient fields and left modules over quotient fields + Category of endsets of unital magmas + and right modules over (quotient fields and metric spaces) + and left modules over (quotient fields and metric spaces) + FIXME: find a better name for this method """ diff --git a/src/sage/categories/metric_spaces.py b/src/sage/categories/metric_spaces.py new file mode 100644 index 00000000000..c22ef8008db --- /dev/null +++ b/src/sage/categories/metric_spaces.py @@ -0,0 +1,258 @@ +r""" +Metric Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category import Category +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.with_realizations import WithRealizationsCategory + +class MetricSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Metric" + + @classmethod + def default_super_categories(cls, category): + """ + Return the default super categories of ``category.Metric()``. + + Mathematical meaning: if `A` is a metric space in the + category `C`, then `A` is also a topological space. + + INPUT: + + - ``cls`` -- the class ``MetricSpaces`` + - ``category`` -- a category `Cat` + + OUTPUT: + + A (join) category + + In practice, this returns ``category.Metric()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() + ` + (that is the join of ``category`` and ``cat.Metric()`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Groups()``. Then, a group `G` with a metric + is simultaneously a topological group by itself, and a + metric space:: + + sage: Groups().Metric().super_categories() + [Category of topological groups, Category of metric spaces] + + This resulted from the following call:: + + sage: sage.categories.metric_spaces.MetricSpacesCategory.default_super_categories(Groups()) + Join of Category of topological groups and Category of metric spaces + """ + return Category.join([category.Topological(), + super(MetricSpacesCategory, cls).default_super_categories(category)]) + + # We currently don't have a use for this, but we probably will + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Metric() # indirect doctest + Join of Category of topological groups and Category of metric spaces + """ + return "metric {}".format(self.base_category()._repr_object_names()) + +class MetricSpaces(MetricSpacesCategory): + r""" + The category of metric spaces. + + A *metric* on a set `S` is a function `d : S \times S \to \RR` + such that: + + - `d(a, b) \geq 0`, + - `d(a, b) = 0` if and only if `a = b`. + + A metric space is a set `S` with a distinguished metric. + + .. RUBRIC:: Implementation + + Objects in this category must implement either a ``dist`` on the parent + or the elements or ``metric`` on the parent; otherwise this will cause + an infinite recursion. + + .. TODO:: + + - Implement a general geodesics class. + - Implement a category for metric additive groups + and move the generic distance `d(a, b) = |a - b|` there. + - Incorperate the length of a geodesic as part of the default + distance cycle. + + EXAMPLES:: + + sage: from sage.categories.metric_spaces import MetricSpaces + sage: C = MetricSpaces() + sage: C + Category of metric spaces + sage: TestSuite(C).run() + """ + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Metric() # indirect doctest + Category of metric spaces + """ + return "metric spaces" + + class ParentMethods: + def _test_metric(self, **options): + r""" + Test that this metric space has a properly implemented metric. + + INPUT: + + - ``options`` -- any keyword arguments accepted + by :meth:`_tester` + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP._test_metric() + sage: elts = [UHP.random_element() for i in range(5)] + sage: UHP._test_metric(some_elements=elts) + """ + tester = self._tester(**options) + S = tester.some_elements() + dist = self.metric() + for a in S: + for b in S: + d = dist(a, b) + if a != b: + tester.assertGreater(d, 0) + else: + tester.assertEqual(d, 0) + + def metric(self): + """ + Return the metric of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: m = UHP.metric() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: m(p1, p2) + 2.23230104635820 + """ + return lambda a,b: a.dist(b) + + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` in ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: UHP.dist(p1, p2) + 2.23230104635820 + + sage: PD = HyperbolicPlane().PD() + sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) + arccosh(5/3) + + TESTS:: + + sage: RR.dist(-1, pi) + 4.14159265358979 + sage: RDF.dist(1, -1/2) + 1.5 + sage: CC.dist(3, 2) + 1.00000000000000 + sage: CC.dist(-1, I) + 1.41421356237310 + sage: CDF.dist(-1, I) + 1.4142135623730951 + """ + return (self(a) - self(b)).abs() + + class ElementMethods: + def abs(self): + """ + Return the absolute value of ``self``. + + EXAMPLES:: + + sage: CC(I).abs() + 1.00000000000000 + """ + P = self.parent() + return P.metric()(self, P.zero()) + + def dist(self, b): + """ + Return the distance between ``self`` and ``other``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1 + I) + sage: p1.dist(p2) + arccosh(33/7) + """ + return self.parent().dist(self, b) + + class WithRealizations(WithRealizationsCategory): + class ParentMethods: + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` by converting them + to a realization of ``self`` and doing the computation. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: PD = H.PD() + sage: p1 = PD.get_point(0) + sage: p2 = PD.get_point(I/2) + sage: H.dist(p1, p2) + arccosh(5/3) + """ + R = self.a_realization() + return R.dist(R(a), R(b)) + + class SubcategoryMethods: + @cached_method + def Complete(self): + """ + Return the full subcategory of the complete objects of ``self``. + + EXAMPLES:: + + sage: Sets().Metric().Complete() + Category of complete metric spaces + + TESTS:: + + sage: TestSuite(Sets().Metric().Complete()).run() + sage: Sets().Metric().Complete.__module__ + 'sage.categories.metric_spaces' + """ + return self._with_axiom('Complete') + + class Complete(CategoryWithAxiom): + """ + The category of complete metric spaces. + """ + diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index 70d3773f667..80c74638a97 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -17,8 +17,9 @@ from sage.categories.homsets import HomsetsCategory from category import Category, JoinCategory from category_types import Category_module, Category_over_base_ring -from tensor import TensorProductsCategory +from sage.categories.tensor import TensorProductsCategory, tensor from dual import DualObjectsCategory +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.sets_cat import Sets from sage.categories.bimodules import Bimodules from sage.categories.fields import Fields @@ -347,6 +348,43 @@ def FiniteDimensional(self): """ return self._with_axiom("FiniteDimensional") + @cached_method + def Filtered(self, base_ring=None): + r""" + Return the subcategory of the filtered objects of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + + sage: Coalgebras(QQ).Filtered() + Join of Category of filtered modules over Rational Field + and Category of coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Filtered() + Category of filtered algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Graded.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.filtered_modules import FilteredModulesCategory + return FilteredModulesCategory.category_of(self) + @cached_method def Graded(self, base_ring=None): r""" @@ -383,6 +421,42 @@ def Graded(self, base_ring=None): from sage.categories.graded_modules import GradedModulesCategory return GradedModulesCategory.category_of(self) + @cached_method + def Super(self, base_ring=None): + r""" + Return the super-analogue category of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + + sage: Coalgebras(QQ).Super() + Category of super coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Super() + Category of super algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Super.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.super_modules import SuperModulesCategory + return SuperModulesCategory.category_of(self) + @cached_method def WithBasis(self): r""" @@ -429,11 +503,28 @@ def extra_super_categories(self): else: return [] + Filtered = LazyImport('sage.categories.filtered_modules', 'FilteredModules') Graded = LazyImport('sage.categories.graded_modules', 'GradedModules') + Super = LazyImport('sage.categories.super_modules', 'SuperModules') WithBasis = LazyImport('sage.categories.modules_with_basis', 'ModulesWithBasis') class ParentMethods: - pass + @cached_method + def tensor_square(self): + """ + Returns the tensor square of ``self`` + + EXAMPLES:: + + sage: A = HopfAlgebrasWithBasis(QQ).example() + sage: A.tensor_square() + An example of Hopf algebra with basis: + the group algebra of the Dihedral group of order 6 + as a permutation group over Rational Field # An example + of Hopf algebra with basis: the group algebra of the Dihedral + group of order 6 as a permutation group over Rational Field + """ + return tensor([self, self]) class ElementMethods: @@ -576,3 +667,45 @@ def extra_super_categories(self): """ from magmatic_algebras import MagmaticAlgebras return [MagmaticAlgebras(self.base_category().base_ring())] + + class CartesianProducts(CartesianProductsCategory): + """ + The category of modules constructed as cartesian products of modules + + This construction gives the direct product of modules. The + implementation is based on the following resources: + + - http://groups.google.fr/group/sage-devel/browse_thread/thread/35a72b1d0a2fc77a/348f42ae77a66d16#348f42ae77a66d16 + - http://en.wikipedia.org/wiki/Direct_product + """ + def extra_super_categories(self): + """ + A cartesian product of modules is endowed with a natural + module structure. + + EXAMPLES:: + + sage: Modules(ZZ).CartesianProducts().extra_super_categories() + [Category of modules over Integer Ring] + sage: Modules(ZZ).CartesianProducts().super_categories() + [Category of Cartesian products of commutative additive groups, + Category of modules over Integer Ring] + """ + return [self.base_category()] + + class ParentMethods: + def base_ring(self): + """ + Return the base ring of this cartesian product. + + EXAMPLES:: + + sage: E = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: F = CombinatorialFreeModule(ZZ, [2,3,4]) + sage: C = cartesian_product([E, F]); C + Free module generated by {1, 2, 3} over Integer Ring (+) + Free module generated by {2, 3, 4} over Integer Ring + sage: C.base_ring() + Integer Ring + """ + return self._sets[0].base_ring() diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index c780127492a..aa4dc15e85c 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -16,8 +16,10 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.misc.lazy_import import LazyImport +from sage.misc.lazy_import import LazyImport, lazy_import +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method +from sage.misc.abstract_method import abstract_method from sage.misc.sage_itertools import max_cmp, min_cmp from sage.categories.homsets import HomsetsCategory from sage.categories.cartesian_product import CartesianProductsCategory @@ -25,8 +27,9 @@ from sage.categories.dual import DualObjectsCategory from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.modules import Modules +from sage.categories.poor_man_map import PoorManMap +from sage.rings.infinity import Infinity from sage.structure.element import Element, parent -from sage.misc.lazy_import import lazy_import lazy_import('sage.modules.with_basis.morphism', ['ModuleMorphismByLinearity', 'ModuleMorphismFromMatrix', @@ -123,6 +126,12 @@ class ModulesWithBasis(CategoryWithAxiom_over_base_ring): .. TODO:: ``End(X)`` is an algebra. + .. NOTE:: + + This category currently requires an implementation of an + element method ``support``. Once :trac:`18066` is merged, an + implementation of an ``items`` method will be required. + TESTS:: sage: TestSuite(ModulesWithBasis(ZZ)).run() @@ -152,7 +161,7 @@ def _call_(self, x): Vector space of dimension 3 over Rational Field If ``x`` itself is not a module with basis, but there is a - canonical one associated to it, the later is returned:: + canonical one associated to it, the latter is returned:: sage: CQ(AbelianVariety(Gamma0(37))) # indirect doctest Vector space of dimension 4 over Rational Field @@ -181,7 +190,15 @@ def is_abelian(self): return self.base_ring().is_field() FiniteDimensional = LazyImport('sage.categories.finite_dimensional_modules_with_basis', 'FiniteDimensionalModulesWithBasis') + Filtered = LazyImport('sage.categories.filtered_modules_with_basis', 'FilteredModulesWithBasis') Graded = LazyImport('sage.categories.graded_modules_with_basis', 'GradedModulesWithBasis') + Super = LazyImport('sage.categories.super_modules_with_basis', 'SuperModulesWithBasis') + + # To implement a module_with_basis you need to implement the + # following methods: + # - On the parent class, either basis() or an _indices attribute and + # monomial(). + # - On the element class, monomial_coefficients(). class ParentMethods: @cached_method @@ -227,7 +244,9 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, - ``on_basis`` -- a function `f` from `I` to `Y` - ``diagonal`` -- a function `d` from `I` to `R` - ``function`` -- a function `f` from `X` to `Y` - - ``matrix`` -- a matrix of size `\dim X \times \dim Y` or `\dim Y \times \dim X` + - ``matrix`` -- a matrix of size `\dim Y \times \dim X` + (if the keyword ``side`` is set to ``'left'``) or + `\dim Y \times \dim X` (if this keyword is ``'right'``) Further options include: @@ -321,7 +340,8 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi.category_for() # todo: not implemented (ZZ is currently not in Modules(ZZ)) Category of modules over Integer Ring - Or more generaly any ring admitting a coercion map from the base ring:: + Or more generaly any ring admitting a coercion map from + the base ring:: sage: phi = X.module_morphism(on_basis=lambda i: i, codomain=RR ) sage: phi( 2 * X.monomial(1) + 3 * X.monomial(-1) ) @@ -378,13 +398,17 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi = X.module_morphism( on_basis=on_basis, codomain=Y ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'z'} over Rational Field) should be a module over the base ring of the domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'z'} over Rational Field) + should be a module over the base ring of the + domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) sage: Y = CombinatorialFreeModule(RR['q'],['z']) sage: phi = Y.module_morphism( on_basis=on_basis, codomain=X ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) should be a module over the base ring of the domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + should be a module over the base ring of the + domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) With the ``diagonal=d`` argument, this constructs the @@ -392,7 +416,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, .. MATH:: - `g(x_i) = d(i) y_i` + `g(x_i) = d(i) y_i`. This assumes that the respective bases `x` and `y` of `X` and `Y` have the same index set `I`:: @@ -512,7 +536,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, ValueError: diagonal (=3) should be a function """ - if not len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) == 1: + if len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) != 1: raise ValueError("module_morphism() takes exactly one option out of `matrix`, `on_basis`, `function`, `diagonal`") if matrix is not None: return ModuleMorphismFromMatrix(domain=self, matrix=matrix, **keywords) @@ -764,6 +788,277 @@ def tensor(*parents): """ return parents[0].__class__.Tensor(parents, category = tensor.category_from_parents(parents)) + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: S = SymmetricGroupAlgebra(QQ, 4) + sage: S.cardinality() + +Infinity + sage: S = SymmetricGroupAlgebra(GF(2), 4) # not tested -- MRO bug :trac:`15475` + sage: S.cardinality() # not tested -- MRO bug :trac:`15475` + 16777216 + sage: S.cardinality().factor() # not tested -- MRO bug :trac:`15475` + 2^24 + + sage: E. = ExteriorAlgebra(QQ) + sage: E.cardinality() + +Infinity + sage: E. = ExteriorAlgebra(GF(3)) + sage: E.cardinality() + 81 + + sage: s = SymmetricFunctions(GF(2)).s() + sage: s.cardinality() + +Infinity + """ + if self.dimension() == Infinity: + return Infinity + return self.base_ring().cardinality() ** self.dimension() + + def monomial(self, i): + """ + Return the basis element indexed by ``i``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial('a') + B['a'] + + ``F.monomial`` is in fact (almost) a map:: + + sage: F.monomial + Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + return self.basis()[i] + + def _sum_of_monomials(self, indices): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F._sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + """ + # This is the generic implementation. When implementing a + # concrete instance of a module with basis, you probably want + # to override it with something faster. + return self.sum(self.monomial(index) for index in indices) + + @lazy_attribute + def sum_of_monomials(self): + """ + Return the sum of the basis elements with indices in + ``indices``. + + INPUT: + + - ``indices`` -- an list (or iterable) of indices of basis + elements + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + + sage: F.sum_of_monomials(['a', 'b', 'a']) + 2*B['a'] + B['b'] + + ``F.sum_of_monomials`` is in fact (almost) a map:: + + sage: F.sum_of_monomials + A map to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + # domain = iterables of basis indices of self. + return PoorManMap(self._sum_of_monomials, codomain = self) + + def monomial_or_zero_if_none(self, i): + """ + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial_or_zero_if_none('a') + B['a'] + sage: F.monomial_or_zero_if_none(None) + 0 + """ + if i is None: + return self.zero() + return self.monomial(i) + + def term(self, index, coeff=None): + """ + Construct a term in ``self``. + + INPUT: + + - ``index`` -- the index of a basis element + - ``coeff`` -- an element of the coefficient ring (default: one) + + OUTPUT: + + ``coeff * B[index]``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.term(1, -2) + 0 + (-2, 0) + + Design: should this do coercion on the coefficient ring? + """ + if coeff is None: + coeff = self.base_ring().one() + return coeff * self.monomial(index) + + def sum_of_terms(self, terms): + """ + Construct a sum of terms of ``self``. + + INPUT: + + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + + OUTPUT: + + Sum of ``coeff * B[index]`` over all ``(index, coeff)`` in + ``terms``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.sum_of_terms([(0, 2), (2, -3)]) + 2 + (0, -3) + """ + return self.sum(self.term(index, coeff) for (index, coeff) in terms) + + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): + """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + + INPUT: + + - ``iter_of_elements_coeff`` -- iterator of pairs + ``(element, coeff)`` with ``element`` in ``self`` and + ``coeff`` in ``self.base_ring()`` + + - ``factor_on_left`` -- (optional) if ``True``, the coefficients + are multiplied from the left; if ``False``, the coefficients + are multiplied from the right + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.linear_combination(((a+b, 1), (-2*b + c, -1))) + 1 + (3, -1) + """ + if factor_on_left: + return self.sum(coeff * element + for element, coeff in iter_of_elements_coeff) + else: + return self.sum(element * coeff + for element, coeff in iter_of_elements_coeff) + + def _apply_module_morphism(self, x, on_basis, codomain=False): + """ + Return the image of ``x`` under the module morphism defined by + extending :func:`on_basis` by linearity. + + INPUT: + + - ``x`` -- a element of ``self`` + + - ``on_basis`` -- a function that takes in an object indexing + a basis element and returns an element of the codomain + + - ``codomain`` -- (optional) the codomain of the morphism (by + default, it is computed using :func:`on_basis`) + + If ``codomain`` is not specified, then the function tries to + compute the codomain of the module morphism by finding the image + of one of the elements in the support; hence :func:`on_basis` + should return an element whose parent is the codomain. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s([3]) + s([2,1]) + s([1,1,1]) + sage: b = 2*a + sage: f = lambda part: Integer( len(part) ) + sage: s._apply_module_morphism(a, f) #1+2+3 + 6 + sage: s._apply_module_morphism(b, f) #2*(1+2+3) + 12 + sage: s._apply_module_morphism(s(0), f) + 0 + sage: s._apply_module_morphism(s(1), f) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part)) + Traceback (most recent call last): + ... + ValueError: codomain could not be determined + """ + if x == self.zero(): + if not codomain: + from sage.combinat.family import Family + B = Family(self.basis()) + try: + z = B.first() + except StopIteration: + raise ValueError('codomain could not be determined') + codomain = on_basis(z).parent() + return codomain.zero() + + if not codomain: + keys = x.support() + key = keys[0] + try: + codomain = on_basis(key).parent() + except Exception: + raise ValueError('codomain could not be determined') + + if hasattr( codomain, 'linear_combination' ): + mc = x.monomial_coefficients(copy=False) + return codomain.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) + else: + return_sum = codomain.zero() + mc = x.monomial_coefficients(copy=False) + for key, coeff in mc.iteritems(): + return_sum += coeff * on_basis(key) + return return_sum + + def _apply_module_endomorphism(self, x, on_basis): + """ + This takes in a function ``on_basis`` from the basis indices + to the elements of ``self``, and applies it linearly to ``x``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: f = lambda part: 2*s(part.conjugate()) + sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) + 2*s[2, 1] + 2*s[3] + """ + mc = x.monomial_coefficients(copy=False) + return self.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) class ElementMethods: # TODO: Define the appropriate element methods here (instead of in @@ -777,6 +1072,310 @@ class ElementMethods: # """ # return self._lmul_(-self.parent().base_ring().one(), self) + @abstract_method + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements + in the support of ``self`` and whose values are the + corresponding coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] + 1 + sage: d['c'] + 3 + + TESTS: + + We check that we make a copy of the coefficient dictonary:: + + sage: F = CombinatorialFreeModule(ZZ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] = 5 + sage: f + B['a'] + 3*B['c'] + """ + + def __getitem__(self, m): + """ + Return the coefficient of ``m`` in ``self``. + + EXAMPLES:: + + sage: p = Partition([2,1]) + sage: q = Partition([1,1,1]) + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s(p) + sage: a._coefficient_fast([2,1]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + + :: + + sage: a._coefficient_fast(p) + 1 + sage: a._coefficient_fast(q) + 0 + sage: a[p] + 1 + sage: a[q] + 0 + """ + return self.monomial_coefficients(copy=False).get(m, self.base_ring().zero()) + + def coefficient(self, m): + """ + Return the coefficient of ``m`` in ``self`` and raise an error + if ``m`` is not in the basis indexing set. + + INPUT: + + - ``m`` -- a basis index of the parent of ``self`` + + OUTPUT: + + The ``B[m]``-coordinate of ``self`` with respect to the basis + ``B``. Here, ``B`` denotes the given basis of the parent of + ``self``. + + EXAMPLES:: + + sage: s = CombinatorialFreeModule(QQ, Partitions()) + sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficient([4]) + 1 + sage: z.coefficient([2,1]) + -2 + sage: z.coefficient(Partition([2,1])) + -2 + sage: z.coefficient([1,2]) + Traceback (most recent call last): + ... + AssertionError: [1, 2] should be an element of Partitions + sage: z.coefficient(Composition([2,1])) + Traceback (most recent call last): + ... + AssertionError: [2, 1] should be an element of Partitions + + Test that ``coefficient`` also works for those parents that do + not yet have an element_class:: + + sage: G = DihedralGroup(3) + sage: F = CombinatorialFreeModule(QQ, G) + sage: hasattr(G, "element_class") + False + sage: g = G.an_element() + sage: (2*F.monomial(g)).coefficient(g) + 2 + """ + # NT: coefficient_fast should be the default, just with appropriate assertions + # that can be turned on or off + C = self.parent().basis().keys() + # TODO: This should raise a ValueError - TS + assert m in C, "%s should be an element of %s"%(m, C) + if hasattr(C, "element_class") and not isinstance(m, C.element_class): + m = C(m) + return self[m] + + def is_zero(self): + """ + Return ``True`` if and only if ``self == 0``. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.is_zero() + False + sage: F.zero().is_zero() + True + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: s([2,1]).is_zero() + False + sage: s(0).is_zero() + True + sage: (s([2,1]) - s([2,1])).is_zero() + True + """ + zero = self.parent().base_ring().zero() + return all(v == zero for v in self.monomial_coefficients(copy=False).values()) + + def __len__(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: len(f) + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: len(z) + 4 + """ + zero = self.parent().base_ring().zero() + return len([key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero]) + + def length(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.length() + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.length() + 4 + """ + return len(self) + + def support(self): + """ + Return a list of the objects indexing the basis of + ``self.parent()`` whose corresponding coefficients of + ``self`` are non-zero. + + This method returns these objects in an arbitrary order. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: sorted(f.support()) + ['a', 'c'] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: sorted(z.support()) + [[1], [1, 1, 1], [2, 1], [4]] + """ + zero = self.parent().base_ring().zero() + return [key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero] + + def monomials(self): + """ + Return a list of the monomials of ``self`` (in an arbitrary + order). + + The monomials of an element `a` are defined to be the basis + elements whose corresponding coefficients of `a` are + non-zero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.monomials() + [B['a'], B['c']] + + sage: (F.zero()).monomials() + [] + """ + P = self.parent() + return [P.monomial(key) for key in self.support()] + + def terms(self): + """ + Return a list of the (non-zero) terms of ``self`` (in an + arbitrary order). + + .. SEEALSO:: :meth:`monomials` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.terms() + [B['a'], 2*B['c']] + """ + P = self.parent() + zero = P.base_ring().zero() + return [P.term(key, value) + for key, value in self.monomial_coefficients(copy=False).iteritems() + if value != zero] + + def coefficients(self, sort=True): + """ + Return a list of the (non-zero) coefficients appearing on + the basis elements in ``self`` (in an arbitrary order). + + INPUT: + + - ``sort`` -- (default: ``True``) to sort the coefficients + based upon the default ordering of the indexing set + + .. SEEALSO:: + + :meth:`~sage.categories.finite_dimensional_modules_with_basis.FiniteDimensionalModulesWithBasis.ElementMethods.dense_coefficient_list` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.coefficients() + [1, -3] + sage: f = B['c'] - 3*B['a'] + sage: f.coefficients() + [-3, 1] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficients() + [1, 1, 1, 1] + """ + zero = self.parent().base_ring().zero() + mc = self.monomial_coefficients(copy=False) + if not sort: + return [value for key, value in mc.iteritems() if value != zero] + + v = sorted([(key, value) for key, value in mc.iteritems() + if value != zero]) + return [value for key, value in v] def support_of_term(self): """ @@ -801,7 +1400,7 @@ def support_of_term(self): if len(self) == 1: return self.support()[0] else: - raise ValueError("%s is not a single term"%(self)) + raise ValueError("{} is not a single term".format(self)) def leading_support(self, cmp=None): r""" @@ -831,7 +1430,6 @@ def leading_support(self, cmp=None): """ return max_cmp(self.support(), cmp) - def leading_item(self, cmp=None): r""" Return the pair ``(k, c)`` where diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index 19359a7b2e9..8bcd6676a12 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -18,7 +18,7 @@ from sage.categories.semigroups import Semigroups from sage.misc.lazy_import import LazyImport from sage.categories.subquotients import SubquotientsCategory -from sage.categories.cartesian_product import CartesianProductsCategory, cartesian_product +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -588,6 +588,7 @@ def lift(i, gen): # Infinitely generated # This does not return a good output, but it is "correct" # TODO: Figure out a better way to do things + from sage.categories.cartesian_product import cartesian_product gens_prod = cartesian_product([Family(M.monoid_generators(), lambda g: (i, g)) for i,M in enumerate(F)]) diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 8065f50b367..a76d6ab4aa3 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -31,14 +31,6 @@ import homset include "sage/ext/stdsage.pxi" from sage.structure.element cimport Element -def make_morphism(_class, parent, _dict, _slots): - # from element.pyx - cdef Morphism mor = _class.__new__(_class) - mor._set_parent(parent) - mor._update_slots(_slots) - if HAS_DICTIONARY(mor): - mor.__dict__ = _dict - return mor def is_Morphism(x): return isinstance(x, Morphism) @@ -170,7 +162,10 @@ cdef class Morphism(Map): sage: R. = ZZ[] sage: f = R.hom([t**2]) sage: f.category() - Category of endsets of unital magmas and right modules over (euclidean domains and infinite enumerated sets) and left modules over (euclidean domains and infinite enumerated sets) + Category of endsets of unital magmas and right modules over + (euclidean domains and infinite enumerated sets and metric spaces) + and left modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: K = CyclotomicField(12) sage: L = CyclotomicField(132) diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 5dc0113ada8..52baeec2e41 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -683,6 +683,9 @@ def is_antichain_of_poset(self, o): """ return all(not self.lt(x,y) for x in o for y in o) + CartesianProduct = LazyImport( + 'sage.combinat.posets.cartesian_product', 'CartesianProductPoset') + class ElementMethods: pass # TODO: implement xy, x>=y appropriately once #10130 is resolved diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index b8c70db515d..5e32c54e985 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -348,9 +348,12 @@ sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.categories() - [Join of Category of euclidean domains and Category of infinite enumerated sets, + [Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, Category of gcd domains, Category of integral domains, Category of domains, @@ -360,7 +363,8 @@ Category of commutative magmas, Category of unital magmas, Category of magmas, Category of commutative additive groups, ..., Category of additive magmas, Category of infinite enumerated sets, Category of enumerated sets, - Category of infinite sets, Category of sets, + Category of infinite sets, Category of metric spaces, + Category of topological spaces, Category of sets, Category of sets with partial maps, Category of objects] diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 7e55a539fd3..4373c1708f6 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2,10 +2,16 @@ Coercion via Construction Functors """ import six -from functor import Functor -from basic import * +from sage.misc.lazy_import import lazy_import +from functor import Functor, IdentityFunctor_generic -from sage.structure.parent import CoercionException +lazy_import('sage.categories.commutative_additive_groups', 'CommutativeAdditiveGroups') +lazy_import('sage.categories.commutative_rings', 'CommutativeRings') +lazy_import('sage.categories.groups', 'Groups') +lazy_import('sage.categories.objects', 'Objects') +lazy_import('sage.categories.rings', 'Rings') + +lazy_import('sage.structure.parent', 'CoercionException') # TODO, think through the rankings, and override pushout where necessary. @@ -172,7 +178,7 @@ def __cmp__(self, other): """ return cmp(type(self), type(other)) - def __str__(self): + def _repr_(self): """ NOTE: @@ -193,25 +199,6 @@ def __str__(self): import re return re.sub("<.*'.*\.([^.]*)'>", "\\1", s) - def _repr_(self): - """ - NOTE: - - By default, it returns the name of the construction functor's class. - Usually, this method will be overloaded. - - TEST:: - - sage: F = QQ.construction()[0] - sage: F # indirect doctest - FractionField - sage: Q = ZZ.quo(2).construction()[0] - sage: Q # indirect doctest - QuotientFunctor - - """ - return str(self) - def merge(self, other): """ Merge ``self`` with another construction functor, or return None. @@ -291,6 +278,89 @@ def expand(self): # See the pushout() function below for explanation. coercion_reversed = False + def common_base(self, other_functor, self_bases, other_bases): + r""" + This function is called by :func:`pushout` when no common parent + is found in the construction tower. + + .. NOTE:: + + The main use is for multivariate construction functors, + which use this function to implement recursion for + :func:`pushout`. + + INPUT: + + - ``other_functor`` -- a construction functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + OUTPUT: + + Nothing, since a + :class:`~sage.structure.coerce_exceptions.CoercionException` + is raised. + + .. NOTE:: + + Overload this function in derived class, see + e.e. :class:`MultivariateConstructionFunctor`. + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(QQ, cartesian_product([ZZ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + FractionField(Integer Ring) and The cartesian_product functorial construction(Integer Ring). + """ + self._raise_common_base_exception_( + other_functor, self_bases, other_bases) + + def _raise_common_base_exception_(self, other_functor, + self_bases, other_bases, + reason=None): + r""" + Raise a coercion exception. + + INPUT: + + - ``other_functor`` -- a functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + - ``reason`` -- a string or ``None`` (default). + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(QQ, cartesian_product([QQ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + FractionField(Integer Ring) and The cartesian_product functorial construction(Rational Field). + """ + if not isinstance(self_bases, (tuple, list)): + self_bases = (self_bases,) + if not isinstance(other_bases, (tuple, list)): + other_bases = (other_bases,) + if reason is None: + reason = '.' + else: + reason = ': ' + reason + '.' + raise CoercionException( + 'No common base ("join") found for %s(%s) and %s(%s)%s' % + (self, ', '.join(str(b) for b in self_bases), + other_functor, ', '.join(str(b) for b in other_bases), + reason)) + class CompositeConstructionFunctor(ConstructionFunctor): """ @@ -422,7 +492,7 @@ def __mul__(self, other): all = other.all + [self] return CompositeConstructionFunctor(*all) - def __str__(self): + def _repr_(self): """ TESTS:: @@ -487,6 +557,7 @@ def __init__(self): True """ + from sage.categories.sets_cat import Sets ConstructionFunctor.__init__(self, Sets(), Sets()) def _apply_functor(self, x): @@ -566,6 +637,83 @@ def __mul__(self, other): return self +class MultivariateConstructionFunctor(ConstructionFunctor): + """ + An abstract base class for functors that take + multiple inputs (e.g. cartesian products). + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: A = cartesian_product((QQ['z'], QQ)) + sage: B = cartesian_product((ZZ['t']['z'], QQ)) + sage: pushout(A, B) + The cartesian product of (Univariate Polynomial Ring in z over + Univariate Polynomial Ring in t over Rational Field, + Rational Field) + sage: A.construction() + (The cartesian_product functorial construction, + (Univariate Polynomial Ring in z over Rational Field, Rational Field)) + sage: pushout(A, B) + The cartesian product of (Univariate Polynomial Ring in z over Univariate Polynomial Ring in t over Rational Field, Rational Field) + """ + def common_base(self, other_functor, self_bases, other_bases): + r""" + This function is called by :func:`pushout` when no common parent + is found in the construction tower. + + INPUT: + + - ``other_functor`` -- a construction functor. + + - ``self_bases`` -- the arguments passed to this functor. + + - ``other_bases`` -- the arguments passed to the functor + ``other_functor``. + + OUTPUT: + + A parent. + + If no common base is found a :class:`sage.structure.coerce_exceptions.CoercionException` + is raised. + + .. NOTE:: + + Overload this function in derived class, see + e.g. :class:`MultivariateConstructionFunctor`. + + TESTS:: + + sage: from sage.categories.pushout import pushout + sage: pushout(cartesian_product([ZZ]), QQ) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(Integer Ring) and FractionField(Integer Ring): + (Multivariate) functors are incompatible. + sage: pushout(cartesian_product([ZZ]), cartesian_product([ZZ, QQ])) # indirect doctest + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(Integer Ring) and + The cartesian_product functorial construction(Integer Ring, Rational Field): + Functors need the same number of arguments. + """ + if self != other_functor: + self._raise_common_base_exception_( + other_functor, self_bases, other_bases, + '(Multivariate) functors are incompatible') + if len(self_bases) != len(other_bases): + self._raise_common_base_exception_( + other_functor, self_bases, other_bases, + 'Functors need the same number of arguments') + from sage.structure.element import get_coercion_model + Z_bases = tuple(get_coercion_model().common_parent(S, O) + for S, O in zip(self_bases, other_bases)) + return self(Z_bases) + + class PolynomialFunctor(ConstructionFunctor): """ Construction functor for univariate polynomial rings. @@ -650,7 +798,7 @@ def _apply_functor_to_morphism(self, f): TEST:: sage: P = ZZ['x'].construction()[0] - sage: P(ZZ.hom(GF(3))) + sage: P(ZZ.hom(GF(3))) # indirect doctest Ring morphism: From: Univariate Polynomial Ring in x over Integer Ring To: Univariate Polynomial Ring in x over Finite Field of size 3 @@ -720,7 +868,7 @@ def merge(self, other): else: return None - def __str__(self): + def _repr_(self): """ TEST:: @@ -891,7 +1039,7 @@ def expand(self): else: return [MultiPolynomialFunctor((x,), self.term_order) for x in reversed(self.vars)] - def __str__(self): + def _repr_(self): """ TEST:: @@ -1040,7 +1188,7 @@ def _apply_functor(self, R): from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing return InfinitePolynomialRing(R, self._gens, order=self._order, implementation=self._imple) - def __str__(self): + def _repr_(self): """ TEST:: @@ -1961,6 +2109,8 @@ def __init__(self): sage: F(ZZ['t']) Fraction Field of Univariate Polynomial Ring in t over Integer Ring """ + from sage.categories.integral_domains import IntegralDomains + from sage.categories.fields import Fields Functor.__init__(self, IntegralDomains(), Fields()) def _apply_functor(self, R): @@ -2084,7 +2234,7 @@ def __init__(self, p, prec, extras=None): if self.type not in self._dvr_types: raise ValueError("completion type must be one of %s"%(", ".join(self._dvr_types))) - def __str__(self): + def _repr_(self): """ TEST:: @@ -3034,6 +3184,7 @@ class BlackBoxConstructionFunctor(ConstructionFunctor): def __init__(self, box): """ TESTS:: + sage: from sage.categories.pushout import BlackBoxConstructionFunctor sage: FG = BlackBoxConstructionFunctor(gap) sage: FM = BlackBoxConstructionFunctor(maxima) @@ -3065,6 +3216,7 @@ def _apply_functor(self, R): def __cmp__(self, other): """ TESTS:: + sage: from sage.categories.pushout import BlackBoxConstructionFunctor sage: FG = BlackBoxConstructionFunctor(gap) sage: FM = BlackBoxConstructionFunctor(maxima) @@ -3208,7 +3360,7 @@ def pushout(R, S): ....: coercion_reversed = True ....: def __init__(self): ....: ConstructionFunctor.__init__(self, Rings(), Rings()) - ....: def __call__(self, R): + ....: def _apply_functor(self, R): ....: return EvenPolynomialRing(R.base(), R.variable_name()) ....: sage: pushout(EvenPolynomialRing(QQ, 'x'), ZZ) @@ -3235,13 +3387,233 @@ def pushout(R, S): sage: pushout(EvenPolynomialRing(QQ, 'x')^2, RR['x']^2) Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Real Field with 53 bits of precision + Some more tests related to univariate/multivariate + constructions. We consider a generalization of polynomial rings, + where in addition to the coefficient ring `C` we also specify + an additive monoid `E` for the exponents of the indeterminate. + In particular, the elements of such a parent are given by + + .. MATH:: + + \sum_{i=0}^I c_i X^{e_i} + + with `c_i \in C` and `e_i \in E`. We define + :: + + sage: class GPolynomialRing(Parent): + ....: def __init__(self, coefficients, var, exponents): + ....: self.coefficients = coefficients + ....: self.var = var + ....: self.exponents = exponents + ....: super(GPolynomialRing, self).__init__(category=Rings()) + ....: def _repr_(self): + ....: return 'Generalized Polynomial Ring in %s^(%s) over %s' % ( + ....: self.var, self.exponents, self.coefficients) + ....: def construction(self): + ....: return GPolynomialFunctor(self.var, self.exponents), self.coefficients + ....: def _coerce_map_from_(self, R): + ....: return self.coefficients.has_coerce_map_from(R) + + and + :: + + sage: class GPolynomialFunctor(ConstructionFunctor): + ....: rank = 10 + ....: def __init__(self, var, exponents): + ....: self.var = var + ....: self.exponents = exponents + ....: ConstructionFunctor.__init__(self, Rings(), Rings()) + ....: def _repr_(self): + ....: return 'GPoly[%s^(%s)]' % (self.var, self.exponents) + ....: def _apply_functor(self, coefficients): + ....: return GPolynomialRing(coefficients, self.var, self.exponents) + ....: def merge(self, other): + ....: if isinstance(other, GPolynomialFunctor) and self.var == other.var: + ....: exponents = pushout(self.exponents, other.exponents) + ....: return GPolynomialFunctor(self.var, exponents) + + We can construct a parent now in two different ways:: + + sage: GPolynomialRing(QQ, 'X', ZZ) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + sage: GP_ZZ = GPolynomialFunctor('X', ZZ); GP_ZZ + GPoly[X^(Integer Ring)] + sage: GP_ZZ(QQ) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + + Since the construction + :: + + sage: GP_ZZ(QQ).construction() + (GPoly[X^(Integer Ring)], Rational Field) + + uses the coefficient ring, we have the usual coercion with respect + to this parameter:: + + sage: pushout(GP_ZZ(ZZ), GP_ZZ(QQ)) + Generalized Polynomial Ring in X^(Integer Ring) over Rational Field + sage: pushout(GP_ZZ(ZZ['t']), GP_ZZ(QQ)) + Generalized Polynomial Ring in X^(Integer Ring) over Univariate Polynomial Ring in t over Rational Field + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(ZZ['b,c'])) + Generalized Polynomial Ring in X^(Integer Ring) + over Multivariate Polynomial Ring in a, b, c over Integer Ring + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(QQ['b,c'])) + Generalized Polynomial Ring in X^(Integer Ring) + over Multivariate Polynomial Ring in a, b, c over Rational Field + sage: pushout(GP_ZZ(ZZ['a,b']), GP_ZZ(ZZ['c,d'])) + Traceback (most recent call last): + ... + CoercionException: ('Ambiguous Base Extension', ...) + + :: + + sage: GP_QQ = GPolynomialFunctor('X', QQ) + sage: pushout(GP_ZZ(ZZ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Integer Ring + sage: pushout(GP_QQ(ZZ), GP_ZZ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Integer Ring + + :: + + sage: GP_ZZt = GPolynomialFunctor('X', ZZ['t']) + sage: pushout(GP_ZZt(ZZ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Integer Ring + + :: + + sage: pushout(GP_ZZ(ZZ), GP_QQ(QQ)) + Generalized Polynomial Ring in X^(Rational Field) over Rational Field + sage: pushout(GP_ZZ(QQ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Rational Field) over Rational Field + sage: pushout(GP_ZZt(QQ), GP_QQ(ZZ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Rational Field + sage: pushout(GP_ZZt(ZZ), GP_QQ(QQ)) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t + over Rational Field) over Rational Field + sage: pushout(GP_ZZt(ZZ['a,b']), GP_QQ(ZZ['c,d'])) + Traceback (most recent call last): + ... + CoercionException: ('Ambiguous Base Extension', ...) + sage: pushout(GP_ZZt(ZZ['a,b']), GP_QQ(ZZ['b,c'])) + Generalized Polynomial Ring in X^(Univariate Polynomial Ring in t over Rational Field) + over Multivariate Polynomial Ring in a, b, c over Integer Ring + + Some tests with cartesian products:: + + sage: from sage.sets.cartesian_product import CartesianProduct + sage: A = CartesianProduct((ZZ['x'], QQ['y'], QQ['z']), Sets().CartesianProducts()) + sage: B = CartesianProduct((ZZ['x'], ZZ['y'], ZZ['t']['z']), Sets().CartesianProducts()) + sage: A.construction() + (The cartesian_product functorial construction, + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Rational Field)) + sage: pushout(A, B) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Univariate Polynomial Ring in t over Rational Field) + sage: pushout(ZZ, cartesian_product([ZZ, QQ])) + Traceback (most recent call last): + ... + CoercionException: 'NoneType' object is not iterable + + :: + + sage: from sage.categories.pushout import PolynomialFunctor + sage: from sage.sets.cartesian_product import CartesianProduct + sage: class CartesianProductPoly(CartesianProduct): + ....: def __init__(self, polynomial_rings): + ....: sort = sorted(polynomial_rings, key=lambda P: P.variable_name()) + ....: super(CartesianProductPoly, self).__init__(sort, Sets().CartesianProducts()) + ....: def vars(self): + ....: return tuple(P.variable_name() for P in self.cartesian_factors()) + ....: def _pushout_(self, other): + ....: if isinstance(other, CartesianProductPoly): + ....: s_vars = self.vars() + ....: o_vars = other.vars() + ....: if s_vars == o_vars: + ....: return + ....: return pushout(CartesianProductPoly( + ....: self.cartesian_factors() + + ....: tuple(f for f in other.cartesian_factors() + ....: if f.variable_name() not in s_vars)), + ....: CartesianProductPoly( + ....: other.cartesian_factors() + + ....: tuple(f for f in self.cartesian_factors() + ....: if f.variable_name() not in o_vars))) + ....: C = other.construction() + ....: if C is None: + ....: return + ....: elif isinstance(C[0], PolynomialFunctor): + ....: return pushout(self, CartesianProductPoly((other,))) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), + ....: CartesianProductPoly((ZZ['y'],))) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring) + sage: pushout(CartesianProductPoly((ZZ['x'], ZZ['y'])), + ....: CartesianProductPoly((ZZ['x'], ZZ['z']))) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring, + Univariate Polynomial Ring in z over Integer Ring) + sage: pushout(CartesianProductPoly((QQ['a,b']['x'], QQ['y'])), + ....: CartesianProductPoly((ZZ['b,c']['x'], SR['z']))) + The cartesian product of + (Univariate Polynomial Ring in x over + Multivariate Polynomial Ring in a, b, c over Rational Field, + Univariate Polynomial Ring in y over Rational Field, + Univariate Polynomial Ring in z over Symbolic Ring) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), ZZ['y']) + The cartesian product of + (Univariate Polynomial Ring in x over Integer Ring, + Univariate Polynomial Ring in y over Integer Ring) + sage: pushout(QQ['b,c']['y'], CartesianProductPoly((ZZ['a,b']['x'],))) + The cartesian product of + (Univariate Polynomial Ring in x over + Multivariate Polynomial Ring in a, b over Integer Ring, + Univariate Polynomial Ring in y over + Multivariate Polynomial Ring in b, c over Rational Field) + + :: + + sage: pushout(CartesianProductPoly((ZZ['x'],)), ZZ) + Traceback (most recent call last): + ... + CoercionException: No common base ("join") found for + The cartesian_product functorial construction(...) and None(Integer Ring): + (Multivariate) functors are incompatible. + AUTHORS: - -- Robert Bradshaw + - Robert Bradshaw + - Peter Bruin + - Simon King + - Daniel Krenn + - David Roe """ if R is S or R == S: return R + if hasattr(R, '_pushout_'): + P = R._pushout_(S) + if P is not None: + return P + + if hasattr(S, '_pushout_'): + P = S._pushout_(R) + if P is not None: + return P + if isinstance(R, type): R = type_to_parent(R) @@ -3253,6 +3625,15 @@ def pushout(R, S): Rs = [c[1] for c in R_tower] Ss = [c[1] for c in S_tower] + # If there is a multivariate construction functor in the tower, we must chop off the end + # because tuples don't have has_coerce_map_from functions and to align with the + # modification of Rs and Ss below + from sage.structure.parent import Parent + if not isinstance(Rs[-1], Parent): + Rs = Rs[:-1] + if not isinstance(Ss[-1], Parent): + Ss = Ss[:-1] + if R in Ss: if not any(c[0].coercion_reversed for c in S_tower[1:]): return S @@ -3265,6 +3646,7 @@ def pushout(R, S): R_tower, S_tower = S_tower, R_tower # look for join + Z = None if Ss[-1] in Rs: if Rs[-1] == Ss[-1]: while Rs and Ss and Rs[-1] == Ss[-1]: @@ -3289,12 +3671,14 @@ def pushout(R, S): Ss.pop() Z = Rs.pop() + if Z is None and R_tower[-1][0] is not None: + Z = R_tower[-1][0].common_base(S_tower[-1][0], R_tower[-1][1], S_tower[-1][1]) + R_tower = expand_tower(R_tower[:len(Rs)]) + S_tower = expand_tower(S_tower[:len(Ss)]) else: - raise CoercionException("No common base") - - # Rc is a list of functors from Z to R and Sc is a list of functors from Z to S - R_tower = expand_tower(R_tower[:len(Rs)+1]) - S_tower = expand_tower(S_tower[:len(Ss)+1]) + # Rc is a list of functors from Z to R and Sc is a list of functors from Z to S + R_tower = expand_tower(R_tower[:len(Rs)+1]) + S_tower = expand_tower(S_tower[:len(Ss)+1]) Rc = [c[0] for c in R_tower[1:]] Sc = [c[0] for c in S_tower[1:]] @@ -3575,11 +3959,14 @@ def construction_tower(R): """ tower = [(None, R)] c = R.construction() + from sage.structure.parent import Parent while c is not None: f, R = c if not isinstance(f, ConstructionFunctor): f = BlackBoxConstructionFunctor(f) tower.append((f,R)) + if not isinstance(R, Parent): + break c = R.construction() return tower diff --git a/src/sage/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py index b1aecb420c9..85deb76afeb 100644 --- a/src/sage/categories/regular_crystals.py +++ b/src/sage/categories/regular_crystals.py @@ -453,8 +453,7 @@ def wt_zero(x): if checker(y): edges.append([x, y, i]) from sage.graphs.all import DiGraph - G = DiGraph(edges) - G.add_vertices(X) + G = DiGraph([X, edges], format="vertices_and_edges", immutable=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) @@ -560,7 +559,7 @@ def demazure_operator_simple(self, i, ring = None): sage: K = crystals.KirillovReshetikhin(['A',2,1],2,1) sage: t = K(rows=[[3],[2]]) sage: t.demazure_operator_simple(0) - B[[[2, 3]]] + B[[[1, 2]]] + B[[[1, 2]]] + B[[[2, 3]]] TESTS:: @@ -734,7 +733,7 @@ def _test_stembridge_local_axioms(self, index_set=None, verbose=False, **options Running with ``verbose=True`` will print warnings when a test fails. - REFERENCES:: + REFERENCES: .. [S2003] John R. Stembridge, A local characterization of simply-laced crystals, @@ -874,8 +873,8 @@ def dual_equivalence_class(self, index_set=None): if y not in visited: todo.add(y) from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(visited) + G = Graph([visited, edges], format="vertices_and_edges", + immutable=True, multiedges=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 5f868cb201c..41e312055f3 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -580,16 +580,17 @@ def quotient(self, I, names=None): sage: F. = FreeAlgebra(QQ) sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) n: - from random import sample - S = sample(S, n) - - for x, y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertEqual(x==y, y==x, LazyFormat("non symmetric equality: %s but %s")%( print_compare(x, y), print_compare(y, x))) @@ -1280,14 +1297,9 @@ def _test_elements_neq(self, **options): """ tester = self._tester(**options) S = list(tester.some_elements()) + [None, 0] - n = tester._max_runs - from sage.combinat.cartesian_product import CartesianProduct - S = CartesianProduct(S,S) - if len(S) > n: - from random import sample - S = sample(S, n) - for x,y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertNotEqual(x == y, x != y, LazyFormat("__eq__ and __ne__ inconsistency:\n" " %s == %s returns %s but %s != %s returns %s")%( @@ -1400,10 +1412,32 @@ def _test_cardinality(self, **options): # Functorial constructions CartesianProduct = CartesianProduct - def cartesian_product(*parents): + def cartesian_product(*parents, **kwargs): """ Return the cartesian product of the parents. + INPUT: + + - ``parents`` -- a list (or other iterable) of parents. + + - ``category`` -- (default: ``None``) the category the + cartesian product belongs to. If ``None`` is passed, + then + :meth:`~sage.categories.covariant_functorial_construction.CovariantFactorialConstruction.category_from_parents` + is used to determine the category. + + - ``extra_category`` -- (default: ``None``) a category + that is added to the cartesian product in addition + to the categories obtained from the parents. + + - other keyword arguments will passed on to the class used + for this cartesian product (see also + :class:`~sage.sets.cartesian_product.CartesianProduct`). + + OUTPUT: + + The cartesian product. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ) @@ -1423,10 +1457,31 @@ def cartesian_product(*parents): sage: C.category() Join of Category of rings and ... and Category of Cartesian products of commutative additive groups + + :: + + sage: cartesian_product([ZZ, ZZ], category=Sets()).category() + Category of sets + sage: cartesian_product([ZZ, ZZ]).category() + Join of + Category of Cartesian products of commutative rings and + Category of Cartesian products of enumerated sets + sage: cartesian_product([ZZ, ZZ], extra_category=Posets()).category() + Join of + Category of Cartesian products of commutative rings and + Category of posets and + Category of Cartesian products of enumerated sets """ - return parents[0].CartesianProduct( - parents, - category = cartesian_product.category_from_parents(parents)) + category = kwargs.pop('category', None) + extra_category = kwargs.pop('extra_category', None) + + category = category or cartesian_product.category_from_parents(parents) + if extra_category: + if isinstance(category, (list, tuple)): + category = tuple(category) + (extra_category,) + else: + category = category & extra_category + return parents[0].CartesianProduct(parents, category=category, **kwargs) def algebra(self, base_ring, category=None): """ @@ -1655,6 +1710,10 @@ def __invert__(self): Facade = LazyImport('sage.categories.facade_sets', 'FacadeSets') Finite = LazyImport('sage.categories.finite_sets', 'FiniteSets', at_startup=True) + Topological = LazyImport('sage.categories.topological_spaces', + 'TopologicalSpaces', 'Topological', at_startup=True) + Metric = LazyImport('sage.categories.metric_spaces', 'MetricSpaces', + 'Mertic', at_startup=True) class Infinite(CategoryWithAxiom): @@ -1986,6 +2045,105 @@ def example(self): class ParentMethods: + def __iter__(self): + r""" + Return a lexicographic iterator for the elements of this cartesian product. + + EXAMPLES:: + + sage: for x,y in cartesian_product([Set([1,2]), Set(['a','b'])]): + ....: print x,y + 1 a + 1 b + 2 a + 2 b + + sage: A = FiniteEnumeratedSets()(["a", "b"]) + sage: B = FiniteEnumeratedSets().example(); B + An example of a finite enumerated set: {1,2,3} + sage: C = cartesian_product([A, B, A]); C + The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) + sage: C in FiniteEnumeratedSets() + True + sage: list(C) + [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), + ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] + sage: C.__iter__.__module__ + 'sage.categories.enumerated_sets' + + sage: F22 = GF(2).cartesian_product(GF(2)) + sage: list(F22) + [(0, 0), (0, 1), (1, 0), (1, 1)] + + sage: C = cartesian_product([Permutations(10)]*4) + sage: it = iter(C) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) + + .. WARNING:: + + The elements are returned in lexicographic order, + which gives a valid enumeration only if all + factors, but possibly the first one, are + finite. So the following one is fine:: + + sage: it = iter(cartesian_product([ZZ, GF(2)])) + sage: [next(it) for _ in range(10)] + [(0, 0), (0, 1), (1, 0), (1, 1), + (-1, 0), (-1, 1), (2, 0), (2, 1), + (-2, 0), (-2, 1)] + + But this one is not:: + + sage: it = iter(cartesian_product([GF(2), ZZ])) + sage: [next(it) for _ in range(10)] + doctest:...: UserWarning: Sage is not able to determine + whether the factors of this cartesian product are + finite. The lexicographic ordering might not go through + all elements. + [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), + (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] + + .. NOTE:: + + Here it would be faster to use :func:`itertools.product` for sets + of small size. But the latter expands all factor in memory! + So we can not reasonably use it in general. + + ALGORITHM: + + Recipe 19.9 in the Python Cookbook by Alex Martelli + and David Ascher. + """ + if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): + from warnings import warn + warn("Sage is not able to determine whether the factors of " + "this cartesian product are finite. The lexicographic " + "ordering might not go through all elements.") + + # visualize an odometer, with "wheels" displaying "digits"...: + factors = list(self.cartesian_factors()) + wheels = map(iter, factors) + digits = [next(it) for it in wheels] + while True: + yield self._cartesian_product_of_elements(digits) + for i in range(len(digits)-1, -1, -1): + try: + digits[i] = next(wheels[i]) + break + except StopIteration: + wheels[i] = iter(factors[i]) + digits[i] = next(wheels[i]) + else: + break @cached_method def an_element(self): @@ -2034,7 +2192,15 @@ def is_finite(self): True """ f = self.cartesian_factors() - return any(c.is_empty() for c in f) or all(c.is_finite() for c in f) + try: + # Note: some parent might not implement "is_empty". So we + # carefully isolate this test. + test = any(c.is_empty() for c in f) + except (AttributeError, NotImplementedError): + pass + else: + if test: return test + return all(c.is_finite() for c in f) def cardinality(self): r""" @@ -2067,7 +2233,7 @@ def cardinality(self): # Note: some parent might not implement "is_empty". So we # carefully isolate this test. is_empty = any(c.is_empty() for c in f) - except Exception: + except (AttributeError,NotImplementedError): pass else: if is_empty: @@ -2580,3 +2746,6 @@ def _repr_(self): The subset algebra of {1, 2, 3} over Rational Field in the realization Blah """ return "{} in the realization {}".format(self.realization_of(), self._realization_name()) + +# Moved from sage.categories.cartesian_product to avoid circular import errors +cartesian_product = CartesianProductFunctor() diff --git a/src/sage/categories/simplicial_complexes.py b/src/sage/categories/simplicial_complexes.py new file mode 100644 index 00000000000..aa19a8a277b --- /dev/null +++ b/src/sage/categories/simplicial_complexes.py @@ -0,0 +1,101 @@ +""" +Simplicial Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +#from sage.categories.cw_complexes import CWComplexes +from sage.categories.sets_cat import Sets + +class SimplicialComplexes(Category_singleton): + r""" + The category of abstract simplicial complexes. + + An abstract simplicial complex `A` is a collection of sets `X` + such that: + + - `\emptyset \in A`, + - if `X \subset Y \in A`, then `X \in A`. + + .. TODO:: + + Implement the category of simplicial complexes considered + as :class:`CW complexes ` + and rename this to the category of ``AbstractSimplicialComplexes`` + with appropriate functors. + + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: C = SimplicialComplexes(); C + Category of simplicial complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: SimplicialComplexes().super_categories() + [Category of sets] + """ + return [Sets()] + + class Finite(CategoryWithAxiom): + """ + Category of finite simplicial complexes. + """ + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.dimension() + 2 + """ + return max(c.dimension() for c in self.facets()) + + class ParentMethods: + @abstract_method + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.facets() + {(1, 2), (1, 3, 4), (2, 5), (4, 5)} + """ + + @abstract_method + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.faces() + {-1: {()}, + 0: {(1,), (2,), (3,), (4,), (5,)}, + 1: {(1, 2), (1, 3), (1, 4), (2, 5), (3, 4), (4, 5)}, + 2: {(1, 3, 4)}} + """ + diff --git a/src/sage/categories/super_algebras.py b/src/sage/categories/super_algebras.py new file mode 100644 index 00000000000..6071afdbce8 --- /dev/null +++ b/src/sage/categories/super_algebras.py @@ -0,0 +1,67 @@ +r""" +Super Algebras +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.misc.lazy_import import LazyImport + +class SuperAlgebras(SuperModulesCategory): + """ + The category of super algebras. + + An `R`-*super algebra* is an `R`-super module `A` endowed with an + `R`-algebra structure satisfying + + .. MATH:: + + A_0 A_0 \subseteq A_0, \qquad + A_0 A_1 \subseteq A_1, \qquad + A_1 A_0 \subseteq A_1, \qquad + A_1 A_1 \subseteq A_0 + + and `1 \in A_0`. + + EXAMPLES:: + + sage: Algebras(ZZ).Super() + Category of super algebras over Integer Ring + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Super()).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Algebras(ZZ).Super().super_categories() # indirect doctest + [Category of graded algebras over Integer Ring, + Category of super modules over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + .. WARNING:: + + Because a super module `M` is naturally `\ZZ / 2 \ZZ`-graded, and + graded modules have a natural filtration induced by the grading, if + `M` has a different filtration, then the associated graded module + `\operatorname{gr} M \neq M`. This is most apparent with super + algebras, such as the :class:`differential Weyl algebra + `, and the + multiplication may not coincide. + """ + raise NotImplementedError + diff --git a/src/sage/categories/super_algebras_with_basis.py b/src/sage/categories/super_algebras_with_basis.py new file mode 100644 index 00000000000..9a4a1bc05a0 --- /dev/null +++ b/src/sage/categories/super_algebras_with_basis.py @@ -0,0 +1,61 @@ +r""" +Super algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules + +class SuperAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super algebras with a distinguished basis + + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super(); C + Category of super algebras with basis over Integer Ring + + TESTS:: + + sage: TestSuite(C).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super() + sage: sorted(C.super_categories(), key=str) # indirect doctest + [Category of graded algebras with basis over Integer Ring, + Category of super algebras over Integer Ring, + Category of super modules with basis over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + .. SEEALSO:: + + :meth:`~sage.categories.filtered_modules_with_basis.ParentMethods.graded_algebra` + + EXAMPLES:: + + sage: W. = algebras.DifferentialWeyl(QQ) + sage: W.graded_algebra() + Graded Algebra of Differential Weyl algebra of + polynomials in x, y over Rational Field + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + diff --git a/src/sage/categories/super_hopf_algebras_with_basis.py b/src/sage/categories/super_hopf_algebras_with_basis.py new file mode 100644 index 00000000000..28347832bc4 --- /dev/null +++ b/src/sage/categories/super_hopf_algebras_with_basis.py @@ -0,0 +1,30 @@ +r""" +Super Hopf algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperHopfAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super Hopf algebras with a distinguished basis. + + EXAMPLES:: + + sage: C = HopfAlgebras(ZZ).WithBasis().Super(); C + Category of super hopf algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of super algebras with basis over Integer Ring, + Category of super coalgebras with basis over Integer Ring, + Category of super hopf algebras over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + diff --git a/src/sage/categories/super_modules.py b/src/sage/categories/super_modules.py new file mode 100644 index 00000000000..a0a06dddf3d --- /dev/null +++ b/src/sage/categories/super_modules.py @@ -0,0 +1,229 @@ +r""" +Super modules +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import CovariantConstructionCategory +from sage.categories.modules import Modules + +# Note, a commutative algebra is not a commutative super algebra, +# therefore the following whitelist. +axiom_whitelist = frozenset(["Facade", "Finite", "Infinite", + "FiniteDimensional", "Connected", "WithBasis", + # "Commutative", + "Associative", "Inverse", "Unital", "Division", + "AdditiveCommutative", "AdditiveAssociative", + "AdditiveInverse", "AdditiveUnital", + "NoZeroDivisors", "Distributive"]) + +class SuperModulesCategory(CovariantConstructionCategory, Category_over_base_ring): + @classmethod + def default_super_categories(cls, category, *args): + """ + Return the default super categories of `F_{Cat}(A,B,...)` for + `A,B,...` parents in `Cat`. + + INPUT: + + - ``cls`` -- the category class for the functor `F` + - ``category`` -- a category `Cat` + - ``*args`` -- further arguments for the functor + + OUTPUT: + + A join category. + + This implements the property that subcategories constructed by + the set of whitelisted axioms is a subcategory. + + EXAMPLES:: + + sage: HopfAlgebras(ZZ).WithBasis().FiniteDimensional().Super() # indirect doctest + Category of finite dimensional super hopf algebras with basis over Integer Ring + """ + axioms = axiom_whitelist.intersection(category.axioms()) + C = super(SuperModulesCategory, cls).default_super_categories(category, *args) + return C._with_axioms(axioms) + + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Super() + sage: C + Category of super algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of graded algebras over Rational Field, + Category of super modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + """ + super(SuperModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Super" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Super() # indirect doctest + Category of super algebras with basis over Rational Field + """ + return "super {}".format(self.base_category()._repr_object_names()) + +class SuperModules(SuperModulesCategory): + r""" + The category of super modules. + + An `R`-*super module* (where `R` is a ring) is an `R`-module `M` equipped + with a decomposition `M = M_0 \oplus M_1` into two `R`-submodules + `M_0` and `M_1` (called the *even part* and the *odd part* of `M`, + respectively). + + Thus, an `R`-super module automatically becomes a `\ZZ / 2 \ZZ`-graded + `R`-module, with `M_0` being the degree-`0` component and `M_1` being the + degree-`1` component. + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + The category of super modules defines the super structure which + shall be preserved by morphisms:: + + sage: Modules(ZZ).Super().additional_structure() + Category of super modules over Integer Ring + + TESTS:: + + sage: TestSuite(Modules(ZZ).Super()).run() + """ + def super_categories(self): + """ + EXAMPLES:: + + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + Nota bene:: + + sage: Modules(QQ).Super() + Category of super modules over Rational Field + sage: Modules(QQ).Super().super_categories() + [Category of graded modules over Rational Field] + """ + return [self.base_category().Graded()] + + def extra_super_categories(self): + r""" + Adds :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Super().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Super().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Super()`` returns an + instance of :class:`SuperModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Super()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class ParentMethods: + pass + + class ElementMethods: + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element or ``1`` + if an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even_odd() + 1 + sage: C.basis()[2,2].is_even_odd() + 0 + """ + return self.degree() % 2 + + def is_even(self): + """ + Return if ``self`` is an even element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even() + False + sage: C.basis()[2,2].is_even() + True + """ + return self.is_even_odd() == 0 + + def is_odd(self): + """ + Return if ``self`` is an odd element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_odd() + True + sage: C.basis()[2,2].is_odd() + False + """ + return self.is_even_odd() == 1 + diff --git a/src/sage/categories/super_modules_with_basis.py b/src/sage/categories/super_modules_with_basis.py new file mode 100644 index 00000000000..43b3dc1215a --- /dev/null +++ b/src/sage/categories/super_modules_with_basis.py @@ -0,0 +1,185 @@ +r""" +Super modules with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperModulesWithBasis(SuperModulesCategory): + """ + The category of super modules with a distinguished basis. + + An `R`-*super module with a distinguished basis* is an + `R`-super module equipped with an `R`-module basis whose elements are + homogeneous. + + EXAMPLES:: + + sage: C = GradedModulesWithBasis(ZZ); C + Category of graded modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Graded() + True + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def _even_odd_on_basis(self, m): + """ + Return the parity of the basis element indexed by ``m``. + + OUTPUT: + + ``0`` if ``m`` is for an even element or ``1`` if ``m`` + is for an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: C._even_odd_on_basis((0,)) + 1 + sage: C._even_odd_on_basis((0,1)) + 0 + """ + return self.degree_on_basis(m) % 2 + + class ElementMethods: + def is_super_homogeneous(self): + r""" + Return whether this element is homogeneous, in the sense + of a super module (i.e., is even or odd). + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_super_homogeneous() + True + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a = x*y + x - 3*y + 4 + sage: a.is_super_homogeneous() + False + + The exterior algebra has a `\ZZ` grading, which induces the + `\ZZ / 2\ZZ` grading. However the definition of homogeneous + elements differs because of the different gradings:: + + sage: E. = ExteriorAlgebra(QQ) + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a.is_homogeneous() + False + """ + even_odd = self.parent()._even_odd_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = even_odd(m) + else: + if degree != even_odd(m): + return False + return True + + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element and ``1`` if + ``self`` is an odd element. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_even_odd() + 1 + sage: a = x*y + 4 + sage: a.is_even_odd() + 0 + sage: a = x + 4 + sage: a.is_even_odd() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + sage: E. = ExteriorAlgebra(QQ) + sage: (x*y).is_even_odd() + 0 + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_super_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent()._even_odd_on_basis(self.leading_support()) + + def even_component(self): + """ + Return the even component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.even_component() + x*y + 4 + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x + y + sage: a.even_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 0) + + def odd_component(self): + """ + Return the odd component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.odd_component() + x - 3*y + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x*y + sage: a.odd_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 1) + diff --git a/src/sage/categories/topological_spaces.py b/src/sage/categories/topological_spaces.py new file mode 100644 index 00000000000..41f98e0decc --- /dev/null +++ b/src/sage/categories/topological_spaces.py @@ -0,0 +1,108 @@ +r""" +Topological Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.sets_cat import Sets + +class TopologicalSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Topological" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Topological() # indirect doctest + Category of topological groups + """ + return "topological {}".format(self.base_category()._repr_object_names()) + +class TopologicalSpaces(TopologicalSpacesCategory): + """ + The category of topological spaces. + + EXAMPLES:: + + sage: Sets().Topological() + Category of topological spaces + sage: Sets().Topological().super_categories() + [Category of sets] + + The category of topological spaces defines the topological structure, + which shall be preserved by morphisms:: + + sage: Sets().Topological().additional_structure() + Category of topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological()).run() + """ + # We must override the general object because the names don't match + _base_category_class = (Sets,) + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Topological() # indirect doctest + Category of topological spaces + """ + return "topological spaces" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Connected() + Category of connected topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Connected()).run() + sage: Sets().Topological().Connected.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Connected') + + @cached_method + def Compact(self): + """ + Return the subcategory of the compact objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Compact() + Category of compact topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Compact()).run() + sage: Sets().Topological().Compact.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Compact') + + class Connected(CategoryWithAxiom): + """ + The category of connected topological spaces. + """ + + class Compact(CategoryWithAxiom): + """ + The category of compact topological spaces. + """ + diff --git a/src/sage/categories/unique_factorization_domains.py b/src/sage/categories/unique_factorization_domains.py index 50c6fd107a4..ddedb674d3b 100644 --- a/src/sage/categories/unique_factorization_domains.py +++ b/src/sage/categories/unique_factorization_domains.py @@ -124,6 +124,87 @@ def is_unique_factorization_domain(self, proof=True): """ return True + def _gcd_univariate_polynomial(self, f, g): + """ + Return the greatest common divisor of ``f`` and ``g``. + + INPUT: + + - ``f``, ``g`` -- two polynomials defined over this UFD. + + .. NOTE:: + + This is a helper method for + :meth:`sage.rings.polynomial.polynomial_element.Polynomial.gcd`. + + ALGORITHM: + + Algorithm 3.3.1 in [GTM138]_, based on pseudo-division. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if f.degree() < g.degree(): + A,B = g, f + else: + A,B = f, g + + if B.is_zero(): + return A + + a = b = self.zero() + for c in A.coefficients(): + a = a.gcd(c) + if a.is_one(): + break + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + parent = f.parent() + + d = a.gcd(b) + A = parent(A/a) + B = parent(B/b) + g = h = 1 + + delta = A.degree()-B.degree() + _,R = A.pseudo_quo_rem(B) + + while R.degree() > 0: + A = B + B = parent(R/(g*h**delta)) + g = A.leading_coefficient() + h = self(h*g**delta/h**delta) + delta = A.degree() - B.degree() + _, R = A.pseudo_quo_rem(B) + + if R.is_zero(): + b = self.zero() + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + return parent(d*B/b) + + return d + class ElementMethods: # prime? # squareFree diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index 84e1b6e81f5..5895ac96efc 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -1,7 +1,6 @@ from sage.misc.lazy_import import lazy_import -from code_constructions import (permutation_action, - walsh_matrix,cyclotomic_cosets) +from code_constructions import (permutation_action, walsh_matrix,cyclotomic_cosets) from sage.misc.superseded import deprecated_callable_import deprecated_callable_import(15445, @@ -62,14 +61,14 @@ elias_bound_asymp, mrrw1_bound_asymp) -from linear_code import (LinearCode, LinearCodeFromVectorSpace, - best_known_linear_code, - best_known_linear_code_www, - bounds_minimum_distance, - self_orthogonal_binary_codes) +lazy_import("sage.coding.linear_code", ["LinearCode",\ + "LinearCodeFromVectorSpace",\ + "best_known_linear_code",\ + "best_known_linear_code_www",\ + "bounds_minimum_distance", + "self_orthogonal_binary_codes"]) from sd_codes import self_dual_codes_binary - lazy_import("sage.coding.delsarte_bounds", ["Krawtchouk", "delsarte_bound_hamming_space", "delsarte_bound_additive_hamming_space"]) diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index d97b3b99c87..9af7b3a908c 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -22,7 +22,7 @@ CyclicCode, CyclicCodeFromCheckPolynomial, DuadicCodeEvenPair, DuadicCodeOddPair, ExtendedBinaryGolayCode, ExtendedQuadraticResidueCode, ExtendedTernaryGolayCode, - HammingCode, LinearCodeFromCheckMatrix, + HammingCode, LinearCode, LinearCodeFromCheckMatrix, QuadraticResidueCode, QuadraticResidueCodeEvenPair, QuadraticResidueCodeOddPair, RandomLinearCode, ReedSolomonCode, TernaryGolayCode, @@ -30,6 +30,7 @@ from guava import BinaryReedMullerCode, QuasiQuadraticResidueCode, RandomLinearCodeGuava -from sage.misc.rest_index_of_methods import gen_rest_table_index -import sys -__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__], only_local_functions=False)) +import encoders_catalog as encoders +from sage.misc.rest_index_of_methods import gen_rest_table_index as _gen_rest_table_index +import sys as _sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=_gen_rest_table_index(_sys.modules[__name__], only_local_functions=False)) diff --git a/src/sage/coding/encoder.py b/src/sage/coding/encoder.py new file mode 100644 index 00000000000..d5051e7ccfe --- /dev/null +++ b/src/sage/coding/encoder.py @@ -0,0 +1,325 @@ +r""" +Encoder + +Representation of a bijection between a message space and a code. +""" + +#***************************************************************************** +# Copyright (C) 2015 David Lucas +# +# 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.modules.free_module_element import vector +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.structure.sage_object import SageObject + +class Encoder(SageObject): + r""" + Abstract top-class for :class:`Encoder` objects. + + Every encoder class should inherit from this abstract class. + + To implement an encoder, you need to: + + - inherit from :class:`Encoder`, + + - call ``Encoder.__init__`` in the subclass constructor. + Example: ``super(SubclassName, self).__init__(code)``. + By doing that, your subclass will have its ``code`` parameter initialized. + + - Then, if the message space is a vector space, default implementations of :meth:`encode` and + :meth:`unencode_nocheck` methods are provided. These implementations rely on :meth:`generator_matrix` + which you need to override to use the default implementations. + + - If the message space is not of the form `F^k`, where `F` is a finite field, + you cannot have a generator matrix. + In that case, you need to override :meth:`encode`, :meth:`unencode_nocheck` and + :meth:`message_space`. + + - By default, comparison of :class:`Encoder` (using methods ``__eq__`` and ``__ne__`` ) are + by memory reference: if you build the same encoder twice, they will be different. If you + need something more clever, override ``__eq__`` and ``__ne__`` in your subclass. + + - As :class:`Encoder` is not designed to be instantiated, it does not have any representation + methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + + REFERENCES: + + .. [Nielsen] Johan S. R. Nielsen, (https://bitbucket.org/jsrn/codinglib/) + """ + + def __init__(self, code): + r""" + Initializes mandatory parameters for an :class:`Encoder` object. + + This method only exists for inheritance purposes as it initializes + parameters that need to be known by every linear code. An abstract + encoder object should never be created. + + INPUT: + + - ``code`` -- the associated code of ``self`` + + EXAMPLES: + + We first create a new :class:`Encoder` subclass:: + + sage: class EncoderExample(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(EncoderExample, self).__init__(code) + + We now create a member of our newly made class:: + + sage: G = Matrix(GF(2), [[1, 0, 0, 1], [0, 1, 1, 1]]) + sage: C = LinearCode(G) + sage: E = EncoderExample(C) + + We can check its parameters:: + + sage: E.code() + Linear code of length 4, dimension 2 over Finite Field of size 2 + """ + self._code = code + + def encode(self, word): + r""" + Transforms an element of the message space into a codeword. + + This is a default implementation which assumes that the message + space of the encoder is `F^{k}`, where `F` is + :meth:`sage.coding.linear_code.AbstractLinearCode.base_field` + and `k` is :meth:`sage.coding.linear_code.AbstractLinearCode.dimension`. + If this is not the case, this method should be overwritten by the subclass. + + .. NOTE:: + + :meth:`encode` might be a partial function over ``self``'s :meth:`message_space`. + One should use the exception :class:`EncodingError` to catch attempts + to encode words that are outside of the message space. + + INPUT: + + - ``word`` -- a vector of the message space of the ``self``. + + OUTPUT: + + - a vector of :meth:`code`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector(GF(2), (0, 1, 1, 0)) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + If ``word`` is not in the message space of ``self``, it will return an exception:: + + sage: word = random_vector(GF(7), 4) + sage: E.encode(word) + Traceback (most recent call last): + ... + ValueError: The value to encode must be in Vector space of dimension 4 over Finite Field of size 2 + """ + M = self.message_space() + if word not in M: + raise ValueError("The value to encode must be in %s" % M) + return vector(word) * self.generator_matrix() + + def unencode(self, c, nocheck=False): + r""" + Returns the message corresponding to the codeword ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a vector of the same length as :meth:`code` over the + base field of :meth:`code`. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + OUTPUT: + + - an element of the message space of ``self`` + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode(c) + (0, 1, 1, 0) + + TESTS: + + If ``nocheck`` is set to ``False``, and one provides a word which is not in + :meth:`code`, :meth:`unencode` will return an error:: + + sage: c = vector(GF(2), (0, 1, 0, 0, 1, 1, 0)) + sage: c in C + False + sage: E.unencode(c, False) + Traceback (most recent call last): + ... + EncodingError: Given word is not in the code + + If ones tries to unencode a codeword of a code of dimension 0, it + returns the empty vector:: + + sage: G = Matrix(GF(17), []) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: c = C.random_element() + sage: E.unencode(c) + () + """ + if nocheck == False and c not in self.code(): + raise EncodingError("Given word is not in the code") + return self.unencode_nocheck(c) + + @cached_method + def _unencoder_matrix(self): + r""" + Finds an information set for the matrix ``G`` returned by :meth:`generator_matrix`, + and returns the inverse of that submatrix of ``G``. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E._unencoder_matrix() + ( + [0 0 1 1] + [0 1 0 1] + [1 1 1 0] + [0 1 1 1], (0, 1, 2, 3) + ) + """ + info_set = self.code().information_set() + Gt = self.generator_matrix().matrix_from_columns(info_set) + return (Gt.inverse(), info_set) + + def unencode_nocheck(self, c): + r""" + Returns the message corresponding to ``c``. + + When ``c`` is not a codeword, the output is unspecified. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + INPUT: + + - ``c`` -- a vector of the same length as ``self`` over the + base field of ``self`` + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + + Taking a vector that does not belong to ``C`` will not raise an error but + probably just give a non-sensical result:: + + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 1)) + sage: c in C + False + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + sage: m = vector(GF(2), (0, 1, 1, 0)) + sage: c1 = E.encode(m) + sage: c == c1 + False + """ + U, info_set = self._unencoder_matrix() + cc = vector(self.code().base_ring(), [c[i] for i in info_set]) + return cc * U + + def code(self): + r""" + Returns the code for this :class:`Encoder`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.code() == C + True + """ + return self._code + + def message_space(self): + r""" + Returns the ambient space of allowed input to :meth:`encode`. + Note that :meth:`encode` is possibly a partial function over + the ambient space. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.message_space() + Vector space of dimension 4 over Finite Field of size 2 + """ + return self.code().base_field()**(self.code().dimension()) + + @abstract_method(optional = True) + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + This is an abstract method and it should be implemented separately. + Reimplementing this for each subclass of :class:`Encoder` is not mandatory + (as a generator matrix only makes sense when the message space is of the `F^k`, + where `F` is the base field of :meth:`code`.) + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + +class EncodingError(Exception): + r""" + Special exception class to indicate an error during encoding or unencoding. + """ + pass diff --git a/src/sage/coding/encoders_catalog.py b/src/sage/coding/encoders_catalog.py new file mode 100644 index 00000000000..36a0f84e34a --- /dev/null +++ b/src/sage/coding/encoders_catalog.py @@ -0,0 +1,16 @@ +r""" +Index of encoders + +The ``codes.encoders`` object may be used to access the encoders that Sage can build. + +:class:`linear_code.LinearCodeGeneratorMatrixEncoder ` + +.. NOTE:: + + To import these names into the global namespace, use: + + sage: from sage.coding.encoders_catalog import * +""" + +from sage.misc.lazy_import import lazy_import as _lazy_import +_lazy_import('sage.coding.linear_code', 'LinearCodeGeneratorMatrixEncoder') diff --git a/src/sage/coding/guava.py b/src/sage/coding/guava.py index c555d28db82..43842884356 100644 --- a/src/sage/coding/guava.py +++ b/src/sage/coding/guava.py @@ -45,7 +45,7 @@ def BinaryReedMullerCode(r,k): order r is a code with length `2^k` and minimum distance `2^k-r` (see for example, section 1.10 in [HP]_). By definition, the `r^{th}` order binary Reed-Muller code of length `n=2^m`, for - `0 \leq r \leq m`, is the set of all vectors `(f(p)\ |\ p \\in GF(2)^m)`, + `0 \leq r \leq m`, is the set of all vectors `(f(p)\ |\ p \in GF(2)^m)`, where `f` is a multivariate polynomial of degree at most `r` in `m` variables. diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index fbbf2287c35..f39e6e6d77c 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -216,6 +216,7 @@ import sage.modules.free_module as fm import sage.modules.module as module from sage.categories.modules import Modules +from copy import copy from sage.interfaces.all import gap from sage.rings.finite_rings.constructor import FiniteField as GF from sage.groups.perm_gps.permgroup import PermutationGroup @@ -237,7 +238,7 @@ from sage.misc.decorators import rename_keyword from sage.misc.cachefunc import cached_method from sage.misc.superseded import deprecated_function_alias - +from encoder import Encoder ZZ = IntegerRing() VectorSpace = fm.VectorSpace @@ -438,8 +439,8 @@ def best_known_linear_code(n, k, F): This means that best possible binary linear code of length 10 and dimension 5 is a code with minimum distance 4 and covering radius - somewhere between 2 and 4. Use ``minimum_distance_why(10,5,GF(2))`` or - ``print bounds_minimum_distance(10,5,GF(2))`` for further details. + somewhere between 2 and 4. + Use ``bounds_minimum_distance(10,5,GF(2))`` for further details. """ q = F.order() C = gap("BestKnownLinearCode(%s,%s,GF(%s))"%(n,k,q)) @@ -530,8 +531,7 @@ def bounds_minimum_distance(n, k, F): The values for the lower and upper bound are obtained from a table constructed by Cen Tjhai for GUAVA, derived from the table of - Brouwer. (See http://www.codetables.de/ or use the - Sage function ``minimum_distance_why`` for the most recent data.) + Brouwer. See http://www.codetables.de/ for the most recent data. These tables contain lower and upper bounds for `q=2` (when ``n <= 257``), `q=3` (when ``n <= 243``), `q=4` (``n <= 256``). (Current as of 11 May 2006.) For codes over other fields and for larger word lengths, @@ -710,12 +710,6 @@ class AbstractLinearCode(module.Module): So, every Linear Code-related class should inherit from this abstract class. - This class provides: - - - ``length``, the length of the code - - - numerous methods that will work for any linear code (including families) - To implement a linear code, you need to: - inherit from AbstractLinearCode @@ -727,19 +721,35 @@ class AbstractLinearCode(module.Module): You need of course to complete the constructor by adding any additional parameter needed to describe properly the code defined in the subclass. - - reimplement ``generator_matrix()`` method + - fill the dictionary of its encoders in ``sage.coding.__init__.py`` file. Example: + I want to link the encoder ``MyEncoderClass`` to ``MyNewCodeClass`` + under the name ``MyEncoderName``. + All I need to do is to write this line in the ``__init__.py`` file: + ``MyNewCodeClass._registered_encoders["NameOfMyEncoder"] = MyEncoderClass`` and all instances of + ``MyNewCodeClass`` will be able to use instances of ``MyEncoderClass``. As AbstractLinearCode is not designed to be implemented, it does not have any representation - methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + methods. You should implement ``_repr_`` and ``_latex_`` methods in the subclass. .. NOTE:: - AbstractLinearCode embeds some generic implementations of helper methods like ``__cmp__`` or ``__eq__``. - As they are designed to fit for every linear code, they mostly use the generator matrix - and thus can be long for certain families of code. In that case, overriding these methods is encouraged. + :class:`AbstractLinearCode` has generic implementations of the comparison methods ``__cmp`` + and ``__eq__`` which use the generator matrix and are quite slow. In subclasses you are + encouraged to override these functions. + + .. WARNING:: + + The default encoder should always have `F^{k}` as message space, with `k` the dimension + of the code and `F` is the base ring of the code. + A lot of methods of the abstract class rely on the knowledge of a generator matrix. + It is thus strongly recommended to set an encoder with a generator matrix implemented + as a default encoder. """ - def __init__(self, base_field, length): + + _registered_encoders = {} + + def __init__(self, base_field, length, default_encoder_name): """ Initializes mandatory parameters for a Linear Code object. @@ -753,13 +763,15 @@ def __init__(self, base_field, length): - ``length`` -- the length of ``self`` + - ``default_encoder_name`` -- the name of the default encoder of ``self`` + EXAMPLES: We first create a new LinearCode subclass:: sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): ....: def __init__(self, field, length, dimension, generator_matrix): - ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length) + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "GeneratorMatrix") ....: self._dimension = dimension ....: self._generator_matrix = generator_matrix ....: def generator_matrix(self): @@ -803,10 +815,31 @@ def __init__(self, base_field, length): Traceback (most recent call last): ... ValueError: length must be a Python int or a Sage Integer + + If the name of the default encoder is not known by the class, it will raise + an exception:: + + sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): + ....: def __init__(self, field, length, dimension, generator_matrix): + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "Fail") + ....: self._dimension = dimension + ....: self._generator_matrix = generator_matrix + ....: def generator_matrix(self): + ....: return self._generator_matrix + ....: def _repr_(self): + ....: return "Dummy code of length %d, dimension %d over %s" % (self.length(), self.dimension(), self.base_field()) + + sage: C = CodeExample(GF(17), 10, 5, generator_matrix) + Traceback (most recent call last): + ... + ValueError: You must set a valid encoder as default encoder for this code, by completing __init__.py """ if not isinstance(length, (int, Integer)): raise ValueError("length must be a Python int or a Sage Integer") self._length = Integer(length) + if not default_encoder_name in self._registered_encoders: + raise ValueError("You must set a valid encoder as default encoder for this code, by completing __init__.py") + self._default_encoder_name = default_encoder_name cat = Modules(base_field).FiniteDimensional().WithBasis().Finite() facade_for = VectorSpace(base_field, self._length) self.Element = type(facade_for.an_element()) #for when we made this a non-facade parent @@ -844,6 +877,68 @@ def _an_element_(self): """ return self.gens()[0] + def add_encoder(self, name, encoder): + r""" + Adds an encoder to the list of registered encoders of ``self``. + + .. NOTE:: + + This method only adds ``encoder`` to ``self``, and not to any member of the class + of ``self``. To know how to add an :class:`sage.coding.encoder.Encoder`, please refer + to the documentation of :class:`AbstractLinearCode`. + + INPUT: + + - ``name`` -- the string name for the encoder + + - ``encoder`` -- the class name of the encoder + + EXAMPLES: + + First of all, we create a (very basic) new encoder:: + + sage: class MyEncoder(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(MyEncoder, self).__init__(code) + ....: def _repr_(self): + ....: return "MyEncoder encoder with associated code %s" % self.code() + + We now create a new code:: + + sage: C = codes.HammingCode(3, GF(2)) + + We can add our new encoder to the list of available encoders of C:: + + sage: C.add_encoder("MyEncoder", MyEncoder) + sage: C.encoders_available() + ['MyEncoder', 'GeneratorMatrix'] + + We can verify that any new code will not know MyEncoder:: + + sage: C2 = codes.HammingCode(3, GF(3)) + sage: C2.encoders_available() + ['GeneratorMatrix'] + + TESTS: + + It is impossible to use a name which is in the dictionnary of available encoders:: + + sage: C.add_encoder("GeneratorMatrix", MyEncoder) + Traceback (most recent call last): + ... + ValueError: There is already a registered encoder with this name + """ + if self._registered_encoders == self.__class__._registered_encoders: + self._registered_encoders = copy(self._registered_encoders) + reg_enc = self._registered_encoders + if name in reg_enc: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + else: + if name in self._registered_encoders: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + def automorphism_group_gens(self, equivalence="semilinear"): r""" Return generators of the automorphism group of ``self``. @@ -1639,6 +1734,133 @@ def __eq__(self, right): return False return True + def encode(self, word, encoder_name=None, **kwargs): + r""" + Transforms an element of a message space into a codeword. + + INPUT: + + - ``word`` -- a vector of a message space of the code. + + - ``encoder_name`` -- (default: ``None``) Name of the encoder which will be used + to encode ``word``. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + OUTPUT: + + - a vector of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + It is possible to manually choose the encoder amongst the list of the available ones:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word, 'GeneratorMatrix') + (1, 1, 0, 0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.encode(word) + + @cached_method + def encoder(self, encoder_name=None, **kwargs): + r""" + Returns an encoder of ``self``. + + The returned encoder provided by this method is cached. + + This methods creates a new instance of the encoder subclass designated by ``encoder_name``. + While it is also possible to do the same by directly calling the subclass' constructor, + it is strongly advised to use this method to take advantage of the caching mechanism. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + returned. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the constructor of the encoder + this method will return. + + OUTPUT: + + - an Encoder object. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoder() + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + + We check that the returned encoder is cached:: + + sage: C.encoder.is_in_cache() + True + + If the name of an encoder which is not known by ``self`` is passed, + an exception will be raised:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: C.encoder('NonExistingEncoder') + Traceback (most recent call last): + ... + ValueError: Passed Encoder name not known + """ + if encoder_name is None: + encoder_name = self._default_encoder_name + if encoder_name in self._registered_encoders: + encClass = self._registered_encoders[encoder_name] + E = encClass(self, **kwargs) + return E + else: + raise ValueError("Passed Encoder name not known") + + def encoders_available(self, classes=False): + r""" + Returns a list of the available encoders' names for ``self``. + + INPUT: + + - ``classes`` -- (default: ``False``) if ``classes`` is set to ``True``, it also + returns the encoders' classes associated with the encoders' names. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoders_available() + ['GeneratorMatrix'] + + sage: C.encoders_available(True) + {'GeneratorMatrix': + } + """ + if classes == True: + return copy(self._registered_encoders) + return self._registered_encoders.keys() + def extended_code(self): r""" If ``self`` is a linear code of length `n` defined over `F` then this @@ -1810,8 +2032,31 @@ def __getitem__(self, i): codeword.set_immutable() return codeword - def generator_matrix(self): - return NotImplementedError("This method must be set in subclasses") + def generator_matrix(self, encoder_name=None, **kwargs): + r""" + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. The default encoder of ``self`` + will be used if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + EXAMPLES:: + + sage: G = matrix(GF(3),2,[1,-1,1,-1,1,1]) + sage: code = LinearCode(G) + sage: code.generator_matrix() + [1 2 1] + [2 1 1] + """ + E = self.encoder(encoder_name, **kwargs) + return E.generator_matrix() + + gen_mat = deprecated_function_alias(17973, generator_matrix) def generator_matrix_systematic(self): """ @@ -1897,11 +2142,13 @@ def __iter__(self): FiniteFieldsubspace_iterator return FiniteFieldsubspace_iterator(self.generator_matrix(), immutable=True) - + @cached_method def information_set(self): """ Return an information set of the code. + Return value of this method is cached. + A set of column positions of a generator matrix of a code is called an information set if the corresponding columns form a square matrix of full rank. @@ -2223,10 +2470,6 @@ def minimum_distance(self, algorithm=None): raise ValueError("The algorithm argument must be one of None, " "'gap' or 'guava'; got '{0}'".format(algorithm)) - #sage: C.minimum_distance_upper_bound() # optional (net connection) - #5 - # sage: C.minimum_distance_why() # optional (net connection) - # Ub(10,5) = 5 follows by the Griesmer bound. F = self.base_ring() q = F.order() G = self.generator_matrix() @@ -2396,7 +2639,7 @@ def permutation_automorphism_group(self, algorithm="partition"): print "\n Using the %s codewords of weight %s \n Supergroup size: \n %s\n "%(wts[wt],wt,size) gap.eval("Cwt:=Filtered(eltsC,c->WeightCodeword(c)=%s)"%wt) # bottleneck 2 (repeated gap.eval("matCwt:=List(Cwt,c->VectorCodeword(c))") # for each i until stop = 1) - if gap("Length(matCwt)") > 0: + if gap("Length(matCwt)") > 0: A = gap("MatrixAutomorphisms(matCwt)") G2 = gap("Intersection2(%s,%s)"%(str(A).replace("\n",""),str(Gp).replace("\n",""))) # bottleneck 3 Gp = G2 @@ -2878,7 +3121,7 @@ def spectrum(self, algorithm=None): input = code2leon(self) + "::code" import os, subprocess lines = subprocess.check_output([os.path.join(guava_bin_dir, 'wtdist'), input]) - import StringIO # to use the already present output parser + import StringIO # to use the already present output parser wts = [0]*(n+1) s = 0 for L in StringIO.StringIO(lines).readlines(): @@ -3015,6 +3258,42 @@ def syndrome(self, r): """ return self.parity_check_matrix()*r + def unencode(self, c, encoder_name=None, nocheck=False, **kwargs): + r""" + Returns the message corresponding to ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a codeword of ``self`` + + - ``encoder_name`` -- (default: ``None``) name of the decoder which will be used + to decode ``word``. The default decoder of ``self`` will be used if + default value is kept. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + OUTPUT: + + - an element of the message space of ``encoder_name`` of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: C.unencode(c) + (0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.unencode(c, nocheck) def weight_enumerator(self, names="xy", name2=None): """ @@ -3285,11 +3564,6 @@ class LinearCode(AbstractLinearCode): - David Joyner (11-2005) """ - # sage: C.minimum_distance_upper_bound() # optional (net connection) - # 3 - # sage: C.minimum_distance_why() # optional (net connection) - # Ub(7,4) = 3 follows by the Griesmer bound. - def __init__(self, generator_matrix, d=None): r""" See the docstring for :meth:`LinearCode`. @@ -3351,7 +3625,7 @@ def __init__(self, generator_matrix, d=None): if generator_matrix.nrows() == 0: raise ValueError("this linear code contains no non-zero vector") - super(LinearCode, self).__init__(base_ring, generator_matrix.ncols()) + super(LinearCode, self).__init__(base_ring, generator_matrix.ncols(), "GeneratorMatrix") self._generator_matrix = generator_matrix self._dimension = generator_matrix.rank() self._minimum_distance = d @@ -3370,9 +3644,18 @@ def _repr_(self): """ return "Linear code of length %s, dimension %s over %s"%(self.length(), self.dimension(), self.base_ring()) - def generator_matrix(self): + def generator_matrix(self, encoder_name=None, **kwargs): r""" - Return a generator matrix of this code. + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. ``self._generator_matrix`` + will be returned if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. EXAMPLES:: @@ -3382,6 +3665,80 @@ def generator_matrix(self): [1 2 1] [2 1 1] """ - return self._generator_matrix + if encoder_name is None or encoder_name is 'GeneratorMatrix': + return self._generator_matrix + return super(LinearCode, self).generator_matrix(encoder_name, **kwargs) - gen_mat = deprecated_function_alias(17973, generator_matrix) + + +####################### encoders ############################### +class LinearCodeGeneratorMatrixEncoder(Encoder): + r""" + Encoder based on generator_matrix for Linear codes. + + This is the default encoder of a generic linear code, and should never be used for other codes than + :class:`LinearCode`. + + INPUT: + + - ``code`` -- The associated :class:`LinearCode` of this encoder. + """ + + def __init__(self, code): + r""" + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + super(LinearCodeGeneratorMatrixEncoder, self).__init__(code) + + def _repr_(self): + r""" + Returns a string representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + return "Generator matrix-based encoder for %s" % self.code() + + def _latex_(self): + r""" + Returns a latex representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: latex(E) + \textnormal{Generator matrix-based encoder for }[7, 4]\textnormal{ Linear code over }\Bold{F}_{2} + """ + return "\\textnormal{Generator matrix-based encoder for }%s" % self.code()._latex_() + + @cached_method + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + return self.code().generator_matrix() +LinearCode._registered_encoders["GeneratorMatrix"] = LinearCodeGeneratorMatrixEncoder diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index 42910e4469d..b9ad2739432 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -778,16 +778,18 @@ def to_lehmer_code(self, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: p.to_lehmer_code(a[0],a[1]) + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders, sides): + ....: p.to_lehmer_code(o,s) [2, 3, 2, 0, 1, 2, 0, 0] [2, 2, 0, 0, 2, 1, 0, 3] [3, 1, 0, 0, 2, 1, 0, 3] [0, 3, 3, 0, 1, 2, 0, 1] - sage: for a in CP: + sage: for a in itertools.product(orders, sides): ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]), a[0],a[1])==p True True @@ -2198,15 +2200,17 @@ def from_lehmer_code(self, C, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.to_lehmer_code() [0, 3, 3, 0, 1, 2, 0, 1] sage: A.from_lehmer_code(p.to_lehmer_code())==p True - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]),a[0],a[1])==p + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders,sides): + ....: A.from_lehmer_code(p.to_lehmer_code(o,s),o,s)==p True True True diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 1031746c30b..bbfce97acdf 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -36,6 +36,8 @@ #Permutations from permutation import Permutation, Permutations, Arrangements, PermutationOptions, CyclicPermutations, CyclicPermutationsOfPartition from affine_permutation import AffinePermutationGroup +lazy_import('sage.combinat.colored_permutations', ['ColoredPermutations', + 'SignedPermutations']) from derangements import Derangements lazy_import('sage.combinat.baxter_permutations', ['BaxterPermutations']) @@ -46,9 +48,8 @@ #PerfectMatchings from perfect_matching import PerfectMatching, PerfectMatchings -# Integer lists lex - -from integer_list import IntegerListsLex as IntegerListsLex +# Integer lists +from integer_lists import IntegerListsLex #Compositions from composition import Composition, Compositions @@ -146,8 +147,8 @@ 'sage.combinat.backtrack', globals(), locals(), - ["SearchForest", - "TransitiveIdeal", + ["SearchForest", + "TransitiveIdeal", "TransitiveIdealGraded"], ("This class soon will not be available in that " "way anymore. Use RecursivelyEnumeratedSet " @@ -190,10 +191,13 @@ lazy_import('sage.combinat.finite_state_machine', ['Automaton', 'Transducer', 'FiniteStateMachine']) lazy_import('sage.combinat.finite_state_machine_generators', - ['transducers']) + ['automata', 'transducers']) # Binary Recurrence Sequences from binary_recurrence_sequences import BinaryRecurrenceSequence # Six Vertex Model lazy_import('sage.combinat.six_vertex_model', 'SixVertexModel') +# Fully Packed Loop +lazy_import('sage.combinat.fully_packed_loop', ['FullyPackedLoop', 'FullyPackedLoops']) + diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 1db5bc146ae..b199b91f966 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -49,6 +49,7 @@ from sage.combinat.combinatorial_map import combinatorial_map from sage.combinat.non_decreasing_parking_function import NonDecreasingParkingFunction from sage.combinat.permutation import Permutation +from sage.combinat.six_vertex_model import SquareIceModel class AlternatingSignMatrix(Element): r""" @@ -103,6 +104,17 @@ def __init__(self, parent, asm): self._matrix = asm Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: A = AlternatingSignMatrices(3) + sage: elt = A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: hash(elt) + 12 + """ + return hash(self._matrix) + def _repr_(self): """ Return a string representation of ``self``. @@ -307,7 +319,7 @@ def inversion_number(self): number of the permutation. This definition is equivalent to the one given in [MiRoRu]_. - + EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -457,10 +469,93 @@ def height_function(self): n = asm.nrows() + 1 return matrix([[i+j-2*nw_corner_sum(asm,i,j) for i in range(n)] for j in range(n)]) + def to_six_vertex_model(self): + r""" + Return the six vertex model configuration from ``self``. + This method calls :meth:`sage.combinat.six_vertex_model.from_alternating_sign_matrix`. + + EXAMPLES:: + + sage: asm = AlternatingSignMatrix([[0,1,0],[1,-1,1],[0,1,0]]) + sage: asm.to_six_vertex_model() + ^ ^ ^ + | | | + --> # -> # <- # <-- + ^ | ^ + | V | + --> # <- # -> # <-- + | ^ | + V | V + --> # -> # <- # <-- + | | | + V V V + + TESTS:: + + sage: ASM = AlternatingSignMatrices(5) + sage: all((x.to_six_vertex_model()).to_alternating_sign_matrix() == x + ....: for x in ASM) + True + """ + + asm = self.to_matrix() + n = asm.nrows() + M = SquareIceModel(n) + return M.from_alternating_sign_matrix(self) + + def to_fully_packed_loop(self): + r""" + Return the fully packed loop configuration from ``self``. + + .. SEEALSO:: + + :class:FullyPackedLoop + + EXAMPLES:: + + sage: asm = AlternatingSignMatrix([[1,0,0],[0,1,0],[0,0,1]]) + sage: fpl = asm.to_fully_packed_loop() + sage: fpl + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + + """ + from sage.combinat.fully_packed_loop import FullyPackedLoop + return FullyPackedLoop(self) + + def link_pattern(self): + """ + Return the link pattern corresponding to the fully packed loop + corresponding to self. + + EXAMPLES: + + We can extract the underlying link pattern (a non-crossing + partition) from a fully packed loop:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: A.link_pattern() + [(1, 2), (3, 6), (4, 5)] + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: B.link_pattern() + [(1, 6), (2, 5), (3, 4)] + """ + return self.to_fully_packed_loop().link_pattern() + @combinatorial_map(name='gyration') def gyration(self): r""" - Return the alternating sign matrix obtained by applying the gyration + Return the alternating sign matrix obtained by applying gyration to the height function in bijection with ``self``. Gyration acts on height functions as follows. Go through the entries of @@ -470,11 +565,6 @@ def gyration(self): still a height function. Gyration was first defined in [Wieland00]_ as an action on fully-packed loops. - REFERENCES: - - .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of - alternating sign matrices*. Electron. J. Combin. 7 (2000). - EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -492,7 +582,6 @@ def gyration(self): [0 1 0] [0 0 1] [1 0 0] - sage: A = AlternatingSignMatrices(3) sage: A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]).gyration().gyration() [ 0 1 0] @@ -724,25 +813,70 @@ def ASM_compatible_smaller(self): return(output) @combinatorial_map(name='to Dyck word') - def to_dyck_word(self): + def to_dyck_word(self, method): r""" - Return the Dyck word determined by the last diagonal of - the monotone triangle corresponding to ``self``. + Return a Dyck word determined by the specified method. + + The method 'last_diagonal' uses the last diagonal of the monotone + triangle corresponding to ``self``. The method 'link_pattern' returns + the Dyck word in bijection with the link pattern of the fully packed + loop. + + Note that these two methods in general yield different Dyck words for a + given alternating sign matrix. + + INPUT: + + - ``method`` - + + - ``'last_diagonal'`` + - ``'link_pattern'`` EXAMPLES:: sage: A = AlternatingSignMatrices(3) - sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word() + sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word(method = 'last_diagonal') [1, 1, 0, 0, 1, 0] - sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(); d + sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(method = 'last_diagonal'); d [1, 1, 0, 1, 0, 0] sage: parent(d) Complete Dyck words + sage: A = AlternatingSignMatrices(3) + sage: asm = A([[0,1,0],[1,0,0],[0,0,1]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 0, 1, 0] + sage: asm = A([[0,1,0],[1,-1,1],[0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 1, 0, 0] + sage: A = AlternatingSignMatrices(4) + sage: asm = A([[0,0,1,0],[1,0,0,0],[0,1,-1,1],[0,0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 1, 1, 0, 1, 0, 0, 0] + sage: asm.to_dyck_word() + Traceback (most recent call last): + ... + TypeError: to_dyck_word() takes exactly 2 arguments (1 given) + sage: asm.to_dyck_word(method = 'notamethod') + Traceback (most recent call last): + ... + ValueError: unknown method 'notamethod' """ - MT = self.to_monotone_triangle() - nplus = self._matrix.nrows() + 1 - parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] - return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + if method == 'last_diagonal': + MT = self.to_monotone_triangle() + nplus = self._matrix.nrows() + 1 + parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] + return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + + elif method == 'link_pattern': + from sage.combinat.perfect_matching import PerfectMatching + from sage.combinat.dyck_word import DyckWords + p = PerfectMatching(self.link_pattern()).to_non_crossing_set_partition() + asm = self.to_matrix() + n = asm.nrows() + d = DyckWords(n) + return d.from_noncrossing_partition(p) + + raise ValueError("unknown method '%s'" % method) def number_negative_ones(self): """ @@ -826,6 +960,8 @@ def to_semistandard_tableau(self): ssyt[i][j] = mt[j][-(i+1)] return SemistandardTableau(ssyt) + + def left_key(self): r""" Return the left key of the alternating sign matrix ``self``. @@ -890,7 +1026,7 @@ def left_key_as_permutation(self): """ return self.left_key().to_permutation() -class AlternatingSignMatrices(Parent, UniqueRepresentation): +class AlternatingSignMatrices(UniqueRepresentation, Parent): r""" Class of all `n \times n` alternating sign matrices. @@ -1036,7 +1172,9 @@ def _element_constructor_(self, asm): raise ValueError("Cannot convert between alternating sign matrices of different sizes") if asm in MonotoneTriangles(self._n): return self.from_monotone_triangle(asm) - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) Element = AlternatingSignMatrix @@ -1082,7 +1220,9 @@ def from_monotone_triangle(self, triangle): asm.append(row) prev = v - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) def from_corner_sum(self, corner): r""" diff --git a/src/sage/combinat/baxter_permutations.py b/src/sage/combinat/baxter_permutations.py index 8a214976497..4a0c5a1f9af 100644 --- a/src/sage/combinat/baxter_permutations.py +++ b/src/sage/combinat/baxter_permutations.py @@ -176,7 +176,7 @@ def __iter__(self): ....: for a in BaxterPermutations(n)) True - ALGORITHM:: + ALGORITHM: The algorithm using generating trees described in [BBF08]_ is used. The idea is that all Baxter permutations of size `n + 1` can be @@ -184,7 +184,7 @@ def __iter__(self): to right maximum or just after a right to left maximum of a Baxter permutation of size `n`. - REFERENCES:: + REFERENCES: .. [BBF08] N. Bonichon, M. Bousquet-Melou, E. Fusy. Baxter permutations and plane bipolar orientations. diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index 125eabd2928..e0f292fc1a4 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -25,15 +25,17 @@ def CartesianProduct(*iters): """ - Returns the combinatorial class of the Cartesian product of - \*iters. + This is deprecated. Use :obj:`cartesian_product` instead. EXAMPLES:: sage: cp = CartesianProduct([1,2], [3,4]); cp - Cartesian product of [1, 2], [3, 4] + doctest:...: DeprecationWarning: CartesianProduct is deprecated. Use + cartesian_product instead + See http://trac.sagemath.org/18411 for details. + The cartesian product of ({1, 2}, {3, 4}) sage: cp.list() - [[1, 3], [1, 4], [2, 3], [2, 4]] + [(1, 3), (1, 4), (2, 3), (2, 4)] Note that you must not use a generator-type object that is returned by a function (using "yield"). They cannot be copied or @@ -48,24 +50,106 @@ def CartesianProduct(*iters): ValueError: generators are not allowed, see the documentation (type "CartesianProduct?") for a workaround - You either create a list of all values or you use - :class:`sage.combinat.misc.IterableFunctionCall` to make a - (copy-able) iterator:: + The usage of iterable is also deprecated, so the following will no longer be + supported:: sage: from sage.combinat.misc import IterableFunctionCall - sage: CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)).list() - [[3, 'a'], [3, 'b'], [6, 'a'], [6, 'b']] - - See the documentation for - :class:`~sage.combinat.misc.IterableFunctionCall` for more - information. + sage: C = CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)) + doctest:...: DeprecationWarning: Usage of IterableFunctionCall in + CartesianProduct is deprecated. You can use EnumeratedSetFromIterator + (in sage.sets.set_from_iterator) instead. + See http://trac.sagemath.org/18411 for details. + sage: list(C) + doctest:...: UserWarning: Sage is not able to determine whether the + factors of this cartesian product are finite. The lexicographic ordering + might not go through all elements. + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] + + You might use + :class:`~sage.sets.set_from_iterator.EnumeratedSetFromIterator` for that + purpose.:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: A = EnumeratedSetFromIterator(a, (3,), category=FiniteEnumeratedSets()) + sage: B = EnumeratedSetFromIterator(b, category=FiniteEnumeratedSets()) + sage: C = cartesian_product([A, B]) + sage: C.list() + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] """ if any(isgenerator(i) for i in iters): raise ValueError('generators are not allowed, see the documentation '+ '(type "CartesianProduct?") for a workaround') - return CartesianProduct_iters(*iters) + + from sage.misc.superseded import deprecation + deprecation(18411, "CartesianProduct is deprecated. Use cartesian_product instead") + + from sage.combinat.misc import IterableFunctionCall + from sage.sets.set_from_iterator import EnumeratedSetFromIterator + deprecate_ifc = False + iiters = [] + for a in iters: + if isinstance(a, IterableFunctionCall): + deprecate_ifc = True + iiters.append(EnumeratedSetFromIterator(a.f, a.args, a.kwargs)) + else: + iiters.append(a) + iters = tuple(iiters) + + if deprecate_ifc: + deprecation(18411, """Usage of IterableFunctionCall in CartesianProduct is deprecated. You can use EnumeratedSetFromIterator (in sage.sets.set_from_iterator) instead.""") + + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(iters) class CartesianProduct_iters(CombinatorialClass): + r""" + Cartesian product of finite sets. + + This class will soon be deprecated (see :trac:`18411` and :trac:`19195`). + One should instead use the functorial construction + :class:`cartesian_product `. + The main differences in behavior are: + + - construction: ``CartesianProduct`` takes as many argument as + there are factors whereas ``cartesian_product`` takes a single + list (or iterable) of factors; + + - representation of elements: elements are represented by plain + Python list for ``CartesianProduct`` versus a custom element + class for ``cartesian_product``; + + - membership testing: because of the above, plain Python lists are + not considered as elements of a ``cartesian_product``. + + All of these is illustrated in the examples below. + + EXAMPLES:: + + sage: F1 = ['a', 'b'] + sage: F2 = [1, 2, 3, 4] + sage: F3 = Permutations(3) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(F1, F2, F3) + sage: c = cartesian_product([F1, F2, F3]) + + sage: type(C.an_element()) + + sage: type(c.an_element()) + + + sage: l = ['a', 1, Permutation([3,2,1])] + sage: l in C + True + sage: l in c + False + sage: elt = c(l) + sage: elt + ('a', 1, [3, 2, 1]) + sage: elt in c + True + sage: elt.parent() is c + True + """ def __init__(self, *iters): """ TESTS:: @@ -84,13 +168,20 @@ def __contains__(self, x): """ EXAMPLES:: - sage: cp = CartesianProduct([1,2],[3,4]) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: cp = CartesianProduct_iters([1,2],[3,4]) sage: [1,3] in cp True sage: [1,2] in cp False sage: [1, 3, 1] in cp False + + Note that it differs with the behavior of cartesian products:: + + sage: cp = cartesian_product([[1,2], [3,4]]) + sage: [1,3] in cp + False """ try: return len(x) == len(self.iters) and all(x[i] in self.iters[i] for i in range(len(self.iters))) @@ -101,7 +192,8 @@ def __repr__(self): """ EXAMPLES:: - sage: CartesianProduct(range(2), range(3)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)) Cartesian product of [0, 1], [0, 1, 2] """ return "Cartesian product of " + ", ".join(map(str, self.iters)) @@ -113,18 +205,19 @@ def cardinality(self): EXAMPLES:: - sage: CartesianProduct(range(2), range(3)).cardinality() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3), xrange(4)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3), xrange(4)).cardinality() 24 This works correctly for infinite objects:: - sage: CartesianProduct(ZZ, QQ).cardinality() + sage: CartesianProduct_iters(ZZ, QQ).cardinality() +Infinity - sage: CartesianProduct(ZZ, []).cardinality() + sage: CartesianProduct_iters(ZZ, []).cardinality() 0 """ return self._mrange.cardinality() @@ -145,15 +238,16 @@ def __len__(self): EXAMPLES:: - sage: C = CartesianProduct(xrange(3), xrange(4)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(3), xrange(4)) sage: len(C) 12 - sage: C = CartesianProduct(ZZ, QQ) + sage: C = CartesianProduct_iters(ZZ, QQ) sage: len(C) Traceback (most recent call last): ... TypeError: cardinality does not fit into a Python int. - sage: C = CartesianProduct(ZZ, []) + sage: C = CartesianProduct_iters(ZZ, []) sage: len(C) 0 """ @@ -165,9 +259,10 @@ def list(self): EXAMPLES:: - sage: CartesianProduct(range(3), range(3)).list() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(3), range(3)).list() [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: CartesianProduct('dog', 'cat').list() + sage: CartesianProduct_iters('dog', 'cat').list() [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -191,9 +286,10 @@ def __iter__(self): EXAMPLES:: - sage: [e for e in CartesianProduct(range(3), range(3))] + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: [e for e in CartesianProduct_iters(range(3), range(3))] [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: [e for e in CartesianProduct('dog', 'cat')] + sage: [e for e in CartesianProduct_iters('dog', 'cat')] [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -213,9 +309,10 @@ def is_finite(self): EXAMPLES:: - sage: CartesianProduct(ZZ, []).is_finite() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(ZZ, []).is_finite() True - sage: CartesianProduct(4,4).is_finite() + sage: CartesianProduct_iters(4,4).is_finite() Traceback (most recent call last): ... ValueError: Unable to determine whether this product is finite @@ -237,14 +334,15 @@ def unrank(self, x): EXAMPLES:: - sage: C = CartesianProduct(xrange(1000), xrange(1000), xrange(1000)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(1000), xrange(1000), xrange(1000)) sage: C[238792368] [238, 792, 368] Check for :trac:`15919`:: sage: FF = IntegerModRing(29) - sage: C = CartesianProduct(FF, FF, FF) + sage: C = CartesianProduct_iters(FF, FF, FF) sage: C.unrank(0) [0, 0, 0] """ @@ -271,7 +369,8 @@ def random_element(self): EXAMPLES:: - sage: CartesianProduct('dog', 'cat').random_element() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters('dog', 'cat').random_element() ['d', 'a'] """ return [rnd.choice(_) for _ in self.iters] diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index c983bebb262..fff2fcb9fcf 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" ClusterSeed @@ -9,8 +10,20 @@ AUTHORS: -- Gregg Musiker -- Christian Stump +- Gregg Musiker: Initial Version +- Christian Stump: Initial Version +- Aram Dermenjian (2015-07-01): Updating ability to not rely solely on clusters +- Jesse Levitt (2015-07-01): Updating ability to not rely solely on clusters + +REFERENCES: + +.. [BDP2013] Thomas Brüstle, Grégoire Dupont, Matthieu Pérotin + *On Maximal Green Sequences* + :arxiv:`1205.2050` + +.. [FZ2007] Sergey Fomin, Andrei Zelevinsky + "Cluster Algebras IV: coefficients" + :arxiv:`0602259` .. seealso:: For mutation types of cluster seeds, see :meth:`sage.combinat.cluster_algebra_quiver.quiver_mutation_type.QuiverMutationType`. Cluster seeds are closely related to :meth:`sage.combinat.cluster_algebra_quiver.quiver.ClusterQuiver`. """ @@ -23,6 +36,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** import time +from operator import pos from sage.structure.sage_object import SageObject from copy import copy from sage.rings.all import QQ, infinity @@ -30,8 +44,16 @@ from sage.rings.all import FractionField, PolynomialRing from sage.rings.fraction_field_element import FractionFieldElement from sage.sets.all import Set -from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible +from sage.graphs.digraph import DiGraph +from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible from sage.combinat.cluster_algebra_quiver.mutation_type import is_mutation_finite +from sage.misc.misc import exists +from random import randint +from sage.misc.all import prod +from sage.matrix.all import identity_matrix +from sage.matrix.constructor import matrix +from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver +from sage.rings.integer import Integer class ClusterSeed(SageObject): r""" @@ -82,57 +104,715 @@ class ClusterSeed(SageObject): sage: S = ClusterSeed(['F', 4, [2,1]]); S A seed for a cluster algebra of rank 6 of type ['F', 4, [1, 2]] + + sage: S = ClusterSeed(['A',4]); S._use_fpolys + True + + sage: S._use_d_vec + True + + sage: S._use_g_vec + True + + sage: S._use_c_vec + True + + sage: S = ClusterSeed(['A',4]); S.use_fpolys(False); S._use_fpolys + False + """ - def __init__(self, data, frozen=None, is_principal=None): + def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user_labels_prefix='x'): r""" + + Initializes the ClusterSeed ``self`` with the following range of possible attributes: + + * self._n - the number of mutable elements of the cluster seed. + * self._m - the number of immutable elements of the cluster seed. + * self._M - the 'n + m' x 'n' exchange matrix associated to the cluster seed. + * self._B - the mutable part of self._M. + * self._b_initial - the initial exchange matrix + * self._description - the description of the ClusterSeed + * self._frozen - the number of frozen vertices, assumed to be the later vertices, when the input is a ClusterQuiver. + * self._use_fpolys - a boolean tracking whether F-polynomials and cluster variables will be tracked as part of every mutation. + * self._cluster - a list tracking the current names of cluster elements. + * self._user_labels_prefix - the prefix for every named cluster element. Defaults to 'x'. + * self._user_labels - an optional dictionary or list of user defined names for all cluster elements. Defaults to 'x_i' for mutable elements and 'y_i' for immutable elements. + * self._init_vars - an internal list for defining ambient the algebraic setting and naming quiver vertices. + * self._init_exch - the dictionary storing the initial mutable cluster variable names. + * self._U - the coefficient tuple of the initial cluster seed. + * self._F - the dictionary of F-polynomials. + * self._R - the ambient polynomial ring. + * self._y - the coefficient tuple for the current cluster seed. + * self._yhat - the mixed coefficient tuple appearing in Proposition 3.9 of [FZ2007] + * self._use_g_vec - a boolean stating if g-vectors for the cluster seed are being tracked. User input overridden as needed. + * self._G - the matrix containing all g-vectors. + * self._use_d_vec - a boolean stating if d-vectors for the cluster seed are being tracked. + * self._D - the matrix containing all d-vectors. + * self._bot_is_c - a boolean stating if the c-vectors are stored on the bottom of the exchange matrix M. + * self._use_c_vec - a boolean stating if c-vectors for the cluster seed are being tracked. User input overridden as needed. + * self._C - the matrix containing all c-vectors. + * self._BC - an extended matrix involving the B and C matrices used for simplifying mutation calculations. + * self._is_principal - a boolean tracking whether the ClusterSeed contains immutable elements coming from a principal extension of the mutable vertices. To be deprecated in future versions. + + * self._quiver - the ClusterQuiver corresponding to the exchange matrix self._M . + * self._mutation_type - the mutation type of self._quiver . + + * self._track_mut - a boolean tracking whether the a ClusterSeed's mutation path is being recorded. + * self._mut_path - the list of integers recording the mutation path of a seed - with consecutive repeats deleted since mutations is an involution. + TESTS:: sage: S = ClusterSeed(['A',4]) sage: TestSuite(S).run() """ - from quiver import ClusterQuiver + from sage.matrix.matrix import Matrix + + #initialize a null state ClusterSeed object so all tests run and fail as appropriate. + # numerous doctests if this null state is not first initialized. + self._n = 0 + self._m = 0 + self._M = None + self._B = None + self._b_initial = None + self._description = None + self._frozen = 0 + self._use_fpolys = None + self._cluster = None + self._user_labels_prefix = None + self._user_labels = None + self._init_vars = None + self._init_exch = None + self._U = None + self._F = None + self._R = None + self._y = None + self._yhat = None + + self._use_g_vec = None + self._G = None + + self._use_d_vec = None + self._D = None + + self._bot_is_c = None + self._use_c_vec = None + self._C = None + self._BC = None + self._is_principal = None + + self._quiver = None + self._mutation_type = None + + self._track_mut = None + self._mut_path = None # constructs a cluster seed from a cluster seed if isinstance(data, ClusterSeed): if frozen: print "The input \'frozen\' is ignored" + + # Copy the following attributes from data self._M = copy( data._M ) - self._cluster = copy(data._cluster) + self._M.set_immutable() + self._B = copy( data._B ) self._n = data._n self._m = data._m - self._R = data._R + + # initialize matrix of g-vectors if desired and possible + if data._use_g_vec and (data._G or data._cluster or (data._B.is_skew_symmetric() and data._C) or data._track_mut): + self._G = data.g_matrix() + + # initialize matrix of c-vectors if desired and possible + if data._use_c_vec and (data._C or (data._B.is_skew_symmetric() and (data._cluster or (data._use_g_vec and data._G)) or data._track_mut)): + self._C = data.c_matrix() + self._BC = copy(self._M).stack(copy(self._C)) + else: + self._BC = copy(self._M) + + # initialize matrix of d-vectors if desired and possible + if data._use_d_vec and (data._D or data._cluster or data._track_mut): + self._D = data.d_matrix() + + self._cluster = copy( data._cluster) + + self._b_initial = copy( data._b_initial) + + self._mutation_type = copy( data._mutation_type) + self._description = copy( data._description) self._quiver = ClusterQuiver( data._quiver ) if data._quiver else None - self._mutation_type = copy( data._mutation_type ) - self._description = copy( data._description ) + + # copy all previous booleans + self._use_fpolys = data._use_fpolys + self._use_g_vec = data._use_g_vec + self._use_d_vec = data._use_d_vec + self._bot_is_c = data._bot_is_c + self._use_c_vec = data._use_c_vec + self._track_mut = data._track_mut self._is_principal = data._is_principal + # copy all previous dictionaries, names and data + self._user_labels = copy( data._user_labels) + self._user_labels_prefix = copy( data._user_labels_prefix) + self._init_vars = copy( data._init_vars) + self._init_exch = copy( data._init_exch) + self._U = copy( data._U) + self._F = copy( data._F) + self._R = copy( data._R) + self._y = copy( data._y) + self._yhat = copy( data._yhat) + self._mut_path = copy( data._mut_path) + # constructs a cluster seed from a quiver elif isinstance(data, ClusterQuiver): if frozen: print "The input \'frozen\' is ignored" quiver = ClusterQuiver( data ) - self._M = copy(quiver._M) + + self._M = copy(quiver._M) # B-tilde exchange matrix + self._M.set_immutable() self._n = quiver._n self._m = quiver._m - self._quiver = quiver - self._mutation_type = quiver._mutation_type + self._B = copy(self._M[:self._n,:self._n]) # Square Part of the B_matrix + + # If initializing from a ClusterQuiver rather than a ClusterSeed, the initial B-matrix is reset to be the input B-matrix. + self._b_initial = copy(self._M) + self._mutation_type = copy(quiver._mutation_type) self._description = 'A seed for a cluster algebra of rank %d' %(self._n) - self._R = FractionField(PolynomialRing(QQ,['x%s'%i for i in range(0,self._n)]+['y%s'%i for i in range(0,self._m)])) - self._cluster = list(self._R.gens()) - self._is_principal = None + self._quiver = quiver + + # We are now updating labels from user's most recent choice. + self._is_principal = is_principal + self._user_labels = user_labels + self._user_labels_prefix = user_labels_prefix + + # initialize the rest + + self._C = matrix.identity(self._n) + self._use_c_vec = True + + self._G = matrix.identity(self._n) + self._use_g_vec = True + + self._BC = copy(self._M).stack(self.c_matrix()) + self._bot_is_c=False + + self._D = -matrix.identity(self._n) + self._use_d_vec = True + + self._mut_path = [ ] + self._track_mut = True + + if user_labels: + self._sanitize_init_vars(user_labels, user_labels_prefix) + else: + xs = {i:'x%s'%i for i in xrange(self._n)} + ys = {(i+self._n):'y%s'%i for i in xrange(self._n+self._m)} + self._init_vars = copy(xs) + self._init_vars.update(ys) + + self._init_exch = dict(self._init_vars.items()[:self._n]) + self._U = PolynomialRing(QQ,['y%s'%i for i in xrange(self._n)]) + self._F = dict([(i,self._U(1)) for i in self._init_exch.values()]) + self._R = PolynomialRing(QQ,[val for val in self._init_vars.values()]) + self._y = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n,self._n+self._m)])) for j in xrange(self._n)]) + self._yhat = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n+self._m)])) for j in xrange(self._n)]) + #self._cluster = None + self._use_fpolys = True # in all other cases, we construct the corresponding ClusterQuiver first else: quiver = ClusterQuiver( data, frozen=frozen ) - self.__init__( quiver ) + self.__init__( quiver, is_principal=is_principal, user_labels=user_labels, user_labels_prefix=user_labels_prefix) - if is_principal is not None: - self._is_principal = is_principal + def use_c_vectors(self, use=True, bot_is_c=False, force=False): + r""" + Reconstruct c vectors from other data or initialize if no usable data exists. + + Warning: Initialization may lead to inconsistent data. + + INPUT: + + - ``use`` -- (default:True) If True, will use c vectors + - ``bot_is_c`` -- (default:False) If True and ClusterSeed self has self._m == self._n, then will assume bottom half of the extended exchange matrix is the c-matrix. If true, lets the ClusterSeed know c-vectors can be calculated. + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); + sage: S.use_c_vectors(False); S.use_g_vectors(False); S.use_fpolys(False); S.track_mutations(False) + sage: S.use_c_vectors(True) + Warning: Initializing c-vectors at this point could lead to inconsistent seed data. + + sage: S.use_c_vectors(True, force=True) + sage: S.c_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: S = ClusterSeed(['A',4]); + sage: S.use_c_vectors(False); S.use_g_vectors(False); S.use_fpolys(False); S.track_mutations(False) + sage: S.mutate(1); + sage: S.use_c_vectors(True, force=True) + sage: S.c_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + """ + if self._use_c_vec != use: + self._use_c_vec = use + if self._use_c_vec: + #self._C = matrix.identity(self._n) + try: + self._use_c_vec = False # temporarily turns off c-vectors to see if they can be recovered. + self._C = self.c_matrix() # if not just sets it to be identity matrix, i.e. reinitialized. + self._BC = copy(self._M).stack(self.c_matrix()) + self._use_c_vec = True + except ValueError: + if not force: + print("Warning: Initializing c-vectors at this point could lead to inconsistent seed data.") + else: + self._use_c_vec = True + self._C = matrix.identity(self._n) + self._BC = copy(self._M).stack(self.c_matrix()) + except AttributeError: + if not force: + print("Warning: Initializing c-vectors at this point could lead to inconsistent seed data.") + else: + self._use_c_vec = True + self._C = matrix.identity(self._n) + self._BC = copy(self._M).stack(self.c_matrix()) + else: + self._C = None + self._BC = copy(self._M) + if self._bot_is_c != bot_is_c: # If we need to do this. It overrides the previous designations. + self._bot_is_c = bot_is_c + if self._bot_is_c == True: + self._use_c_vec = True + if self._m == self._n: # in this case, the second half of a 2n x n matrix is a c-matrix. + self._C = copy(self._M[self._n:(self._n+self._m),:self._n]) + self._BC = copy(self._M) + else: # self._n != self._m + raise ValueError('There are immutable elements not in the c-matrix. Storing the c-matrix separately.') + self._C = copy(self._M[self._m:(self._n+self._m),:self._n]) + self._BC = copy(self._M) + self._M = self._M[:self._m:self._n] + self._M.set_immutable() + self._bot_is_c = False + + def use_g_vectors(self, use=True, force=False): + r""" + Reconstruct g vectors from other data or initialize if no usable data exists. + + Warning: Initialization may lead to inconsistent data. + + INPUT: + + - ``use`` -- (default:True) If True, will use g vectors + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); + sage: S.use_g_vectors(False); S.use_fpolys(False) + sage: S.use_g_vectors(True) + sage: S.g_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: S = ClusterSeed(['A',4]); + sage: S.use_g_vectors(False); S.use_fpolys(False) + sage: S.mutate(1); + sage: S.use_g_vectors(True) + sage: S.g_matrix() + [ 1 0 0 0] + [ 0 -1 0 0] + [ 0 0 1 0] + [ 0 0 0 1] + + sage: S = ClusterSeed(['A',4]); + sage: S.use_g_vectors(False); S.use_fpolys(False); S.track_mutations(False) + sage: S.mutate(1); + sage: S.use_c_vectors(False) + sage: S.g_matrix() + Traceback (most recent call last): + ... + ValueError: Unable to calculate g-vectors. Need to use g vectors. + + sage: S = ClusterSeed(['A',4]); + sage: S.use_g_vectors(False); S.use_fpolys(False); S.track_mutations(False) + sage: S.mutate(1); + sage: S.use_c_vectors(False) + sage: S.use_g_vectors(True) + Warning: Initializing g-vectors at this point could lead to inconsistent seed data. + + sage: S.use_g_vectors(True, force=True) + sage: S.g_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + """ + if self._use_g_vec != use: + self._use_g_vec = use + if self._use_g_vec: + #self._G = matrix.identity(self._n) if self._use_g_vec else None + try: + self._use_g_vec = False # temporarily turns off g-vectors to see if they can be recovered. + self._G = self.g_matrix() # if not just sets it to be identity matrix, i.e. reinitialized. + self._use_g_vec = True + except ValueError: + if not force: + print("Warning: Initializing g-vectors at this point could lead to inconsistent seed data.") + else: + self._use_g_vec = True + self._G = matrix.identity(self._n) + except AttributeError: + if not force: + print("Warning: Initializing g-vectors at this point could lead to inconsistent seed data.") + else: + self._use_g_vec = True + self._G = matrix.identity(self._n) + else: + self._G = None + + # Initially coded so c_vectors would be turned back on but now each of these boolean flags are independent + #if self._use_g_vec and not self._use_c_vec: + # self.use_c_vectors(True) + + def use_d_vectors(self, use=True, force=False): + r""" + Reconstruct d vectors from other data or initialize if no usable data exists. + + Warning: Initialization may lead to inconsistent data. + + INPUT: + + - ``use`` -- (default:True) If True, will use d vectors + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); + sage: S.use_d_vectors(True) + sage: S.d_matrix() + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + + sage: S = ClusterSeed(['A',4]); S.use_d_vectors(False); S.track_mutations(False); S.mutate(1); S.d_matrix() + [-1 0 0 0] + [ 0 1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + sage: S.use_fpolys(False) + sage: S.d_matrix() + Traceback (most recent call last): + ... + ValueError: Unable to calculate d-vectors. Need to use d vectors. + + sage: S = ClusterSeed(['A',4]); S.use_d_vectors(False); S.track_mutations(False); S.mutate(1); S.d_matrix() + [-1 0 0 0] + [ 0 1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + sage: S.use_fpolys(False) + sage: S.use_d_vectors(True) + Warning: Initializing d-vectors at this point could lead to inconsistent seed data. + + sage: S.use_d_vectors(True, force=True) + sage: S.d_matrix() + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + + sage: S = ClusterSeed(['A',4]); S.mutate(1); S.d_matrix() + [-1 0 0 0] + [ 0 1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + sage: S = ClusterSeed(['A',4]); S.use_d_vectors(True); S.mutate(1); S.d_matrix() + [-1 0 0 0] + [ 0 1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + """ + if self._use_d_vec != use: + self._use_d_vec = use + if self._use_d_vec: + try: + self._use_d_vec = False # temporarily turns off d-vectors to see if they can be recovered. + self._D = self.d_matrix() + self._use_d_vec = True + except ValueError: + if not force: + print("Warning: Initializing d-vectors at this point could lead to inconsistent seed data.") + else: + self._use_d_vec = True # if not just sets it to be negative identity matrix, i.e. reinitialized. + self._D = -matrix.identity(self._n) + except AttributeError: + if not force: + print("Warning: Initializing d-vectors at this point could lead to inconsistent seed data.") + else: + self._use_d_vec = True # if not just sets it to be negative identity matrix, i.e. reinitialized. + self._D = -matrix.identity(self._n) + else: + self._D = None + + def use_fpolys(self, use=True, user_labels=None, user_labels_prefix=None): + r""" + Use F-polynomials in our Cluster Seed + + Note: This will automatically try to recompute the cluster variables if possible + + INPUT: + + - ``use`` -- (default:True) If True, will use F-polynomials + - ``user_labels`` -- (default:None) If set will overwrite the default cluster variable labels + - ``user_labels_prefix`` -- (default:None) If set will overwrite the default + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); S.use_fpolys(False); S._cluster + sage: S.use_fpolys(True) + sage: S.cluster() + [x0, x1, x2, x3] + + sage: S = ClusterSeed(['A',4]); S.use_fpolys(False); S.track_mutations(False); S.mutate(1) + sage: S.use_fpolys(True) + Traceback (most recent call last): + ... + ValueError: F-polynomials and Cluster Variables cannot be reconstructed from given data. + sage: S.cluster() + Traceback (most recent call last): + ... + ValueError: Clusters not being tracked + + """ + if user_labels: + self._user_labels = user_labels + if user_labels_prefix: + self._user_labels_prefix = user_labels_prefix + if self._use_fpolys != use: + self._use_fpolys = use + + if self._use_fpolys: + + if user_labels: + self._sanitize_init_vars(user_labels, user_labels_prefix) + else: + xs = {i:'x%s'%i for i in xrange(self._n)} + ys = {(i+self._n):'y%s'%i for i in xrange(self._n+self._m)} + self._init_vars = copy(xs) + self._init_vars.update(ys) + + if self._G == matrix.identity(self._n): # If we are at the root + if not self._use_g_vec: + self.use_g_vectors(True) + self._init_exch = dict(self._init_vars.items()[:self._n]) + self._U = PolynomialRing(QQ,['y%s'%i for i in xrange(self._n)]) + self._F = dict([(i,self._U(1)) for i in self._init_exch.values()]) + self._R = PolynomialRing(QQ,[val for val in self._init_vars.values()]) + self._y = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n,self._n+self._m)])) for j in xrange(self._n)]) + self._yhat = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n+self._m)])) for j in xrange(self._n)]) + elif self._cluster: + raise ValueError("should not be possible to have cluster variables without f-polynomials") # added this as a sanity check. This error should never appear however. + elif self._track_mut == True: # If we can navigate from the root to where we are + if not self._use_g_vec: + self.use_g_vectors(True) + catchup = ClusterSeed(self._b_initial, user_labels=user_labels, user_labels_prefix=user_labels_prefix) + catchup.use_c_vectors(use=self._use_c_vec,bot_is_c=self._bot_is_c) + catchup.mutate(self.mutations()) + + self._init_exch = catchup._init_exch + self._U = catchup._U + self._F = catchup._F + self._R = catchup._R + self._y = catchup._y + self._yhat = catchup._yhat + else: + self._use_fpolys = False + self._cluster = None + raise ValueError("F-polynomials and Cluster Variables cannot be reconstructed from given data.") + + # since we have F polynomials, set up clusters properly + self._cluster = None + self.cluster() + else: + if user_labels: + print("Warning: since 'use_fpolys' is False, the parameter 'user_labels' is ignored.") + self._init_vars = None + self._init_exch = None + self._U = None + self._F = None + self._R = None + self._y = None + self._yhat = None + self._cluster = None + + def track_mutations(self, use=True): + r""" + Begins tracking the mutation path. + + Warning: May initialize all other data to ensure that all c, d, and g vectors agree on the start of mutations. + + INPUT: + + - ``use`` -- (default:True) If True, will begin filling the mutation path + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); S.track_mutations(False) + sage: S.mutate(0) + sage: S.mutations() + Traceback (most recent call last): + ... + ValueError: Not recording mutation sequence. Need to track mutations. + sage: S.track_mutations(True) + sage: S.g_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: S.mutate([0,1]) + sage: S.mutations() + [0, 1] + """ + + if self._track_mut != use: + self._track_mut = use + if self._track_mut: + self._b_initial = self.b_matrix() + self._mut_path = [] + use_c = self._use_c_vec + use_d = self._use_d_vec + use_g = self._use_g_vec + # use_f = self._use_fpolys #### also reinitialize F polynomials? -J + # if use_f: + # self.use_fpolys(False) + if use_g: + self.use_g_vectors(False) + if use_c: + self.use_c_vectors(False) + if use_d: + self.use_d_vectors(False) + if use_g: + self.use_g_vectors() + if use_c: + self.use_c_vectors() + if use_d: + self.use_d_vectors() + # if use_f: + # self.use_fpolys() + else: + self._mut_path = None + + def _sanitize_init_vars(self, user_labels, user_labels_prefix = 'x'): + r""" + Warning: This is an internal method that rewrites a user-given set of cluster variable names into a format that Sage can utilize. + + INPUT: + + - ``user_labels`` -- The labels that need sanitizing + - ``user_labels_prefix`` -- (default:'x') The prefix to use for labels if integers given for labels + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4]); S._init_vars + {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} + sage: S._sanitize_init_vars([1,2,3,4],'z') + sage: S._init_vars + {0: 'z1', 1: 'z2', 2: 'z3', 3: 'z4'} + + sage: S = ClusterSeed(['A',4]); S._init_vars + {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} + sage: S._sanitize_init_vars(['a', 'b', 'c', 'd']) + sage: S._init_vars + {0: 'a', 1: 'b', 2: 'c', 3: 'd'} + + + """ + if isinstance(user_labels,list): + self._init_vars = {} + for i in xrange(len(user_labels)): + if isinstance(user_labels[i], Integer): + self._init_vars[i] = user_labels_prefix+user_labels[i].str() + elif isinstance(user_labels[i], list): + self._user_labels_prefix = user_labels_prefix + strng = self._user_labels_prefix + for j in user_labels[i]: + if isinstance(j, Integer): + strng = strng+"_"+j.str() + else: + strng = strng+"_"+j + self._init_vars[i] = strng + else: + self._init_vars[i] = user_labels[i] + elif isinstance(user_labels,dict): + self._init_vars = user_labels + else: + raise ValueError("The input 'user_labels' must be a dictionary or a list.") + if len(self._init_vars.keys()) != self._n+self._m: + raise ValueError("The number of user-defined labels is not the number of exchangeable and frozen variables.") + + def set_c_matrix(self, data): + r""" + Will force set the c matrix according to a matrix, a quiver, or a seed. + + INPUT: + + - ``data`` -- The matrix to set the c matrix to. Also allowed + to be a quiver or cluster seed, in which case the b_matrix + is used. + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]); + sage: X = matrix([[0,0,1],[0,1,0],[1,0,0]]) + sage: S.set_c_matrix(X) + sage: S.c_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + + sage: Y = matrix([[-1,0,1],[0,1,0],[1,0,0]]) + sage: S.set_c_matrix(Y) + C matrix does not look to be valid - there exists a column containing positive and negative entries. + Continuing... + + sage: Z = matrix([[1,0,1],[0,1,0],[2,0,2]]) + sage: S.set_c_matrix(Z) + C matrix does not look to be valid - not a linearly independent set. + Continuing... + """ + if isinstance(data, ClusterQuiver): + data = data.b_matrix() + if isinstance(matrix, ClusterSeed): + data=data.b_matrix() + + if data.determinant() == 0: + print "C matrix does not look to be valid - not a linearly independent set." + print "Continuing..." + + # Do a quick check to make sure that each column is either all positive or all negative. + # Can do this through green/red vertices + greens = Set(get_green_vertices(data)) + reds = Set(get_red_vertices(data)) + if greens.intersection(reds).cardinality() > 0 or greens.union(reds).cardinality() < data.ncols(): + print "C matrix does not look to be valid - there exists a column containing positive and negative entries." + print "Continuing..." + + self._C = data + self._BC = copy(self._M.stack(self._C)) def __eq__(self, other): r""" - Returns True iff ``self`` represent the same cluster seed as ``other``. + Returns True iff ``self`` represent the same cluster seed as ``other`` and all tracked data agrees. EXAMPLES:: @@ -144,8 +824,63 @@ def __eq__(self, other): sage: T.mutate( 2 ) sage: S.__eq__( T ) True + + sage: S = ClusterSeed(['A',2]) + sage: T = ClusterSeed(S) + sage: S.__eq__( T ) + True + + sage: S.mutate([0,1,0,1,0]) + sage: S.__eq__( T ) + False + sage: S.cluster() + [x1, x0] + sage: T.cluster() + [x0, x1] + + sage: S.mutate([0,1,0,1,0]) + sage: S.__eq__( T ) + True + sage: S.cluster() + [x0, x1] + """ + if not isinstance(other, ClusterSeed): + return False + g_vec = True + c_vec = True + d_vec = True + clusters = True + ExMat = self._M == other._M + if self._use_fpolys and other._use_fpolys: + clusters = self.cluster() == other.cluster() and self.ground_field() == other.ground_field() + elif self._use_g_vec and other._use_g_vec: + g_vec = self.g_matrix() == other.g_matrix() + if self._use_c_vec and other._use_c_vec: + c_vec = self.c_matrix() == other.c_matrix() + if self._use_d_vec and other._use_d_vec: + d_vec = self.d_matrix() == other.d_matrix() + return g_vec and c_vec and d_vec and clusters and ExMat + + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterSeed(['A',5]) + sage: hash(Q) # indirect doctest + -5649412990944896369 # 64-bit + 222337679 # 32-bit """ - return isinstance(other, ClusterSeed) and self._M == other._M and self._cluster == other._cluster + # mat_hash = self._M.__hash__() + if self._use_fpolys: + return tuple(self.cluster()).__hash__() + elif self._use_g_vec: + return self.g_matrix().__hash__() + elif self._use_c_vec: + return self.c_matrix().__hash__() + elif self._use_d_vec: + return self.d_matrix().__hash__() def _repr_(self): r""" @@ -177,7 +912,7 @@ def _repr_(self): name += ' with %s frozen variables'%self._m return name - def plot(self, circular=False, mark=None, save_pos=False): + def plot(self, circular=False, mark=None, save_pos=False, force_c =False, with_greens=False, add_labels = False): r""" Returns the plot of the quiver of ``self``. @@ -186,6 +921,9 @@ def plot(self, circular=False, mark=None, save_pos=False): - ``circular`` -- (default:False) if True, the circular plot is chosen, otherwise >>spring<< is used. - ``mark`` -- (default: None) if set to i, the vertex i is highlighted. - ``save_pos`` -- (default:False) if True, the positions of the vertices are saved. + - ``force_c`` -- (default:False) if True, will show the frozen vertices even if they were never initialized + - ``with_greens`` -- (default:False) if True, will display the green vertices in green + - ``add_labels`` -- (default:False) if True, will use the initial variables as labels EXAMPLES:: @@ -193,9 +931,22 @@ def plot(self, circular=False, mark=None, save_pos=False): sage: pl = S.plot() sage: pl = S.plot(circular=True) """ - return self.quiver().plot(circular=circular,mark=mark,save_pos=save_pos) - def show(self, fig_size=1, circular=False, mark=None, save_pos=False): + greens = [] + if with_greens: + greens = self.green_vertices() + + if force_c: + quiver = ClusterQuiver(self._BC) + elif add_labels: + # relabelling multiple times causes errors, so we need to always do it in place + quiver = self.quiver().relabel(self._init_vars, inplace=True) + else: + quiver = self.quiver() + + return quiver.plot(circular=circular,mark=mark,save_pos=save_pos, greens=greens) + + def show(self, fig_size=1, circular=False, mark=None, save_pos=False, force_c = False, with_greens= False, add_labels = False): r""" Shows the plot of the quiver of ``self``. @@ -205,13 +956,29 @@ def show(self, fig_size=1, circular=False, mark=None, save_pos=False): - ``circular`` -- (default: False) if True, the circular plot is chosen, otherwise >>spring<< is used. - ``mark`` -- (default: None) if set to i, the vertex i is highlighted. - ``save_pos`` -- (default:False) if True, the positions of the vertices are saved. + - ``force_c`` -- (default:False) if True, will show the frozen vertices even if they were never initialized + - ``with_greens`` -- (default:False) if True, will display the green vertices in green + - ``add_labels`` -- (default:False) if True, will use the initial variables as labels TESTS:: sage: S = ClusterSeed(['A',5]) sage: S.show() # long time """ - self.quiver().show(fig_size=fig_size, circular=circular,mark=mark,save_pos=save_pos) + + greens = [] + if with_greens: + greens = self.green_vertices() + + if force_c: + quiver = ClusterQuiver(self._BC) + elif add_labels: + # relabelling multiple times causes errors, so we need to always do it in place + quiver = self.quiver().relabel(self._init_vars, inplace=True) + else: + quiver = self.quiver() + + quiver.show(fig_size=fig_size, circular=circular,mark=mark,save_pos=save_pos, greens=greens) def interact(self, fig_size=1, circular=True): r""" @@ -228,6 +995,7 @@ def interact(self, fig_size=1, circular=True): sage: S.interact() # long time 'The interactive mode only runs in the Sage notebook.' """ + ## Also update so works in cloud and not just notebook from sage.plot.plot import EMBEDDED_MODE from sagenb.notebook.interact import interact, selector from sage.misc.all import html,latex @@ -333,7 +1101,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def ground_field(self): r""" @@ -343,7 +1111,7 @@ def ground_field(self): sage: S = ClusterSeed(['A',3]) sage: S.ground_field() - Fraction Field of Multivariate Polynomial Ring in x0, x1, x2 over Rational Field + Multivariate Polynomial Ring in x0, x1, x2, y0, y1, y2 over Rational Field """ return self._R @@ -364,9 +1132,10 @@ def x(self,k): sage: S.x(2) x2 """ - if k in range(self._n): + + if self._use_fpolys and k in range(self._n): x = self._R.gens()[k] - return ClusterVariable( x.parent(), x.numerator(), x.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) + return ClusterVariable(FractionField(self._R), x.numerator(), x.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ,xdim=self._n) else: raise ValueError("The input is not in an index of a cluster variable.") @@ -387,9 +1156,10 @@ def y(self,k): sage: S.y(2) y2 """ - if k in range(self._m): + + if self._use_fpolys and k in range(self._m): x = self._R.gens()[self._n+k] - return ClusterVariable( x.parent(), x.numerator(), x.denominator(), mutation_type=self._mutation_type, variable_type='frozen variable' ) + return ClusterVariable( FractionField(self._R), x.numerator(), x.denominator(), mutation_type=self._mutation_type, variable_type='frozen variable',xdim=self._n ) else: raise ValueError("The input is not in an index of a frozen variable.") @@ -424,26 +1194,72 @@ def m(self): """ return self._m - def cluster_variable(self,k): + def mutations(self): r""" - Returns the `k`-th *cluster variable* of ``self``. + Returns the list of mutations ``self`` has undergone if they are being tracked. - EXAMPLES:: + Examples:: sage: S = ClusterSeed(['A',3]) - sage: S.mutate([1,2]) + sage: S.mutations() + [] - sage: [S.cluster_variable(k) for k in range(3)] - [x0, (x0*x2 + 1)/x1, (x0*x2 + x1 + 1)/(x1*x2)] + sage: S.mutate([0,1,0,2]) + sage: S.mutations() + [0, 1, 0, 2] + + sage: S.track_mutations(False) + sage: S.mutations() + Traceback (most recent call last): + ... + ValueError: Not recording mutation sequence. Need to track mutations. """ - if k not in range(self._n): - raise ValueError("The cluster seed does not have a cluster variable of index %s."%k) - f = self._cluster[k] - return ClusterVariable( f.parent(), f.numerator(), f.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) + if self._track_mut: + return copy(self._mut_path) + else: + raise ValueError("Not recording mutation sequence. Need to track mutations.") + + def cluster_variable(self, k): + r""" + Generates a cluster variable using F-polynomials + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S.mutate([0,1]) + sage: S.cluster_variable(0) + (x1 + 1)/x0 + sage: S.cluster_variable(1) + (x0*x2 + x1 + 1)/(x0*x1) + """ + if self._use_fpolys: + IE = self._init_exch.values() + if (k in xrange(self._n)) or (k in IE): + if k in xrange(self._n): + pass + elif k in IE: + k = IE.index(k) + + g_mon = prod([self._R.gen(i)**self._G[i,k] for i in xrange(self._n)]) + F_num = self._F[IE[k]].subs(self._yhat) + F_den = self._R(self._F[IE[k]].subs(self._y).denominator()) + cluster_variable = g_mon*F_num*F_den + + return ClusterVariable(FractionField(self._R), cluster_variable.numerator(), cluster_variable.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=self._n) + else: + raise ValueError('No cluster variable with index or label ' + str(k) + '.') + elif self._track_mut: # if we can recreate the clusters + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) + catchup.mutate(self.mutations()) + return catchup.cluster_variable(k) + else: + raise ValueError('Clusters not being tracked') + return None def cluster(self): r""" - Returns the *cluster* of ``self``. + Returns a copy of the *cluster* of ``self``. EXAMPLES:: @@ -463,16 +1279,63 @@ def cluster(self): sage: S.cluster() [x0, x1, x2] """ - return [ self.cluster_variable(k) for k in range(self._n) ] - def f_polynomial(self,k,ignore_coefficients=False): + if not self._use_fpolys: + if self._track_mut: # if we can recreate the clusters + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) + catchup.mutate(self.mutations()) + return catchup.cluster() + else: + raise ValueError('Clusters not being tracked') + elif self._cluster is None: + self._cluster = [ self.cluster_variable(k) for k in range(self._n) ] + return copy(self._cluster) + + def _f_mutate( self, k): + r""" + An internal procedure that returns ``self`` with F-polynomials mutated at k. + + WARNING: This function assumes you are sending it good data + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S._f_mutate(0) + sage: S.f_polynomial(0) + y0 + 1 + """ + if self._use_fpolys: + IE = self._init_exch.values() + else: + IE = [] + + F = self._F + B = self.b_matrix() + C = self.c_matrix() + + # F-polynomials + pos = self._U(1) + neg = self._U(1) + + for j in xrange(self._n): + if C[j,k] > 0: + pos *= self._U.gen(j)**C[j,k] + else: + neg *= self._U.gen(j)**(-C[j,k]) + if B[j,k] > 0: + pos *= F[IE[j]]**B[j,k] + else: + neg *= F[IE[j]]**(-B[j,k]) + + # can the following be improved? + self._F[IE[k]] = (pos+neg)//F[IE[k]] + + def f_polynomial(self,k): r""" Returns the ``k``-th *F-polynomial* of ``self``. It is obtained from the ``k``-th cluster variable by setting all `x_i` to `1`. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -484,37 +1347,41 @@ def f_polynomial(self,k,ignore_coefficients=False): sage: [S.f_polynomial(k) for k in range(3)] [1, y1*y2 + y2 + 1, y1 + 1] - sage: S = ClusterSeed(Matrix([[0,1],[-1,0],[1,0],[-1,1]])); S + sage: S = ClusterSeed(Matrix([[0,1],[-1,0],[1,0],[-1,1]])); S.use_c_vectors(bot_is_c=True); S A seed for a cluster algebra of rank 2 with 2 frozen variables sage: T = ClusterSeed(Matrix([[0,1],[-1,0]])).principal_extension(); T A seed for a cluster algebra of rank 2 with principal coefficients sage: S.mutate(0) sage: T.mutate(0) sage: S.f_polynomials() - Traceback (most recent call last): - ... - ValueError: No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this. - sage: S.f_polynomials(ignore_coefficients=True) [y0 + y1, 1] sage: T.f_polynomials() [y0 + 1, 1] """ - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") + if self._use_fpolys: + IE = self._init_exch.values() + if k in xrange(self._n): + pass + elif k in IE: + k = IE.index(k) + else: + raise ValueError("The cluster seed does not have a cluster variable of index %s."%k) - if k not in range(self._n): - raise ValueError("The cluster seed does not have a cluster variable of index %s."%k) - eval_dict = dict( [ ( self.x(i), 1 ) for i in range(self._n) ] ) - return self.cluster_variable(k).subs(eval_dict) + return self._F[IE[k]] + elif self._track_mut: + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) + catchup.mutate(self.mutations()) + + return catchup.f_polynomial(k) + else: + raise ValueError("Turn on use_fpolys to get F polynomial %s."%k) - def f_polynomials(self,ignore_coefficients=False): + def f_polynomials(self): r""" Returns all *F-polynomials* of ``self``. These are obtained from the cluster variables by setting all `x_i`'s to `1`. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -526,19 +1393,14 @@ def f_polynomials(self,ignore_coefficients=False): sage: S.f_polynomials() [1, y1*y2 + y2 + 1, y1 + 1] """ - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") - return [ self.f_polynomial(k,ignore_coefficients=ignore_coefficients) for k in range(self._n) ] + return [self.f_polynomial(i) for i in xrange(self._n)] - def g_vector(self,k,ignore_coefficients=False): + def g_vector(self,k): r""" Returns the ``k``-th *g-vector* of ``self``. This is the degree vector of the ``k``-th cluster variable after setting all `y_i`'s to `0`. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -550,25 +1412,33 @@ def g_vector(self,k,ignore_coefficients=False): sage: [ S.g_vector(k) for k in range(3) ] [(1, 0, 0), (0, 0, -1), (0, -1, 0)] """ - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") + + if not (self._is_principal or self._use_g_vec or (self._use_fpolys and self._cluster)): + raise ValueError("Unable to calculate g-vectors. Need to use g vectors.") if k not in range(self._n): raise ValueError("The cluster seed does not have a cluster variable of index %s."%k) - f = self.cluster_variable(k) - eval_dict = dict( [ ( self.y(i), 0 ) for i in range(self._m) ] ) - f0 = f.subs(eval_dict) - d1 = f0.numerator().degrees() - d2 = f0.denominator().degrees() - return tuple( d1[i] - d2[i] for i in range(self._n) ) - def g_matrix(self,ignore_coefficients=False): + if self._use_g_vec: # This implies the g-matrix is maintained by the mutate function and will always be up to date + return copy(self._G.column(k)) + elif self._use_fpolys and self._cluster: + f = copy(self.cluster_variable(k)) + eval_dict = dict( [ ( self.y(i), 0 ) for i in range(self._m) ] ) + f0 = f.subs(eval_dict) + d1 = f0.numerator().degrees() + d2 = f0.denominator().degrees() + return tuple( d1[i] - d2[i] for i in range(self._n) ) + else: # in the is_principal=True case + try: + # ensure that we cannot create a loop by calling g_matrix() here by filtering out loop causing conditions in the previous if-elif sections + return self.g_matrix().column(k) + except ValueError: + raise ValueError("Unable to calculate g-vectors. Need to use g vectors.") + + def g_matrix(self, show_warnings=True): r""" - Returns the matrix of all *g-vectors* of ``self``. This are the degree vectors + Returns the matrix of all *g-vectors* of ``self``. These are the degree vectors of the cluster variables after setting all `y_i`'s to `0`. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -583,36 +1453,98 @@ def g_matrix(self,ignore_coefficients=False): [ 0 -1 0] sage: S = ClusterSeed(['A',3]) - sage: S2 = S.principal_extension() sage: S.mutate([0,1]) - sage: S2.mutate([0,1]) sage: S.g_matrix() - Traceback (most recent call last): - ... - ValueError: No principal coefficients initialized. Use - principal_extension, or ignore_coefficients to ignore this. - sage: S.g_matrix(ignore_coefficients=True) - [-1 0 0] - [ 1 0 0] - [ 0 1 1] - sage: S2.g_matrix() [-1 -1 0] [ 1 0 0] [ 0 0 1] + + sage: S = ClusterSeed(['A',4]); S.use_g_vectors(False); S.use_fpolys(False); S.g_matrix() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + + sage: S = ClusterSeed(['A',4]) + sage: S.use_g_vectors(False); S.use_c_vectors(False); S.use_fpolys(False); S.track_mutations(False); S.g_matrix() + Traceback (most recent call last): + ... + ValueError: Unable to calculate g-vectors. Need to use g vectors. """ + from sage.matrix.all import matrix - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") - return matrix( [ self.g_vector(k,ignore_coefficients=ignore_coefficients) for k in range(self._n) ] ).transpose() + if self._use_g_vec: + return copy(self._G) + elif self._use_fpolys and self._cluster: # This only calls g_vector when it will not create a loop. + return matrix( [ self.g_vector(k) for k in range(self._n) ] ).transpose() + elif self._use_c_vec: + if self.b_matrix().is_skew_symmetric(): + return copy(self._C).inverse().transpose() + elif self._track_mut: + BC1 = copy(self._b_initial[0:self._n]) + BC1 = -BC1.transpose() + BC1 = BC1.stack(matrix.identity(self._n)) + seq = iter(self.mutations()) + for k in seq: + BC1.mutate(k) + return copy(BC1[self._n:2*self._n]).inverse().transpose() + else: + raise ValueError("Unable to calculate g-vectors. Need to use g vectors.") + elif self._track_mut: + catchup = ClusterSeed(self._b_initial) + catchup.use_fpolys(False) + catchup.mutate(self.mutations()) + return catchup.g_matrix() + elif show_warnings: + raise ValueError("Unable to calculate g-vectors. Need to use g vectors.") + else: + return None - def c_vector(self,k,ignore_coefficients=False): + def _g_mutate(self, k): + r""" + An internal procedure that returns ``self`` with g-vectors mutated at k. + + WARNING: This function assumes you are sending it good data + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S._g_mutate(0) + sage: S.g_vector(0) + (-1, 1, 0) + + REFERENCES: + + .. [NaZe2011] Tomoki Nakanishi and Andrei Zelevinsky + *On Tropical Dualities In Cluster Algebras* + :arxiv:`1101.3736v3` + """ + from sage.matrix.all import identity_matrix + + if self._use_fpolys: + IE = self._init_exch.values() + else: + IE = [] + + B = self.b_matrix() + C = self.c_matrix() + + # G-matrix + J = identity_matrix(self._n) + if any(x > 0 for x in C.column(k)): + eps = +1 + else: + eps = -1 + for j in xrange(self._n): + J[j,k] += max(0, -eps*B[j,k]) + J[k,k] = -1 + self._G = self._G*J + + def c_vector(self,k): r""" Returns the ``k``-th *c-vector* of ``self``. It is obtained as the ``k``-th column vector of the bottom part of the ``B``-matrix of ``self``. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -627,25 +1559,27 @@ def c_vector(self,k,ignore_coefficients=False): sage: S = ClusterSeed(Matrix([[0,1],[-1,0],[1,0],[-1,1]])); S A seed for a cluster algebra of rank 2 with 2 frozen variables sage: S.c_vector(0) - Traceback (most recent call last): - ... - ValueError: No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this. - sage: S.c_vector(0,ignore_coefficients=True) + (1, 0) + + sage: S = ClusterSeed(Matrix([[0,1],[-1,0],[1,0],[-1,1]])); S.use_c_vectors(bot_is_c=True); S + A seed for a cluster algebra of rank 2 with 2 frozen variables + sage: S.c_vector(0) (1, -1) + """ if k not in range(self._n): raise ValueError("The cluster seed does not have a c-vector of index %s."%k) - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") - return tuple( self._M[i,k] for i in range(self._n,self._n+self._m) ) + if not (self._is_principal or self._use_c_vec): + raise ValueError("Requires C vectors to use.") + if self._use_c_vec: + return self.c_matrix().column(k) + else: + return tuple( self._M[i,k] for i in range(self._n,self._n+self._m) ) - def c_matrix(self,ignore_coefficients=False): + def c_matrix(self,show_warnings=True): r""" Returns all *c-vectors* of ``self``. - Requires principal coefficients, initialized by using principal_extension(), - or the user can set 'ignore_coefficients=True' to bypass this restriction. - Warning: this method assumes the sign-coherence conjecture and that the input seed is sign-coherent (has an exchange matrix with columns of like signs). Otherwise, computational errors might arise. @@ -658,11 +1592,150 @@ def c_matrix(self,ignore_coefficients=False): [ 1 0 0] [ 0 0 -1] [ 0 -1 0] + + sage: S = ClusterSeed(['A',4]); + sage: S.use_g_vectors(False); S.use_fpolys(False); S.use_c_vectors(False); S.use_d_vectors(False); S.track_mutations(False); + sage: S.c_matrix() + Traceback (most recent call last): + ... + ValueError: Unable to calculate c-vectors. Need to use c vectors. """ - if not (ignore_coefficients or self._is_principal): - raise ValueError("No principal coefficients initialized. Use principal_extension, or ignore_coefficients to ignore this.") - return self._M.submatrix(self._n,0) + if self._bot_is_c: + return copy(self._M[self._m:(self._n+self._m),:self._n]) + elif self._use_c_vec: + return copy(self._C) + elif self._use_g_vec or self._use_fpolys: #both of these will populate g_matrix() successfully + if self.b_matrix().is_skew_symmetric(): + return self.g_matrix().inverse().transpose() + elif self._track_mut: + BC1 = copy(self._b_initial[0:self._n]) + BC1 = BC1.stack(matrix.identity(self._n)) + seq = iter(self.mutations()) + for k in seq: + BC1.mutate(k) + return copy(BC1[self._n:2*self._n]) + else: + raise ValueError("Unable to calculate c-vectors. Need to use c vectors.") + elif self._track_mut: + BC1 = copy(self._b_initial[0:self._n]) + BC1 = BC1.stack(matrix.identity(self._n)) + seq = iter(self.mutations()) + for k in seq: + BC1.mutate(k) + return copy(BC1[self._n:2*self._n]) + elif show_warnings: + raise ValueError("Unable to calculate c-vectors. Need to use c vectors.") + else: + return None + + def d_vector(self, k): + r""" + Returns the ``k``-th *d-vector* of ``self``. This is the exponent vector + of the denominator of the ``k``-th cluster variable. + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S.mutate([2,1,2]) + sage: [ S.d_vector(k) for k in range(3) ] + [(-1, 0, 0), (0, 1, 1), (0, 1, 0)] + """ + from sage.modules.free_module_element import vector + + if self._use_d_vec: + return copy(self._D).column(k) + elif self._use_fpolys: + f = self.cluster_variable(k) + if f in self._R.gens(): + return -vector(f.numerator().monomials()[0].exponents()[0][:self._n]) + return vector(f.denominator().monomials()[0].exponents()[0][:self._n]) + elif self._track_mut: + catchup = ClusterSeed(self._b_initial) + catchup.use_fpolys(False) + catchup.use_g_vectors(False) + catchup.use_c_vectors(False) + + catchup.mutate(self.mutations()) + return copy(catchup._D).column(k) + else: + raise ValueError("Unable to calculate d-vector %s. Need to use d vectors."%k) + + def d_matrix(self, show_warnings=True): + r""" + Returns the matrix of *d-vectors* of ``self``. + + EXAMPLES:: + sage: S = ClusterSeed(['A',4]); S.d_matrix() + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + sage: S.mutate([1,2,1,0,1,3]); S.d_matrix() + [1 1 0 1] + [1 1 1 1] + [1 0 1 1] + [0 0 0 1] + + + """ + if not (self._use_d_vec or self._use_fpolys or self._track_mut): + #raise ValueError("No d-vectors initialized.") + raise ValueError("Unable to calculate d-vectors. Need to use d vectors.") + if self._use_d_vec: + return copy(self._D) + elif self._use_fpolys: + return matrix( [ self.d_vector(k) for k in range(self._n) ] ).transpose() + elif self._track_mut: + catchup = ClusterSeed(self._b_initial) + catchup.use_fpolys(False) + catchup.use_g_vectors(False) + catchup.use_c_vectors(False) + catchup.track_mutations(False) + + catchup.mutate(self.mutations()) + return catchup.d_matrix() + elif show_warnings: + raise ValueError("No valid way to calculate d-vectors") + + def _d_mutate(self, k): + r""" + An internal procedure that returns ``self`` with d-vectors mutated at k. + + WARNING: This function assumes you are sending it good data (does not check for sanitized inputs) + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S._d_mutate(0) + sage: S.d_matrix() + [ 1 0 0] + [ 0 -1 0] + [ 0 0 -1] + sage: S.d_vector(0) + (1, 0, 0) + + """ + if self._use_fpolys: + IE = self._init_exch.values() + else: + IE = [] + + B = self.b_matrix() + D = copy(self._D) + dnew = copy(-D.column(k)) + dp = copy( dnew.parent().zero() ) + dn = copy( dnew.parent().zero() ) + dmax = copy( dnew.parent().zero() ) + + for j in xrange(self._n): + if B[j,k] >0: + dp += B[j,k]*D.column(j) + elif B[j,k] <0: + dn -= B[j,k]*D.column(j) + for i in xrange(self._n): + dmax[i] = max(dp[i],dn[i]) + self._D.set_column(k,dnew+dmax) def coefficient(self,k): r""" @@ -676,77 +1749,390 @@ def coefficient(self,k): [y0, 1/y2, 1/y1] """ from sage.misc.all import prod + if k not in range(self._n): raise ValueError("The cluster seed does not have a coefficient of index %s."%k) if self._m == 0: return self.x(0)**0 - #### Note: this special case m = 0 no longer needed except if we want type(answer) to be a cluster variable rather than an integer. else: - exp = self.c_vector(k,ignore_coefficients=True) + try: # are c vectors being tracked? + exp = self.c_vector(k) + except: # if not try and reconstruct them + try: + exp = self.c_matrix().column(k) + except: + raise ValueError("Unable to calculate coefficients without c vectors enabled.") + return prod( self.y(i)**exp[i] for i in xrange(self._m) ) - def coefficients(self): - r""" - Returns all *coefficients* of ``self``. + def coefficients(self): + r""" + Returns all *coefficients* of ``self``. + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]).principal_extension() + sage: S.mutate([2,1,2]) + sage: S.coefficients() + [y0, 1/y2, 1/y1] + """ + # exceptions are caught in the subroutine. + return [ self.coefficient(k) for k in range(self._n) ] + + def quiver(self): + r""" + Returns the *quiver* associated to ``self``. + + EXAMPLES:: + + sage: S = ClusterSeed(['A',3]) + sage: S.quiver() + Quiver on 3 vertices of type ['A', 3] + """ + from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver + if self._quiver is None: + self._quiver = ClusterQuiver( self._M ) + return self._quiver + + def is_acyclic(self): + r""" + Returns True iff self is acyclic (i.e., if the underlying quiver is acyclic). + + EXAMPLES:: + + sage: ClusterSeed(['A',4]).is_acyclic() + True + + sage: ClusterSeed(['A',[2,1],1]).is_acyclic() + True + + sage: ClusterSeed([[0,1],[1,2],[2,0]]).is_acyclic() + False + """ + + return self.quiver()._digraph.is_directed_acyclic() + + def is_bipartite(self,return_bipartition=False): + r""" + Returns True iff self is bipartite (i.e., if the underlying quiver is bipartite). + + INPUT: + + - return_bipartition -- (default:False) if True, the bipartition is returned in the case of ``self`` being bipartite. + + EXAMPLES:: + + sage: ClusterSeed(['A',[3,3],1]).is_bipartite() + True + + sage: ClusterSeed(['A',[4,3],1]).is_bipartite() + False + """ + + return self.quiver().is_bipartite(return_bipartition=return_bipartition) + + def green_vertices(self): + r""" + Return the list of green vertices of ``self``. + + A vertex is defined to be green if its c-vector has all non-positive + entries. More information on green vertices can be found at [BDP2013]_ + + OUTPUT: + + The green vertices as a list of integers. + + EXAMPLES:: + + sage: ClusterSeed(['A',3]).principal_extension().green_vertices() + [0, 1, 2] + + sage: ClusterSeed(['A',[3,3],1]).principal_extension().green_vertices() + [0, 1, 2, 3, 4, 5] + """ + + # Make sure we have c vectors + if not self._use_c_vec: + raise ValueError("Must use c vectors to grab the vertices.") + + return get_green_vertices(self._C) + + def first_green_vertex(self): + r""" + Return the first green vertex of ``self``. + + A vertex is defined to be green if its c-vector has all non-positive entries. + More information on green vertices can be found at [BDP2013]_ + + EXAMPLES:: + + sage: ClusterSeed(['A',3]).principal_extension().first_green_vertex() + 0 + + sage: ClusterSeed(['A',[3,3],1]).principal_extension().first_green_vertex() + 0 + """ + # Make sure we have c vectors + if not self._use_c_vec: + raise ValueError("Must use c vectors to grab the vertices.") + + greens = self.green_vertices() + if len(greens) > 0: + return greens[0] + + return None + + def red_vertices(self): + r""" + Return the list of red vertices of ``self``. + + A vertex is defined to be red if its c-vector has all non-negative entries. + More information on red vertices can be found at [BDP2013]_. + + OUTPUT: + + The red vertices as a list of integers. + + EXAMPLES:: + + sage: ClusterSeed(['A',3]).principal_extension().red_vertices() + [] + + sage: ClusterSeed(['A',[3,3],1]).principal_extension().red_vertices() + [] + + sage: Q = ClusterSeed(['A',[3,3],1]).principal_extension(); + sage: Q.mutate(1); + sage: Q.red_vertices() + [1] + + """ + # Make sure we have c vectors on + if not self._use_c_vec: + raise ValueError("Must use c vectors to grab the vertices.") + + return get_red_vertices(self._C) + + def first_red_vertex(self): + r""" + Return the first red vertex of ``self``. + + A vertex is defined to be red if its c-vector has all non-negative entries. + More information on red vertices can be found at [BDP2013]_. + + EXAMPLES:: + + sage: ClusterSeed(['A',3]).principal_extension().first_red_vertex() + + sage: ClusterSeed(['A',[3,3],1]).principal_extension().first_red_vertex() + + sage: Q = ClusterSeed(['A',[3,3],1]).principal_extension(); + sage: Q.mutate(1); + sage: Q.first_red_vertex() + 1 + + """ + # Make sure we have c vectors + if not self._use_c_vec: + raise ValueError("Must use c vectors to grab the vertices.") + + reds = self.red_vertices() + if len(reds) > 0: + return reds[0] + + return None + + def urban_renewals(self, return_first=False): + r""" + Return the list of the urban renewal vertices of ``self``. + + An urban renewal vertex is one in which there are two arrows pointing + toward the vertex and two arrows pointing away. + + INPUT: + + - ``return_first`` -- (default:False) if True, will return the first urban renewal + + OUTPUT: + + A list of vertices (as integers) EXAMPLES:: - sage: S = ClusterSeed(['A',3]).principal_extension() - sage: S.mutate([2,1,2]) - sage: S.coefficients() - [y0, 1/y2, 1/y1] + sage: G = ClusterSeed(['GR',[4,9]]); G.urban_renewals() + [5, 6] """ - return [ self.coefficient(k) for k in range(self._n) ] - - def quiver(self): + vertices = [] + for i in range(self._n): + if self.quiver().digraph().in_degree(i) == 2 and self.quiver().digraph().out_degree(i) == 2: + if return_first: + return i + vertices.append(i) + + if return_first: + return None + return vertices + + def first_urban_renewal(self): r""" - Returns the *quiver* associated to ``self``. + Return the first urban renewal vertex. + + An urban renewal vertex is one in which there are two arrows pointing + toward the vertex and two arrows pointing away. EXAMPLES:: - sage: S = ClusterSeed(['A',3]) - sage: S.quiver() - Quiver on 3 vertices of type ['A', 3] + sage: G = ClusterSeed(['GR',[4,9]]); G.first_urban_renewal() + 5 """ - from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver - if self._quiver is None: - self._quiver = ClusterQuiver( self._M ) - return self._quiver + return self.urban_renewals(return_first=True) - def is_acyclic(self): + def highest_degree_denominator(self, filter=None): r""" - Returns True iff self is acyclic (i.e., if the underlying quiver is acyclic). + Return the vertex of the cluster polynomial with highest degree in the denominator. - EXAMPLES:: + INPUT: - sage: ClusterSeed(['A',4]).is_acyclic() - True + - ``filter`` - Filter should be a list or iterable - sage: ClusterSeed(['A',[2,1],1]).is_acyclic() - True + OUTPUT: - sage: ClusterSeed([[0,1],[1,2],[2,0]]).is_acyclic() - False + Returns an integer. + + EXAMPLES:: + + sage: B = matrix([[0,-1,0,-1,1,1],[1,0,1,0,-1,-1],[0,-1,0,-1,1,1],[1,0,1,0,-1,-1],[-1,1,-1,1,0,0],[-1,1,-1,1,0,0]]) + sage: C = ClusterSeed(B).principal_extension(); C.mutate([0,1,2,4,3,2,5,4,3]) + sage: C.highest_degree_denominator() + 5 """ - return self.quiver()._digraph.is_directed_acyclic() + if filter is None: + filter = xrange(len(self.cluster())) + degree = 0 + vertex_to_mutate = [] + + # if we have d vectors use those, else see if we have clusters + if self._use_d_vec: + for i in list(enumerate(self.d_matrix().columns())): + if i[0] not in filter: + continue + col = i[1] + vertex = i[0] + cur_vertex_degree = sum(col) + if degree == cur_vertex_degree: + vertex_to_mutate.append(vertex) + if degree < cur_vertex_degree: + degree = cur_vertex_degree + vertex_to_mutate = [vertex] + elif self._use_fpolys: + for i in list(enumerate(self.cluster())): + if i[0] not in filter: + continue + vari = i[1] + vertex = i[0] + denom = vari.denominator() + cur_vertex_degree = denom.degree() + if degree == cur_vertex_degree: + vertex_to_mutate.append(vertex) + if degree < cur_vertex_degree: + degree = cur_vertex_degree + vertex_to_mutate = [vertex] + + + return_key = randint(0,len(vertex_to_mutate) - 1) + return vertex_to_mutate[return_key] + + def smallest_c_vector(self): + r""" + Return the vertex with the smallest c vector - def is_bipartite(self,return_bipartition=False): + OUTPUT: + + Returns an integer. + + EXAMPLES:: + sage: B = matrix([[0,2],[-2,0]]) + sage: C = ClusterSeed(B).principal_extension(); + sage: C.mutate(0) + sage: C.smallest_c_vector() + 0 + + """ + min_sum = infinity + vertex_to_mutate = [] + + for i in list(enumerate(self.c_matrix().columns())): + col = i[1] + vertex=i[0] + cur_vertex_sum = abs(sum(col)) + if min_sum == cur_vertex_sum: + vertex_to_mutate.append(vertex) + if min_sum > cur_vertex_sum: + min_sum = cur_vertex_sum + vertex_to_mutate = [vertex] + + return_key = randint(0,len(vertex_to_mutate) - 1) + return vertex_to_mutate[return_key] + + def most_decreased_edge_after_mutation(self): r""" - Returns True iff self is bipartite (i.e., if the underlying quiver is bipartite). - INPUT: + Return the vertex that will produce the least degrees after mutation - - return_bipartition -- (default:False) if True, the bipartition is returned in the case of ``self`` being bipartite. + EXAMPLES:: + + sage: S = ClusterSeed(['A',5]) + sage: S.mutate([0,2,3,1,2,3,1,2,0,2,3]) + sage: S.most_decreased_edge_after_mutation() + 2 + + """ + analysis = self.mutation_analysis(['edge_diff']) + least_edge = infinity + least_vertex = [] + for edge,edge_analysis in analysis.items(): + if least_edge == edge_analysis['edge_diff']: + least_vertex.append(edge) + if least_edge > edge_analysis['edge_diff']: + least_edge = edge_analysis['edge_diff'] + least_vertex = [edge] + + # if we have one vertex, return it + if len(least_vertex) == 1: + return least_vertex[0] + + # if not then do a test based on which one currently has the highest degree + return self.highest_degree_denominator(least_vertex) + + def most_decreased_denominator_after_mutation(self): + r""" + + Return the vertex that will produce the most decrease in denominator degrees after mutation EXAMPLES:: - sage: ClusterSeed(['A',[3,3],1]).is_bipartite() - True + sage: S = ClusterSeed(['A',5]) + sage: S.mutate([0,2,3,1,2,3,1,2,0,2,3]) + sage: S.most_decreased_denominator_after_mutation() + 2 - sage: ClusterSeed(['A',[4,3],1]).is_bipartite() - False """ - return self.quiver().is_bipartite(return_bipartition=return_bipartition) + analysis = self.mutation_analysis(['d_matrix']) + least_change = infinity + least_vertex = [] + current_columns = [sum(i) for i in self.d_matrix().columns()] + for vertex,edge_analysis in analysis.items(): + mutated_column = sum(edge_analysis['d_matrix'].column(vertex)) + + diff = mutated_column - current_columns[vertex] + if least_change == diff: + least_vertex.append(vertex) + if diff < least_change: + least_change = diff + least_vertex = [vertex] + + return_key = randint(0,len(least_vertex) - 1) + return least_vertex[return_key] def mutate(self, sequence, inplace=True): r""" @@ -754,9 +2140,22 @@ def mutate(self, sequence, inplace=True): INPUT: - - ``sequence`` -- a vertex of self or an iterator of vertices of self. + - ``sequence`` -- a vertex of ``self``, an iterator of vertices of ``self``, + a function which takes in the ClusterSeed and returns a vertex or an iterator of vertices, + or a string representing a type of vertices to mutate. - ``inplace`` -- (default: True) if False, the result is returned, otherwise ``self`` is modified. + Possible values for vertex types in ``sequence`` are: + + - ``"first_source"``: mutates at first found source vertex, + - ``"sources"``: mutates at all sources, + - ``"first_sink"``: mutates at first sink, + - ``"sinks"``: mutates at all sink vertices, + - ``"green"``: mutates at the first green vertex, + - ``"red"``: mutates at the first red vertex, + - ``"urban_renewal"`` or ``"urban"``: mutates at first urban renewal vertex, + - ``"all_urban_renewals"`` or ``"all_urban"``: mutates at all urban renewal vertices. + EXAMPLES:: sage: S = ClusterSeed(['A',4]); S.b_matrix() @@ -795,49 +2194,244 @@ def mutate(self, sequence, inplace=True): sage: T = S.mutate(0,inplace=False) sage: S == T False + + sage: Q = ClusterSeed(['A',3]);Q.b_matrix() + [ 0 1 0] + [-1 0 -1] + [ 0 1 0] + + sage: Q.mutate('first_sink');Q.b_matrix() + [ 0 -1 0] + [ 1 0 1] + [ 0 -1 0] + + sage: def last_vertex(self): return self._n - 1 + sage: Q.mutate(last_vertex); Q.b_matrix() + [ 0 -1 0] + [ 1 0 -1] + [ 0 1 0] + + sage: S = ClusterSeed(['A',4], user_labels=['a','b','c','d']); + sage: S.mutate('a'); S.mutate('(b+1)/a') + sage: S.cluster() + [a, b, c, d] + + sage: S = ClusterSeed(['A',4], user_labels=['a','b','c']); + Traceback (most recent call last): + ... + ValueError: The number of user-defined labels is not the number of exchangeable and frozen variables. + + sage: S = ClusterSeed(['A',4],user_labels=['x','y','w','z']) + sage: S.mutate('x') + sage: S.cluster() + [(y + 1)/x, y, w, z] + sage: S.mutate('(y+1)/x') + sage: S.cluster() + [x, y, w, z] + sage: S.mutate('y') + sage: S.cluster() + [x, (x*w + 1)/y, w, z] + sage: S.mutate('(x*w+1)/y') + sage: S.cluster() + [x, y, w, z] + + sage: S = ClusterSeed(['A',4], user_labels=[[1,2],[2,3],[4,5],[5,6]]); + sage: S.cluster() + [x_1_2, x_2_3, x_4_5, x_5_6] + sage: S.mutate('[1,2]'); + sage: S.cluster() + [(x_2_3 + 1)/x_1_2, x_2_3, x_4_5, x_5_6] + + sage: S = ClusterSeed(['A',4], user_labels=[[1,2],[2,3],[4,5],[5,6]],user_labels_prefix='P'); + sage: S.cluster() + [P_1_2, P_2_3, P_4_5, P_5_6] + sage: S.mutate('[1,2]') + sage: S.cluster() + [(P_2_3 + 1)/P_1_2, P_2_3, P_4_5, P_5_6] + sage: S.mutate('P_4_5') + sage: S.cluster() + [(P_2_3 + 1)/P_1_2, P_2_3, (P_2_3*P_5_6 + 1)/P_4_5, P_5_6] + + sage: S = ClusterSeed(['A',4]) + sage: S.mutate([0,1,0,1,0,2,1]) + sage: T = ClusterSeed(S) + sage: S.use_fpolys(False) + sage: S.use_g_vectors(False) + sage: S.use_c_vectors(False) + sage: S._C + sage: S._G + sage: S._F + sage: S.g_matrix() + [ 0 -1 0 0] + [ 1 1 1 0] + [ 0 0 -1 0] + [ 0 0 1 1] + sage: S.c_matrix() + [ 1 -1 0 0] + [ 1 0 0 0] + [ 1 0 -1 1] + [ 0 0 0 1] + sage: S.f_polynomials() == T.f_polynomials() + True + + sage: S.cluster() == T.cluster() + True + + sage: S._mut_path + [0, 1, 0, 1, 0, 2, 1] + """ + + # check for sanitizable data + if not isinstance(inplace, bool): + raise ValueError('The second parameter must be boolean. To mutate at a sequence of length 2, input it as a list.') + if inplace: seed = self else: - seed = ClusterSeed( self ) + seed = ClusterSeed( self) + + # If we get a string, execute as a function + if isinstance(sequence, str) and len(sequence) > 1 and sequence[0] is not '_': + if sequence is 'green': + sequence = self.first_green_vertex() + elif sequence is 'red': + sequence = self.first_red_vertex() + elif sequence is 'urban' or sequence is 'urban_renewal': + sequence = self.first_urban_renewal() + elif sequence is 'all_urbans' or sequence is 'all_urban_renewals': + sequence = self.urban_renewals() + elif hasattr(self, sequence): + sequence = getattr(self, sequence)() + elif hasattr(self.quiver(), sequence): + sequence = getattr(self.quiver(), sequence)() + # If we are given a list in string format + elif sequence[0] == '[' and sequence[-1] == ']': + # convert to list + from ast import literal_eval + temp_list = literal_eval(sequence) + + sequence = self._user_labels_prefix + for j in temp_list: + if isinstance(j, Integer): + sequence = sequence+"_"+j.str() + elif isinstance(j, int): + sequence = sequence+"_"+`j` + else: + sequence = sequence+"_"+j + + # If we get a function, execute it + if hasattr(sequence, '__call__'): + # function should return either integer or sequence + sequence = sequence(seed) + + + if sequence is None: + raise ValueError('Not mutating: No vertices given.') + + if seed._use_fpolys: + IE = seed._init_exch.values() + else: + IE = [] - n, m = seed._n, seed._m - V = range(n) + n, m = seed.n(), seed.m() + V = range(n)+IE - if sequence in V: - seq = [sequence] + if seed._use_fpolys and isinstance(sequence, str): + sequence = seed.cluster_index(sequence) + if sequence is None: + raise ValueError("Variable provided is not in our cluster") + + if (sequence in xrange(n)) or (sequence in IE): + seqq = [sequence] else: - seq = sequence - if isinstance(seq, tuple): - seq = list( seq ) - if not isinstance(seq, list): + seqq = sequence + + + + if isinstance(seqq, tuple): + seqq = list( seqq ) + if not isinstance(seqq, list): raise ValueError('The quiver can only be mutated at a vertex or at a sequence of vertices') - if not isinstance(inplace, bool): - raise ValueError('The second parameter must be boolean. To mutate at a sequence of length 2, input it as a list.') - if any( v not in V for v in seq ): - v = filter( lambda v: v not in V, seq )[0] - raise ValueError('The quiver cannot be mutated at the vertex ' + str( v )) + + # remove ineligible vertices + #if any( v not in V for v in seqq ): + #v = filter( lambda v: v not in V, seqq )[0] + #raise ValueError('The quiver cannot be mutated at the vertex ' + str( v )) + + seq = iter(seqq) for k in seq: - M = seed._M - cluster = seed._cluster - mon_p = seed._R(1) - mon_n = seed._R(1) - - for j in range(n+m): - if M[j,k] > 0: - mon_p = mon_p*cluster[j]**M[j,k] - elif M[j,k] < 0: - mon_n = mon_n*cluster[j]**(-M[j,k]) - - cluster[k] = (mon_p+mon_n)*cluster[k]**(-1) - seed._M.mutate(k) - #seed._M = _matrix_mutate( seed._M, k ) - - seed._quiver = None + + if k in xrange(n): + pass + elif seed._use_fpolys: + k = seed.cluster_index(k) + if k is None: + raise ValueError("Variable provided is not in our cluster") + else: + raise ValueError('Why wasnt this caught earlier? Cannot mutate in direction ' + str(k) + '.') + + if seed._use_fpolys: + seed._f_mutate(k) + + if seed._use_g_vec: + seed._g_mutate(k) + + if seed._use_d_vec: + seed._d_mutate(k) + + seed._BC.mutate(k) + seed._M = copy(seed._BC[:n+m,:n]) + self._M.set_immutable() + + if seed._use_c_vec: + seed._C = seed._BC[n+m:2*n+m,:n+m] + + if seed._track_mut: + # delete involutive mutations + if len(seed._mut_path) == 0 or seed._mut_path[len(self._mut_path)-1] != k: + seed._mut_path.append(k) + else: + seed._mut_path.pop() + + # a mutation invalidates the cluster although it can be recomputed by F-polys and g-vectors + # moving this into the for loop in case it does some mutations in 'seq' before finding a ValueError + seed._cluster = None + + seed._quiver = None + if not inplace: return seed + def cluster_index(self, cluster_str): + r""" + Returns the index of a cluster if use_fpolys is on + + INPUT: + + - ``cluster_str`` -- The string to look for in the cluster + + OUTPUT: + + Returns an integer or None if the string is not a cluster variable + + EXAMPLES:: + + sage: S = ClusterSeed(['A',4],user_labels=['x','y','z','w']); S.mutate('x') + sage: S.cluster_index('x') + sage: S.cluster_index('(y+1)/x') + 0 + + """ + if self._use_fpolys and isinstance(cluster_str, str): + c = FractionField(self._R)(cluster_str) + cluster_str = ClusterVariable( FractionField(self._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=self._n ) + if cluster_str in self.cluster(): + return self.cluster().index(cluster_str) + + return None + def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_output='seed'): r""" Returns the seeds obtained by mutating ``self`` at all vertices in ``sequence``. @@ -869,6 +2463,7 @@ def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_o sage: S.mutation_sequence([0,1,0,1],return_output='var') [(x1 + 1)/x0, (x0 + x1 + 1)/(x0*x1), (x0 + 1)/x1, x0] """ + seed = ClusterSeed( self ) new_clust_var = [] @@ -876,11 +2471,11 @@ def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_o for v in sequence: seed = seed.mutate(v,inplace=False) - new_clust_var.append( seed._cluster[v]) + new_clust_var.append( seed.cluster()[v]) seed_sequence.append( seed ) if show_sequence: - self.quiver().mutation_sequence(sequence=sequence, show_sequence=True, fig_size=fig_size ) + self.quiver().mutation_sequence2(sequence=sequence, show_sequence=True, fig_size=fig_size ) if return_output=='seed': return seed_sequence @@ -891,6 +2486,248 @@ def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_o else: raise ValueError('The parameter `return_output` can only be `seed`, `matrix`, or `var`.') + def mutation_analysis(self, options=['all'], filter=None): + r""" + Runs an analysis of all potential mutation options. Note that this might take a long time on large seeds. + + Notes: Edges are only returned if we have a non-valued quiver. Green and red vertices are only returned if the cluster is principal. + + INPUT: + + - ``options`` -- (default: ['all']) a list of mutation options. + - ``filter`` -- (default: None) A vertex or interval of vertices to limit our search to + + Possible options are: + + - ``"all"`` - All options below + - ``"edges"`` - Number of edges (works with skew-symmetric quivers) + - ``"edge_diff"`` - Edges added/deleted (works with skew-symmetric quivers) + - ``"green_vertices"`` - List of green vertices (works with principals) + - ``"green_vertices_diff"`` - Green vertices added/removed (works with principals) + - ``"red_vertices"`` - List of red vertices (works with principals) + - ``"red_vertices_diff"`` - Red vertices added/removed (works with principals) + - ``"urban_renewals"`` - List of urban renewal vertices + - ``"urban_renewals_diff"`` - Urban renewal vertices added/removed + - ``"sources"`` - List of source vertices + - ``"sources_diff"`` - Source vertices added/removed + - ``"sinks"`` - List of sink vertices + - ``"sinks_diff"`` - Sink vertices added/removed + - ``"denominators"`` - List of all denominators of the cluster variables + + OUTPUT: + + Outputs a dictionary indexed by the vertex numbers. Each vertex will itself also be a + dictionary with each desired option included as a key in the dictionary. As an example + you would get something similar to: {0: {'edges': 1}, 1: {'edges': 2}}. This represents + that if you were to do a mutation at the current seed then mutating at vertex 0 would result + in a quiver with 1 edge and mutating at vertex 0 would result in a quiver with 2 edges. + + EXAMPLES:: + + sage: B = [[0, 4, 0, -1],[-4,0, 3, 0],[0, -3, 0, 1],[1, 0, -1, 0]] + sage: S = ClusterSeed(matrix(B)); S.mutate([2,3,1,2,1,3,0,2]) + sage: S.mutation_analysis() + {0: {'d_matrix': [ 0 0 1 0] + [ 0 -1 0 0] + [ 0 0 0 -1] + [-1 0 0 0], + 'denominators': [1, 1, x0, 1], + 'edge_diff': 6, + 'edges': 13, + 'green_vertices': [0, 1, 3], + 'green_vertices_diff': {'added': [0], 'removed': []}, + 'red_vertices': [2], + 'red_vertices_diff': {'added': [], 'removed': [0]}, + 'sinks': [], + 'sinks_diff': {'added': [], 'removed': [2]}, + 'sources': [], + 'sources_diff': {'added': [], 'removed': []}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}, + 1: {'d_matrix': [ 1 4 1 0] + [ 0 1 0 0] + [ 0 0 0 -1] + [ 1 4 0 0], + 'denominators': [x0*x3, x0^4*x1*x3^4, x0, 1], + 'edge_diff': 2, + 'edges': 9, + 'green_vertices': [0, 3], + 'green_vertices_diff': {'added': [0], 'removed': [1]}, + 'red_vertices': [1, 2], + 'red_vertices_diff': {'added': [1], 'removed': [0]}, + 'sinks': [2], + 'sinks_diff': {'added': [], 'removed': []}, + 'sources': [], + 'sources_diff': {'added': [], 'removed': []}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}, + 2: {'d_matrix': [ 1 0 0 0] + [ 0 -1 0 0] + [ 0 0 0 -1] + [ 1 0 1 0], + 'denominators': [x0*x3, 1, x3, 1], + 'edge_diff': 0, + 'edges': 7, + 'green_vertices': [1, 2, 3], + 'green_vertices_diff': {'added': [2], 'removed': []}, + 'red_vertices': [0], + 'red_vertices_diff': {'added': [], 'removed': [2]}, + 'sinks': [], + 'sinks_diff': {'added': [], 'removed': [2]}, + 'sources': [2], + 'sources_diff': {'added': [2], 'removed': []}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}, + 3: {'d_matrix': [ 1 0 1 1] + [ 0 -1 0 0] + [ 0 0 0 1] + [ 1 0 0 1], + 'denominators': [x0*x3, 1, x0, x0*x2*x3], + 'edge_diff': -1, + 'edges': 6, + 'green_vertices': [1], + 'green_vertices_diff': {'added': [], 'removed': [3]}, + 'red_vertices': [0, 2, 3], + 'red_vertices_diff': {'added': [3], 'removed': []}, + 'sinks': [2], + 'sinks_diff': {'added': [], 'removed': []}, + 'sources': [1], + 'sources_diff': {'added': [1], 'removed': []}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}} + + sage: S = ClusterSeed(['A',3]).principal_extension() + sage: S.mutation_analysis() + {0: {'d_matrix': [ 1 0 0] + [ 0 -1 0] + [ 0 0 -1], + 'denominators': [x0, 1, 1], + 'green_vertices': [1, 2], + 'green_vertices_diff': {'added': [], 'removed': [0]}, + 'red_vertices': [0], + 'red_vertices_diff': {'added': [0], 'removed': []}, + 'sinks': [], + 'sinks_diff': {'added': [], 'removed': [1]}, + 'sources': [4, 5], + 'sources_diff': {'added': [], 'removed': [3]}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}, + 1: {'d_matrix': [-1 0 0] + [ 0 1 0] + [ 0 0 -1], + 'denominators': [1, x1, 1], + 'green_vertices': [0, 2], + 'green_vertices_diff': {'added': [], 'removed': [1]}, + 'red_vertices': [1], + 'red_vertices_diff': {'added': [1], 'removed': []}, + 'sinks': [0, 2, 4], + 'sinks_diff': {'added': [0, 2, 4], 'removed': [1]}, + 'sources': [1, 3, 5], + 'sources_diff': {'added': [1], 'removed': [4]}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}, + 2: {'d_matrix': [-1 0 0] + [ 0 -1 0] + [ 0 0 1], + 'denominators': [1, 1, x2], + 'green_vertices': [0, 1], + 'green_vertices_diff': {'added': [], 'removed': [2]}, + 'red_vertices': [2], + 'red_vertices_diff': {'added': [2], 'removed': []}, + 'sinks': [], + 'sinks_diff': {'added': [], 'removed': [1]}, + 'sources': [3, 4], + 'sources_diff': {'added': [], 'removed': [5]}, + 'urban_renewals': [], + 'urban_renewals_diff': {'added': [], 'removed': []}}} + + """ + + V = xrange(self._n) + + if filter is None: + filter = V + if filter in V: + filter = [filter] + + # setup our initial information for differences later on + if 'edge_diff' in options or ('all' in options and self._M.is_skew_symmetric()): + initial_edges = self.quiver().number_of_edges() + if 'green_vertices_diff' in options or ('all' in options and self._use_c_vec): + initial_green_vertices = self.green_vertices() + if 'red_vertices_diff' in options or ('all' in options and self._use_c_vec): + initial_red_vertices = self.red_vertices() + if 'urban_renewals_diff' in options or 'all' in options: + initial_urban_renewals= self.urban_renewals() + if 'sources_diff' in options or 'all' in options: + initial_sources = self.quiver().sources() + if 'sinks_diff' in options or 'all' in options: + initial_sinks = self.quiver().sinks() + + #instantiate our dictionary + analysis = {} + for i in filter: + #instantiate our dictionary + analysis[i] = {} + + #run mutations not in place as we just want an analysis + current_mutation = self.mutate(i,inplace=False) + + if ('edges' in options or 'all' in options) and self._M.is_skew_symmetric(): + analysis[i]['edges'] = current_mutation.quiver().number_of_edges() + if ('edge_diff' in options or 'all' in options) and self._M.is_skew_symmetric(): + analysis[i]['edge_diff'] = current_mutation.quiver().number_of_edges() - initial_edges + + if ('green_vertices' in options or 'all' in options) and self._use_c_vec: + analysis[i]['green_vertices'] = current_mutation.green_vertices() + if ('green_vertices_diff' in options or 'all' in options) and self._use_c_vec: + analysis[i]['green_vertices_diff'] = {} + new_green_vertices = current_mutation.green_vertices() + analysis[i]['green_vertices_diff']['added'] = list(set(new_green_vertices) - set(initial_green_vertices)) + analysis[i]['green_vertices_diff']['removed'] = list(set(initial_green_vertices) - set(new_green_vertices)) + + if ('red_vertices' in options or 'all' in options) and self._use_c_vec: + analysis[i]['red_vertices'] = current_mutation.red_vertices() + if ('red_vertices_diff' in options or 'all' in options) and self._use_c_vec: + analysis[i]['red_vertices_diff'] = {} + new_red_vertices = current_mutation.red_vertices() + analysis[i]['red_vertices_diff']['added'] = list(set(new_red_vertices) - set(initial_red_vertices)) + analysis[i]['red_vertices_diff']['removed'] = list(set(initial_red_vertices) - set(new_red_vertices)) + + if 'urban_renewals' in options or 'all' in options: + analysis[i]['urban_renewals'] = current_mutation.urban_renewals() + if 'urban_renewals_diff' in options or 'all' in options: + analysis[i]['urban_renewals_diff'] = {} + new_urban_renewals = current_mutation.urban_renewals() + analysis[i]['urban_renewals_diff']['added'] = list(set(new_urban_renewals) - set(initial_urban_renewals)) + analysis[i]['urban_renewals_diff']['removed'] = list(set(initial_urban_renewals) - set(new_urban_renewals)) + + if 'sources' in options or 'all' in options: + analysis[i]['sources'] = current_mutation.quiver().sources() + if 'sources_diff' in options or 'all' in options: + analysis[i]['sources_diff'] = {} + new_sources = current_mutation.quiver().sources() + analysis[i]['sources_diff']['added'] = list(set(new_sources) - set(initial_sources)) + analysis[i]['sources_diff']['removed'] = list(set(initial_sources) - set(new_sources)) + + if 'sinks' in options or 'all' in options: + analysis[i]['sinks'] = current_mutation.quiver().sinks() + if 'sinks_diff' in options or 'all' in options: + analysis[i]['sinks_diff'] = {} + new_sinks = current_mutation.quiver().sinks() + analysis[i]['sinks_diff']['added'] = list(set(new_sinks) - set(initial_sinks)) + analysis[i]['sinks_diff']['removed'] = list(set(initial_sinks) - set(new_sinks)) + + if ('denominators' in options or 'all' in options) and self._use_fpolys: + analysis[i]['denominators'] = [] + for vari in current_mutation.cluster(): + analysis[i]['denominators'].append(vari.denominator()) + + if ('d_matrix' in options or 'all' in options) and (self._use_d_vec or self._use_fpolys): + analysis[i]['d_matrix'] = current_mutation.d_matrix() + + return analysis + def exchangeable_part(self): r""" Returns the restriction to the principal part (i.e. the exchangeable variables) of ``self``. @@ -905,15 +2742,19 @@ def exchangeable_part(self): sage: T.exchangeable_part().quiver().digraph().edges() [(0, 1, (1, -1)), (2, 1, (1, -1))] - sage: S2 = S.principal_extension() - sage: S3 = S2.principal_extension(ignore_coefficients=True) - sage: S2.exchangeable_part() == S3.exchangeable_part() - True """ from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part eval_dict = dict( [ ( self.y(i), 1 ) for i in xrange(self._m) ] ) - seed = ClusterSeed( _principal_part( self._M ) ) - seed._cluster = [ self._cluster[k].subs(eval_dict) for k in xrange(self._n) ] + + seed = ClusterSeed( _principal_part( self._M ), is_principal = True, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed.use_c_vectors(self._use_c_vec) + seed.use_fpolys(self._use_fpolys) + seed.use_g_vectors(self._use_g_vec) + seed.use_d_vectors(self._use_d_vec) + seed.track_mutations(self._track_mut) + if self._use_fpolys: + self.cluster() + seed._cluster = [ self._cluster[k].subs(eval_dict) for k in xrange(self._n) ] seed._mutation_type = self._mutation_type return seed @@ -988,7 +2829,8 @@ def universal_extension(self): A = 2 - self.b_matrix().apply_map(abs).transpose() - rs = CartanMatrix(A).root_space() + # We give the indexing set of the Cartan matrix to be [1, 2, ..., n] + rs = CartanMatrix(A, index_set=range(1,A.ncols()+1)).root_space() almost_positive_coroots = rs.almost_positive_roots() sign = [-1 if all(x <= 0 for x in self.b_matrix()[i]) else 1 @@ -997,14 +2839,19 @@ def universal_extension(self): for alpha in almost_positive_coroots]) M = self._M.stack(C) - seed = ClusterSeed(M, is_principal=False) + seed = ClusterSeed(M, is_principal = False, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed.use_c_vectors(self._use_c_vec) + seed.use_fpolys(self._use_fpolys) + seed.use_g_vectors(self._use_g_vec) + seed.use_d_vectors(self._use_d_vec) + seed.track_mutations(self._track_mut) + seed._mutation_type = self._mutation_type return seed - def principal_extension(self,ignore_coefficients=False): + def principal_extension(self): r""" - Returns the principal extension of self, yielding a 2n-by-n matrix. Raises an error if the input seed has a non-square exchange matrix, - unless 'ignore_coefficients=True' is set. In this case, the method instead adds n frozen variables to any previously frozen variables. + Returns the principal extension of self, yielding a 2n-by-n matrix. Raises an error if the input seed has a non-square exchange matrix. In this case, the method instead adds n frozen variables to any previously frozen variables. I.e., the seed obtained by adding a frozen variable to every exchangeable variable of ``self``. EXAMPLES:: @@ -1026,35 +2873,38 @@ def principal_extension(self,ignore_coefficients=False): [ 0 0 1 0 0] [ 0 0 0 1 0] [ 0 0 0 0 1] - - sage: T2 = T.principal_extension() - Traceback (most recent call last): - ... - ValueError: The b-matrix is not square. Use ignore_coefficients to ignore this. - - sage: T2 = T.principal_extension(ignore_coefficients=True); T2.b_matrix() - [ 0 1 0 0 0] - [-1 0 1 0 0] - [ 0 -1 0 1 1] - [ 0 0 -1 0 0] - [ 0 0 -1 0 0] - [ 1 0 0 0 0] - [ 0 1 0 0 0] - [ 0 0 1 0 0] - [ 0 0 0 1 0] - [ 0 0 0 0 1] - [ 1 0 0 0 0] - [ 0 1 0 0 0] - [ 0 0 1 0 0] - [ 0 0 0 1 0] - [ 0 0 0 0 1] + + sage: S = ClusterSeed(['A',4],user_labels=['a','b','c','d']) + sage: T= S.principal_extension() + sage: T.cluster() + [a, b, c, d] + sage: T.coefficients() + [y0, y1, y2, y3] + sage: S2 = ClusterSeed(['A',4],user_labels={0:'a',1:'b',2:'c',3:'d'}) + sage: S2 == S + True + sage: T2 = S2.principal_extension() + sage: T2 == T + True """ from sage.matrix.all import identity_matrix - if not ignore_coefficients and self._m != 0: - raise ValueError("The b-matrix is not square. Use ignore_coefficients to ignore this.") + if self._m != 0: + raise ValueError("The b-matrix is not square.") M = self._M.stack(identity_matrix(self._n)) is_principal = (self._m == 0) - seed = ClusterSeed( M, is_principal=is_principal ) + if self._user_labels: + if isinstance(self._user_labels,list): + self._user_labels = self._user_labels + ['y%s'%i for i in xrange(self._n)] + elif isinstance(self._user_labels,dict): + self._user_labels.update( {(i+self._n):'y%s'%i for i in xrange(self._n)} ) + seed = ClusterSeed(M, is_principal = is_principal, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed.use_c_vectors(self._use_c_vec) + seed.use_fpolys(self._use_fpolys) + seed.use_g_vectors(self._use_g_vec) + seed.use_d_vectors(self._use_d_vec) + seed.track_mutations(self._track_mut) + + #### This should fix principal_extension resetting boolean flags. Might need to update user labels to include new principals with y's. -G seed._mutation_type = self._mutation_type return seed @@ -1097,10 +2947,12 @@ def reorient( self, data ): self.reset_cluster() self._mutation_type = None - def set_cluster( self, cluster ): + def set_cluster( self, cluster, force=False ): r""" Sets the cluster for ``self`` to ``cluster``. + Warning: Initialization may lead to inconsistent data. + INPUT: - ``cluster`` -- an iterable defining a cluster for ``self``. @@ -1112,17 +2964,35 @@ def set_cluster( self, cluster ): sage: S.mutate([1,2,1]) sage: S.cluster() [x0, (x1 + 1)/x2, (x0*x2 + x1 + 1)/(x1*x2)] + sage: cluster2 = S.cluster() sage: S.set_cluster(cluster) + Warning: using set_cluster at this point could lead to inconsistent seed data. + + sage: S.set_cluster(cluster, force=True) sage: S.cluster() [x0, x1, x2] + sage: S.set_cluster(cluster2, force=True) + sage: S.cluster() + [x0, (x1 + 1)/x2, (x0*x2 + x1 + 1)/(x1*x2)] + + sage: S = ClusterSeed(['A',3]); S.use_fpolys(False) + sage: S.set_cluster([1,1,1]) + Warning: clusters not being tracked so this command is ignored. """ - if not len(cluster) == self._n+self._m: + + if len(cluster) < self._n+self._m: raise ValueError('The number of given cluster variables is wrong') - if any(c not in self._R for c in cluster): - raise ValueError('The cluster variables are not all contained in %s'%self._R) - self._cluster = [ self._R(x) for x in cluster ] - self._is_principal = None + if self._use_fpolys: + if any(c not in FractionField(self._R) for c in cluster): + raise ValueError('The cluster variables are not all contained in %s'%FractionField(self._R)) + if not force: # if already have f_polynomials, using set_cluster might yield data inconsistent with them. + print("Warning: using set_cluster at this point could lead to inconsistent seed data.") + else: + self._cluster = [ FractionField(self._R)(x) for x in cluster ][0:self._n] + self._is_principal = None + else: + print("Warning: clusters not being tracked so this command is ignored.") def reset_cluster( self ): r""" @@ -1149,14 +3019,36 @@ def reset_cluster( self ): sage: T.reset_cluster() sage: T.cluster() [x0, x1, x2] + + sage: S = ClusterSeed(['B',3],user_labels=[[1,2],[2,3],[3,4]],user_labels_prefix='p') + sage: S.mutate([0,1]) + sage: S.cluster() + [(p_2_3 + 1)/p_1_2, (p_1_2*p_3_4^2 + p_2_3 + 1)/(p_1_2*p_2_3), p_3_4] + + sage: S.reset_cluster() + sage: S.cluster() + [p_1_2, p_2_3, p_3_4] + sage: S.g_matrix() + [1 0 0] + [0 1 0] + [0 0 1] + sage: S.f_polynomials() + [1, 1, 1] """ - self.set_cluster(self._R.gens()) - + if self._use_g_vec: + self._G = matrix.identity(self._n) + if self._use_fpolys: + self._F = dict([(i,self._U(1)) for i in self._init_exch.values()]) + if self._use_fpolys: + self.set_cluster(self._R.gens(), force=True) + def reset_coefficients( self ): r""" Resets the coefficients of ``self`` to the frozen variables but keeps the current cluster. Raises an error if the number of frozen variables is different than the number of exchangeable variables. + WARNING: This command to be phased out since 'use_c_vectors() does this more effectively. + EXAMPLES:: sage: S = ClusterSeed(['A',3]).principal_extension() @@ -1184,15 +3076,19 @@ def reset_coefficients( self ): [ 0 1 0] [ 0 0 1] """ - n,m = self._n, self._m + n, m = self._n, self._m if not n == m: - raise ValueError("The numbers of cluster variables and of frozen variables do not coincide.") + raise ValueError("The numbers of cluster variables " + "and of frozen variables do not coincide.") + newM = copy(self._M) for i in xrange(m): for j in xrange(n): if i == j: - self._M[i+n,j] = 1 + newM[i + n, j] = 1 else: - self._M[i+n,j] = 0 + newM[i + n, j] = 0 + self._M = newM + self._M.set_immutable() self._quiver = None self._is_principal = None @@ -1316,20 +3212,36 @@ def mutation_class_iter( self, depth=infinity, show_depth=False, return_paths=Fa (A seed for a cluster algebra of rank 2 of type ['A', [1, 1], 1], [1, 0, 1]) (A seed for a cluster algebra of rank 2 of type ['A', [1, 1], 1], [0, 1, 0]) """ + + # Variable to track the depth depth_counter = 0 n = self._n timer = time.time() + + #print self.cluster() + + # set up our initial cluster and grab variables + if up_to_equivalence: + cl = Set( self.cluster() ) + else: + cl = tuple( self.cluster() ) + + # If we are tracking return paths if return_paths: yield (self,[]) else: yield self - if up_to_equivalence: - cl = Set( self._cluster ) - else: - cl = tuple( self._cluster ) + + + # instantiate the variables clusters = {} clusters[ cl ] = [ self, range(n), [] ] + #print clusters + + # we get bigger the first time gets_bigger = True + + # If we are showing depth, show some statistics if show_depth: timer2 = time.time() dc = str(depth_counter) @@ -1337,19 +3249,34 @@ def mutation_class_iter( self, depth=infinity, show_depth=False, return_paths=Fa nr = str(len(clusters)) nr += ' ' * (10-len(nr)) print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer) + + # Each time we get bigger and we haven't hit the full depth while gets_bigger and depth_counter < depth: gets_bigger = False + + # set the keys keys = clusters.keys() + + # Our keys are cluster variables, so for each cluster: for key in keys: + # sd is the cluster data sd = clusters[key] + + # another way to do a for loop for each item while sd[1]: i = sd[1].pop() + #print i + + # If we aren't only sinking the source if not only_sink_source or all( entry >= 0 for entry in sd[0]._M.row( i ) ) or all( entry <= 0 for entry in sd[0]._M.row( i ) ): + # do an inplace mutation on our cluster (sd[0]) sd2 = sd[0].mutate( i, inplace=False ) + + # set up our new cluster variables if up_to_equivalence: - cl2 = Set(sd2._cluster) + cl2 = Set(sd2.cluster()) else: - cl2 = tuple(sd2._cluster) + cl2 = tuple(sd2.cluster()) if cl2 in clusters: if not up_to_equivalence and i in clusters[cl2][1]: clusters[cl2][1].remove(i) @@ -1779,38 +3706,38 @@ def variable_class_iter(self, depth=infinity, ignore_bipartite_belt=False): depth_counter = 0 end = False seed2 = ClusterSeed(seed) - for c in seed._cluster: + for c in seed.cluster(): if c not in var_class: - yield ClusterVariable( c.parent(), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) - var_class = var_class.union( seed._cluster ) + yield ClusterVariable( FractionField(seed._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=seed._n ) + var_class = var_class.union( seed.cluster()) - init_cluster = set(seed._cluster) + init_cluster = set(seed.cluster()) while not end and depth_counter < depth: depth_counter += 1 seed.mutate(bipartition[0]) seed.mutate(bipartition[1]) - if set(seed._cluster) in [set(seed2._cluster),init_cluster]: + if set(seed.cluster()) in [set(seed2.cluster()),init_cluster]: end = True if not end: - for c in seed._cluster: + for c in seed.cluster(): if c not in var_class: - yield ClusterVariable( c.parent(), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) - var_class = var_class.union( seed._cluster ) + yield ClusterVariable( FractionField(seed._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=seed._n ) + var_class = var_class.union( seed.cluster() ) seed2.mutate(bipartition[1]) seed2.mutate(bipartition[0]) - if set(seed2._cluster) in [set(seed._cluster),init_cluster]: + if set(seed2.cluster()) in [set(seed.cluster()),init_cluster]: end = True if not end: - for c in seed2._cluster: + for c in seed2.cluster(): if c not in var_class: - yield ClusterVariable( c.parent(), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) - var_class = var_class.union(seed2._cluster) + yield ClusterVariable(FractionField(seed._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=seed._n ) + var_class = var_class.union(seed2.cluster()) return else: - for c in seed._cluster: + for c in seed.cluster(): if c not in var_class: - yield ClusterVariable( c.parent(), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable' ) - var_class = var_class.union(seed._cluster) + yield ClusterVariable( FractionField(seed._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=seed._n) + var_class = var_class.union(seed.cluster()) def variable_class(self, depth=infinity, ignore_bipartite_belt=False): r""" @@ -1821,7 +3748,6 @@ def variable_class(self, depth=infinity, ignore_bipartite_belt=False): - ``depth`` -- (default:infinity) integer, only seeds with distance at most depth from self are returned - ``ignore_bipartite_belt`` -- (default:False) if True, the algorithms does not use the bipartite belt - EXAMPLES: - for examples see :meth:`variable_class_iter` @@ -1836,7 +3762,7 @@ def variable_class(self, depth=infinity, ignore_bipartite_belt=False): var_iter = self.variable_class_iter( depth=depth, ignore_bipartite_belt=ignore_bipartite_belt ) return sorted(var_iter) - def is_finite( self ): + def is_finite(self): r""" Returns True if ``self`` is of finite type. @@ -1904,7 +3830,7 @@ def mutation_type(self): - All affine types can be detected, EXCEPT affine type D (the algorithm is not yet implemented) - All exceptional types can be detected. - - Might fail to work if it is used within different Sage processes simultaneously (that happend in the doctesting). + - Might fail to work if it is used within different Sage processes simultaneously (that happened in the doctesting). EXAMPLES: @@ -2034,6 +3960,70 @@ def greedy(self, a1, a2, method='by_recursion'): raise ValueError("Greedy elements are only currently " "defined for cluster seeds of rank two.") + def oriented_exchange_graph(self): + """ + Return the oriented exchange graph of ``self`` as a directed + graph. + + The seed must be a cluster seed for a cluster algebra of + finite type with principal coefficients (the corresponding + quiver must have mutable vertices 0,1,...,n-1). + + EXAMPLES:: + + sage: S = ClusterSeed(['A', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 5 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 0] + + sage: S = ClusterSeed(['B', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 6 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 1, 0] + + TESTS:: + + sage: S = ClusterSeed(['A',[2,2],1]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for finite mutation type + + sage: S = ClusterSeed(['A', 2]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for principal coefficients + """ + if not self._mutation_type.is_finite(): + raise TypeError('only works for finite mutation type') + + if not self._is_principal: + raise TypeError('only works for principal coefficients') + + covers = [] + n = self.n() + stack = [self] + known_clusters = [] + while stack: + i = stack.pop() + Vari = tuple(sorted(i.cluster())) + B = i.b_matrix() + for k in range(n): + # check if green + if all(B[i2][k] >= 0 for i2 in range(n, 2 * n)): + j = i.mutate(k, inplace=False) + Varj = tuple(sorted(j.cluster())) + covers.append((Vari, Varj)) + if not(Varj in known_clusters): + known_clusters += [Varj] + stack.append(j) + + return DiGraph(covers) + + def _bino(n, k): """ Binomial coefficient which we define as zero for negative n. @@ -2196,6 +4186,50 @@ def is_LeeLiZel_allowable(T,n,m,b,c): return False return True +def get_green_vertices(C): + r""" + Get the green vertices from a matrix. Will go through each clumn and return + the ones where no entry is greater than 0. + + INPUT: + + - ``C`` -- The C matrix to check + + EXAMPLES:: + + sage: from sage.combinat.cluster_algebra_quiver.cluster_seed import get_green_vertices + sage: S = ClusterSeed(['A',4]); S.mutate([1,2,3,2,0,1,2,0,3]) + sage: get_green_vertices(S.c_matrix()) + [0, 3] + + """ + return [ i for (i,v) in enumerate(C.columns()) if any(x > 0 for x in v) ] + ## old code commented out + #import numpy as np + #max_entries = [ np.max(np.array(C.column(i))) for i in xrange(C.ncols()) ] + #return [i for i in xrange(C.ncols()) if max_entries[i] > 0] + +def get_red_vertices(C): + r""" + Get the red vertices from a matrix. Will go through each clumn and return + the ones where no entry is less than 0. + + INPUT: + + - ``C`` -- The C matrix to check + + EXAMPLES:: + sage: from sage.combinat.cluster_algebra_quiver.cluster_seed import get_red_vertices + sage: S = ClusterSeed(['A',4]); S.mutate([1,2,3,2,0,1,2,0,3]) + sage: get_red_vertices(S.c_matrix()) + [1, 2] + + """ + return [ i for (i,v) in enumerate(C.columns()) if any(x < 0 for x in v) ] + ## old code commented out + #import numpy as np + #min_entries = [ np.min(np.array(C.column(i))) for i in xrange(C.ncols()) ] + #return [i for i in xrange(C.ncols()) if min_entries[i] < 0] class ClusterVariable(FractionFieldElement): r""" @@ -2217,7 +4251,7 @@ class ClusterVariable(FractionFieldElement): (x0*x2 + 1)/x1 alpha[2] (x0*x2 + x1 + 1)/(x1*x2) alpha[2] + alpha[3] """ - def __init__( self, parent, numerator, denominator, coerce=True, reduce=True, mutation_type=None, variable_type=None ): + def __init__( self, parent, numerator, denominator, coerce=True, reduce=True, mutation_type=None, variable_type=None, xdim=0 ): r""" Initializes a cluster variable in the same way that elements in the field of rational functions are initialized. @@ -2235,6 +4269,7 @@ def __init__( self, parent, numerator, denominator, coerce=True, reduce=True, mu [(x0 + x1 + 1)/(x0*x1), (x1 + 1)/x0, (x0 + 1)/x1, x1, x0] """ FractionFieldElement.__init__( self, parent, numerator, denominator, coerce=coerce, reduce=reduce ) + self._n = xdim; self._mutation_type = mutation_type self._variable_type = variable_type @@ -2271,10 +4306,11 @@ def almost_positive_root( self ): # where A is a single letter and 15 is an integer Phi = RootSystem([mt[2: 3], ZZ(mt[6: -1])]) Phiplus = Phi.root_lattice().simple_roots() + if self.denominator() == 1: return -Phiplus[ self.numerator().degrees().index(1) + 1 ] else: root = self.denominator().degrees() - return sum( [ root[i]*Phiplus[ i+1 ] for i in range(len(root)) ] ) + return sum( [ root[i]*Phiplus[ i+1 ] for i in range(self._n) ] ) else: raise ValueError('The cluster algebra for %s is not of finite type.'%self._repr_()) diff --git a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py index 4305094f4e1..a1a008c9b1e 100644 --- a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py @@ -789,7 +789,9 @@ def _connected_mutation_type_AAtildeD(dg, ret_conn_vert=False): INPUT: - - ``ret_conn_vert`` (boolean; default:``False``). If ``True, returns 'connecting vertices', technical information that is used in the algorithm. + - ``ret_conn_vert`` -- boolean (default: ``False``). If ``True``, + returns 'connecting vertices', technical information that is + used in the algorithm. A brief description of the algorithm:: diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index 22416a4cec6..3df226d2e61 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -1,12 +1,27 @@ r""" Quiver -A *quiver* is an oriented graphs without loops, two-cycles, or multiple edges. The edges are labelled by pairs `(i,-j)` -such that the matrix `M = (m_{ab})` with `m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices `a` and `b` is skew-symmetrizable. +A *quiver* is an oriented graph without loops, two-cycles, or multiple +edges. The edges are labelled by pairs `(i,-j)` (with `i` and `j` being +positive integers) such that the matrix `M = (m_{ab})` with +`m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices +`a` and `b` is skew-symmetrizable. -For the compendium on the cluster algebra and quiver package see +.. WARNING: - http://arxiv.org/abs/1102.4844. + This is not the standard definition of a quiver. Normally, in + cluster algebra theory, a quiver is defined as an oriented graph + without loops and two-cycles but with multiple edges allowed; the + edges are unlabelled. This notion of quivers, however, can be seen + as a particular case of our notion of quivers. Namely, if we have + a quiver (in the regular sense of this word) with (precisely) + `i` edges from `a` to `b`, then we represent it by a quiver + (in our sense of this word) with an edge from `a` to `b` labelled + by the pair `(i,-i)`. + +For the compendium on the cluster algebra and quiver package see :: + + http://arxiv.org/abs/1102.4844. AUTHORS: @@ -24,7 +39,6 @@ #***************************************************************************** from sage.structure.sage_object import SageObject from copy import copy -from sage.structure.unique_representation import UniqueRepresentation from sage.misc.all import cached_method from sage.rings.all import ZZ, CC, infinity from sage.graphs.all import Graph, DiGraph @@ -32,7 +46,6 @@ from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part, _digraph_mutate, _matrix_to_digraph, _dg_canonical_form, _mutation_class_iter, _digraph_to_dig6, _dig6_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type, _mutation_type_from_data, is_mutation_finite -from sage.groups.perm_gps.permgroup import PermutationGroup class ClusterQuiver(SageObject): """ @@ -181,7 +194,7 @@ def __init__( self, data, frozen=None ): sage: Q = ClusterQuiver(['A',4]) sage: TestSuite(Q).run() """ - from cluster_seed import ClusterSeed + from sage.combinat.cluster_algebra_quiver.cluster_seed import ClusterSeed from sage.matrix.matrix import Matrix # constructs a quiver from a mutation type @@ -252,9 +265,11 @@ def __init__( self, data, frozen=None ): print 'The input data is a quiver, therefore the additional parameter frozen is ignored.' self._M = copy(data._M) + self._M.set_immutable() self._n = data._n self._m = data._m self._digraph = copy( data._digraph ) + self._vertex_dictionary = {} self._mutation_type = data._mutation_type self._description = data._description @@ -266,9 +281,11 @@ def __init__( self, data, frozen=None ): print 'The input data is a matrix, therefore the additional parameter frozen is ignored.' self._M = copy(data).sparse_matrix() + self._M.set_immutable() self._n = n = self._M.ncols() self._m = m = self._M.nrows() - self._n self._digraph = _matrix_to_digraph( self._M ) + self._vertex_dictionary = {} self._mutation_type = None if n+m == 0: self._description = 'Quiver without vertices' @@ -327,7 +344,9 @@ def __init__( self, data, frozen=None ): if not _principal_part(M).is_skew_symmetrizable( positive=True ): raise ValueError("The input digraph must be skew-symmetrizable") self._digraph = dg + self._vertex_dictionary = {} self._M = M + self._M.set_immutable() if n+m == 0: self._description = 'Quiver without vertices' elif n+m == 1: @@ -362,6 +381,18 @@ def __eq__(self, other): """ return isinstance(other, ClusterQuiver) and self._M == other._M + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]) + sage: hash(Q) # indirect doctest + 16 + """ + return self._M.__hash__() + def _repr_(self): """ Returns the description of ``self``. @@ -384,17 +415,25 @@ def _repr_(self): name += ' with %s frozen vertices'%self._m return name - def plot(self, circular=True, center=(0,0), directed=True, mark=None, save_pos=False): + def plot(self, circular=True, center=(0, 0), directed=True, mark=None, + save_pos=False, greens=[]): """ - Returns the plot of the underlying digraph of ``self``. + Return the plot of the underlying digraph of ``self``. INPUT: - - ``circular`` -- (default:True) if True, the circular plot is chosen, otherwise >>spring<< is used. - - ``center`` -- (default:(0,0)) sets the center of the circular plot, otherwise it is ignored. - - ``directed`` -- (default: True) if True, the directed version is shown, otherwise the undirected. - - ``mark`` -- (default: None) if set to i, the vertex i is highlighted. - - ``save_pos`` -- (default:False) if True, the positions of the vertices are saved. + - ``circular`` -- (default: ``True``) if ``True``, the circular plot + is chosen, otherwise >>spring<< is used. + - ``center`` -- (default:(0,0)) sets the center of the circular plot, + otherwise it is ignored. + - ``directed`` -- (default: ``True``) if ``True``, the directed + version is shown, otherwise the undirected. + - ``mark`` -- (default: ``None``) if set to i, the vertex i is + highlighted. + - ``save_pos`` -- (default: ``False``) if ``True``, the positions + of the vertices are saved. + - ``greens`` -- (default: []) if set to a list, will display the green + vertices as green EXAMPLES:: @@ -404,10 +443,10 @@ def plot(self, circular=True, center=(0,0), directed=True, mark=None, save_pos=F """ from sage.plot.colors import rainbow from sage.graphs.graph_generators import GraphGenerators - from sage.all import e,pi,I + from sage.all import e, pi, I graphs = GraphGenerators() # returns positions for graph vertices on two concentric cycles with radius 1 and 2 - def _graphs_concentric_circles(n,m): + def _graphs_concentric_circles(n, m): g1 = graphs.CycleGraph(n).get_pos() g2 = graphs.CycleGraph(m).get_pos() for i in g2: @@ -420,6 +459,7 @@ def _graphs_concentric_circles(n,m): n, m = self._n, self._m colors = rainbow(11) color_dict = { colors[0]:[], colors[1]:[], colors[6]:[], colors[5]:[] } + if directed: dg = DiGraph( self._digraph ) else: @@ -449,7 +489,23 @@ def _graphs_concentric_circles(n,m): else: raise ValueError("The given mark is not a vertex of self.") else: - partition = (range(n),range(n,n+m),[]) + nr = range(n) + mr = range(n,n+m) + for i in greens: + if i < n: + nr.remove(i) + else: + mr.remove(i) + partition = (nr,mr,greens) + + # fix labels + for i in xrange(2): + for p in list(enumerate(partition[i])): + key = p[0] + part = p[1] + if part in self._vertex_dictionary: + partition[0][key]= self._vertex_dictionary[part] + vertex_color_dict = {} vertex_color_dict[ colors[0] ] = partition[0] vertex_color_dict[ colors[6] ] = partition[1] @@ -460,6 +516,7 @@ def _graphs_concentric_circles(n,m): 'edge_colors': color_dict, 'vertex_colors': vertex_color_dict, 'edge_labels' : True, + 'vertex_labels': True, } if circular: pp = _graphs_concentric_circles( n, m ) @@ -468,17 +525,23 @@ def _graphs_concentric_circles(n,m): options[ 'pos' ] = pp return dg.plot( **options ) - def show(self, fig_size=1, circular=False, directed=True, mark=None, save_pos=False): + def show(self, fig_size=1, circular=False, directed=True, mark=None, save_pos=False, greens=[]): """ - Shows the plot of the underlying digraph of ``self``. + Show the plot of the underlying digraph of ``self``. INPUT: - - ``fig_size`` -- (default: 1) factor by which the size of the plot is multiplied. - - ``circular`` -- (default: False) if True, the circular plot is chosen, otherwise >>spring<< is used. - - ``directed`` -- (default: True) if True, the directed version is shown, otherwise the undirected. + - ``fig_size`` -- (default: 1) factor by which the size of the plot + is multiplied. + - ``circular`` -- (default: False) if True, the circular plot is + chosen, otherwise >>spring<< is used. + - ``directed`` -- (default: True) if True, the directed version is + shown, otherwise the undirected. - ``mark`` -- (default: None) if set to i, the vertex i is highlighted. - - ``save_pos`` -- (default:False) if True, the positions of the vertices are saved. + - ``save_pos`` -- (default:False) if True, the positions of the + vertices are saved. + - ``greens`` -- (default:[]) if set to a list, will display the green + vertices as green TESTS:: @@ -486,7 +549,7 @@ def show(self, fig_size=1, circular=False, directed=True, mark=None, save_pos=Fa sage: Q.show() # long time """ n, m = self._n, self._m - plot = self.plot( circular=circular, directed=directed, mark=mark, save_pos=save_pos ) + plot = self.plot( circular=circular, directed=directed, mark=mark, save_pos=save_pos, greens=greens) if circular: plot.show( figsize=[fig_size*3*(n+m)/4+1,fig_size*3*(n+m)/4+1] ) else: @@ -588,8 +651,7 @@ def qmu_save(self,filename=None): sage: S=ClusterSeed(['A',3]) sage: T1=S.principal_extension() - sage: T2=T1.principal_extension(ignore_coefficients=True) - sage: Q=T2.quiver() + sage: Q=T1.quiver() sage: Q.qmu_save(os.path.join(SAGE_TMP, 'sage.qmu')) """ M = self.b_matrix() @@ -668,7 +730,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def digraph(self): """ @@ -909,7 +971,8 @@ def m(self): def canonical_label( self, certify=False ): """ - Returns the canonical labelling of ``self``, see sage.graphs.graph.GenericGraph.canonical_label. + Returns the canonical labelling of ``self``, see + :meth:`sage.graphs.graph.GenericGraph.canonical_label`. INPUT: @@ -1056,13 +1119,88 @@ def principal_extension(self, inplace=False): else: return Q + + def first_sink(self): + r""" + Return the first vertex of ``self`` that is a sink + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]); + sage: Q.mutate([1,2,4,3,2]); + sage: Q.first_sink() + 0 + """ + sinks = self.digraph().sinks() + + if len(sinks) > 0: + return sinks[0] + return None + + + def sinks(self): + r""" + Return all vertices of ``self`` that are sinks + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]); + sage: Q.mutate([1,2,4,3,2]); + sage: Q.sinks() + [0, 2] + + sage: Q = ClusterQuiver(['A',5]) + sage: Q.mutate([2,1,3,4,2]) + sage: Q.sinks() + [3] + """ + return self.digraph().sinks() + + def first_source(self): + r""" + Return the first vertex of ``self`` that is a source + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]) + sage: Q.mutate([2,1,3,4,2]) + sage: Q.first_source() + 1 + """ + sources = self.digraph().sources() + + if len(sources) > 0: + return sources[0] + return None + + def sources(self): + r""" + Returns all vertices of ``self`` that are sources + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]); + sage: Q.mutate([1,2,4,3,2]); + sage: Q.sources() + [] + + sage: Q = ClusterQuiver(['A',5]) + sage: Q.mutate([2,1,3,4,2]) + sage: Q.sources() + [1] + """ + return self.digraph().sources() + def mutate(self, data, inplace=True): """ Mutates ``self`` at a sequence of vertices. INPUT: - - ``sequence`` -- a vertex of ``self`` or an iterator of vertices of ``self``. + - ``sequence`` -- a vertex of ``self``, an iterator of vertices of ``self``, + a function which takes in the ClusterQuiver and returns a vertex or an iterator of vertices, + or a string of the parameter wanting to be called on ClusterQuiver that will return a vertex or + an iterator of vertices. - ``inplace`` -- (default: True) if False, the result is returned, otherwise ``self`` is modified. EXAMPLES:: @@ -1103,6 +1241,21 @@ def mutate(self, data, inplace=True): sage: T = Q.mutate(0,inplace=False) sage: Q == T True + + sage: Q = ClusterQuiver(['A',3]); Q.b_matrix() + [ 0 1 0] + [-1 0 -1] + [ 0 1 0] + sage: Q.mutate('first_sink'); Q.b_matrix() + [ 0 -1 0] + [ 1 0 1] + [ 0 -1 0] + sage: Q.mutate('first_source'); Q.b_matrix() + [ 0 1 0] + [-1 0 -1] + [ 0 1 0] + + TESTS:: @@ -1116,11 +1269,25 @@ def mutate(self, data, inplace=True): ... ValueError: The second parameter must be boolean. To mutate at a sequence of length 2, input it as a list. """ + n = self._n m = self._m dg = self._digraph V = range(n) + # If we get a string, execute as a function + if isinstance(data, str): + data = getattr(self, data)() + + # If we get a function, execute it + if hasattr(data, '__call__'): + # function should return either integer or sequence + data = data(self) + + if data is None: + raise ValueError('Not mutating: No vertices given.') + + if data in V: seq = [data] else: @@ -1140,6 +1307,7 @@ def mutate(self, data, inplace=True): M = _edge_list_to_matrix( dg.edge_iterator(), n, m ) if inplace: self._M = M + self._M.set_immutable() self._digraph = dg else: Q = ClusterQuiver( M ) @@ -1250,6 +1418,7 @@ def reorient( self, data ): dg_new.add_edge( edge[1],edge[0],edge[2] ) self._digraph = dg_new self._M = _edge_list_to_matrix( dg_new.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None elif all( type(edge) in [list,tuple] and len(edge)==2 for edge in data ): edges = self._digraph.edges(labels=False) @@ -1259,6 +1428,7 @@ def reorient( self, data ): self._digraph.delete_edge(edge[1],edge[0]) self._digraph.add_edge(edge[0],edge[1],label) self._M = _edge_list_to_matrix( self._digraph.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None else: raise ValueError('The order is no total order on the vertices of the quiver or a list of edges to be oriented.') @@ -1586,3 +1756,154 @@ def is_mutation_finite( self, nr_of_checks=None, return_path=False ): return is_finite, path else: return is_finite + + def number_of_edges(self): + r""" + Return the total number of edges on the quiver + + Note: This only works with non-valued quivers. If used on a + non-valued quiver then the positive value is taken to be the number of edges added + + OUTPUT: + + Returns an integer of the number of edges + + EXAMPLES:: + + sage: S = ClusterQuiver(['A',4]); S.number_of_edges() + 3 + + sage: S = ClusterQuiver(['B',4]); S.number_of_edges() + 3 + + """ + + digraph_edges = self.digraph().edges() + + total_edges = 0 + for edge in digraph_edges: + total_edges += edge[2][0] + + return total_edges + + def relabel(self, relabelling, inplace=True): + r""" + Returns the quiver after doing a relabelling + + Will relabel the vertices of the quiver + + INPUT: + + - ``relabelling`` -- Dictionary of labels to move around + - ``inplace`` -- (default:True) if True, will return a duplicate of the quiver + + EXAMPLES:: + + sage: S = ClusterQuiver(['A',4]).relabel({1:'5',2:'go'}) + + """ + if inplace: + quiver = self + else: + quiver = ClusterQuiver(self) + quiver._digraph.relabel(relabelling) + quiver._vertex_dictionary = relabelling + return quiver + + def d_vector_fan(self): + r""" + Return the d-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + d-matrices of the clusters. + + This is a complete simplicial fan (and even smooth when the + initial quiver is acyclic). It only makes sense for quivers of + finite type. + + EXAMPLES:: + + sage: Fd = ClusterQuiver([[1,2]]).d_vector_fan(); Fd + Rational polyhedral fan in 2-d lattice N + sage: Fd.ngenerating_cones() + 5 + + sage: Fd = ClusterQuiver([[1,2],[2,3]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + True + + sage: Fd = ClusterQuiver([[1,2],[2,3],[3,1]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + False + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).d_vector_fan() + Traceback (most recent call last): + ... + ValueError: only makes sense for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only makes sense for quivers of finite type') + seed = ClusterSeed(self) + return Fan([Cone(s.d_matrix().columns()) + for s in seed.mutation_class()]) + + def g_vector_fan(self): + r""" + Return the g-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + g-matrices of the clusters. + + This is a complete simplicial fan. It is only supported for + quivers of finite type. + + EXAMPLES:: + + sage: Fg = ClusterQuiver([[1,2]]).g_vector_fan(); Fg + Rational polyhedral fan in 2-d lattice N + sage: Fg.ngenerating_cones() + 5 + + sage: Fg = ClusterQuiver([[1,2],[2,3]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + sage: Fg = ClusterQuiver([[1,2],[2,3],[3,1]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).g_vector_fan() + Traceback (most recent call last): + ... + ValueError: only supported for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only supported for quivers of finite type') + seed = ClusterSeed(self).principal_extension() + return Fan([Cone(s.g_matrix().columns()) + for s in seed.mutation_class()]) + diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py index b4daeb3f04e..649d7a9c93f 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py @@ -1088,8 +1088,7 @@ def properties(self): print '\t- elliptic: ', self.is_elliptic() -class QuiverMutationType_Irreducible(QuiverMutationType_abstract, - UniqueRepresentation, SageObject): +class QuiverMutationType_Irreducible(QuiverMutationType_abstract): """ The mutation type for a cluster algebra or a quiver. Should not be called directly, but through QuiverMutationType. @@ -1933,8 +1932,7 @@ def dual(self): return self -class QuiverMutationType_Reducible(QuiverMutationType_abstract, - UniqueRepresentation, SageObject): +class QuiverMutationType_Reducible(QuiverMutationType_abstract): """ The mutation type for a cluster algebra or a quiver. Should not be called directly, but through QuiverMutationType. Inherits from @@ -1947,7 +1945,7 @@ def __init__(self, *args): INPUT: - ``data`` -- a list each of whose entries is a - QuiverMutationType_Irreducible + QuiverMutationType_Irreducible EXAMPLES:: diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py new file mode 100644 index 00000000000..399d26b67ba --- /dev/null +++ b/src/sage/combinat/colored_permutations.py @@ -0,0 +1,1108 @@ +r""" +Colored Permutations + +.. TODO:: + + Much of the colored permutations (and element) class can be + generalized to `G \wr S_n` +""" +import itertools + +from sage.categories.groups import Groups +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.finite_coxeter_groups import FiniteCoxeterGroups +from sage.structure.element import MultiplicativeGroupElement +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod + +from sage.combinat.permutation import Permutations +from sage.matrix.constructor import diagonal_matrix +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.all import ZZ + + +class ColoredPermutation(MultiplicativeGroupElement): + """ + A colored permutation. + """ + def __init__(self, parent, colors, perm): + """ + Initialize ``self``. + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: TestSuite(s1*s2*t).run() + """ + self._colors = tuple(colors) + self._perm = perm + MultiplicativeGroupElement.__init__(self, parent=parent) + + def __hash__(self): + r""" + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: hash(s1), hash(s2), hash(t) + (2666658751600856334, 3639282354432100950, 3639281107336048003) # 64-bit + (-1973744370, 88459862, -1467077245) # 32-bit + """ + return hash(self._perm) ^ hash(self._colors) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*t + [[1, 0, 0], [3, 1, 2]] + """ + return repr([list(self._colors), self._perm]) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: latex(s1*s2*t) + [3_{1}, 1_{0}, 2_{0}] + """ + ret = "[" + ret += ", ".join("{}_{{{}}}".format(x, self._colors[i]) + for i, x in enumerate(self._perm)) + return ret + "]" + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + """ + colors = tuple(self._colors[i] + other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: ~t + [[0, 0, 3], [1, 2, 3]] + sage: all(x * ~x == C.one() for x in C.gens()) + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([-self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s1*s2 == s2*s1 + False + """ + if not isinstance(other, ColoredPermutation): + return False + return (self.parent() is other.parent() + and self._colors == other._colors + and self._perm == other._perm) + + def __ne__(self, other): + """ + Check inequality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 != s2*s1*s2 + False + sage: s1*s2 != s2*s1 + True + """ + return not self.__eq__(other) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: list(x) + [(1, 3), (0, 1), (0, 2)] + """ + for i, p in enumerate(self._perm): + yield (self._colors[i], p) + + def one_line_form(self): + """ + Return the one line form of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x + [[1, 0, 0], [3, 1, 2]] + sage: x.one_line_form() + [(1, 3), (0, 1), (0, 2)] + """ + return list(self) + + def colors(self): + """ + Return the colors of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.colors() + [1, 0, 0] + """ + return list(self._colors) + + def permutation(self): + """ + Return the permutation of ``self``. + + This is obtained by forgetting the colors. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.permutation() + [3, 1, 2] + """ + return self._perm + + def to_matrix(self): + """ + Return a matrix of ``self``. + + The colors are mapped to roots of unity. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t*s2; x.one_line_form() + [(1, 2), (0, 1), (0, 3)] + sage: M = x.to_matrix(); M + [ 0 1 0] + [zeta4 0 0] + [ 0 0 1] + + The matrix multiplication is in the *opposite* order:: + + sage: M == s2.to_matrix()*t.to_matrix()*s2.to_matrix()*s1.to_matrix() + True + """ + Cp = CyclotomicField(self.parent()._m) + g = Cp.gen() + D = diagonal_matrix(Cp, [g ** i for i in self._colors]) + return self._perm.to_matrix() * D + + +# TODO: Parts of this should be put in the category of complex +# reflection groups +class ColoredPermutations(Parent, UniqueRepresentation): + r""" + The group of `m`-colored permutations on `\{1, 2, \ldots, n\}`. + + Let `S_n` be the symmetric group on `n` letters and `C_m` be the cyclic + group of order `m`. The `m`-colored permutation group on `n` letters + is given by `P_n^m = C_m \wr S_n`. This is also the complex reflection + group `G(m, 1, n)`. + + We define our multiplication by + + .. MATH:: + + ((s_1, \ldots s_n), \sigma) \cdot ((t_1, \ldots, t_n), \tau) + = ((s_1 t_{\sigma(1)}, \ldots, s_n t_{\sigma(n)}), \tau \sigma). + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3); C + 4-colored permutations of size 3 + sage: s1,s2,t = C.gens() + sage: (s1, s2, t) + ([[0, 0, 0], [2, 1, 3]], [[0, 0, 0], [1, 3, 2]], [[0, 0, 1], [1, 2, 3]]) + sage: s1*s2 + [[0, 0, 0], [3, 1, 2]] + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s2*t*s2 + [[0, 1, 0], [1, 2, 3]] + + We can also create a colored permutation by passing + either a list of tuples consisting of ``(color, element)``:: + + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + + or a list of colors and a permutation:: + + sage: C([[3,3,1], [1,3,2]]) + [[3, 3, 1], [1, 3, 2]] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: C(P.an_element()) + [[0, 0, 0], [3, 1, 2]] + + REFERENCES: + + - :wikipedia:`Generalized_symmetric_group` + - :wikipedia:`Complex_reflection_group` + """ + def __init__(self, m, n, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(2, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(1, 3) + sage: TestSuite(C).run() + """ + if m <= 0: + raise ValueError("m must be a positive integer") + self._m = m + self._n = n + self._C = IntegerModRing(self._m) + self._P = Permutations(self._n) + if category is None: + category = (Groups(), FiniteEnumeratedSets()) + Parent.__init__(self, category=category) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: ColoredPermutations(4, 3) + 4-colored permutations of size 3 + """ + return "{}-colored permutations of size {}".format(self._m, self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.one() + [[0, 0, 0], [1, 2, 3]] + """ + return self.element_class(self, [self._C.zero()] * self._n, + self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.gens() + ([[0, 0, 0], [2, 1, 3]], + [[0, 0, 0], [1, 3, 2]], + [[0, 0, 1], [1, 2, 3]]) + """ + zero = [self._C.zero()] * self._n + g = [] + for i in range(self._n - 1): + p = range(1, self._n + 1) + p[i] = i + 2 + p[i + 1] = i + 1 + g.append(self.element_class(self, zero, self._P(p))) + zero[-1] = self._C.one() + g.append(self.element_class(self, zero, self._P.identity())) + return tuple(g) + + def matrix_group(self): + """ + Return the matrix group corresponding to ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.matrix_group() + Matrix group over Cyclotomic Field of order 4 and degree 2 with 3 generators ( + [0 1 0] [1 0 0] [ 1 0 0] + [1 0 0] [0 0 1] [ 0 1 0] + [0 0 1], [0 1 0], [ 0 0 zeta4] + ) + """ + from sage.groups.matrix_gps.finitely_generated import MatrixGroup + return MatrixGroup([g.to_matrix() for g in self.gens()]) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + INPUT: + + Either a list of pairs (color, element) + or a pair of lists (colors, elements). + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + sage: x == C([[2,3,3], [1,3,2]]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (color, element)") + c.append(self._C(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of colors and a permutation") + return self.element_class(self, [self._C(v) for v in x[0]], self._P(x[1])) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: C.has_coerce_map_from(S) + True + + sage: C = ColoredPermutations(4, 3) + sage: C.has_coerce_map_from(S) + False + sage: S = SignedPermutations(4) + sage: C.has_coerce_map_from(S) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [P._C.zero()]*P._n, x) + if self._m == 2 and isinstance(C, SignedPermutations) and C._n == self._n: + return lambda P, x: P.element_class(P, + [P._C.zero() if v == 1 else P._C.one() + for v in x._colors], + x._perm) + return super(ColoredPermutations, self)._coerce_map_from_(C) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 2) + sage: [x for x in C] + [[[0, 0], [1, 2]], + [[0, 1], [1, 2]], + [[1, 0], [1, 2]], + [[1, 1], [1, 2]], + [[0, 0], [2, 1]], + [[0, 1], [2, 1]], + [[1, 0], [2, 1]], + [[1, 1], [2, 1]]] + """ + for p in self._P: + for c in itertools.product(self._C, repeat=self._n): + yield self.element_class(self, c, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.cardinality() + 384 + sage: C.cardinality() == 4**3 * factorial(3) + True + """ + return self._m ** self._n * self._P.cardinality() + + def rank(self): + """ + Return the rank of ``self``. + + The rank of a complex reflection group is equal to the dimension + of the complex vector space the group acts on. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 12) + sage: C.rank() + 12 + sage: C = ColoredPermutations(7, 4) + sage: C.rank() + 4 + sage: C = ColoredPermutations(1, 4) + sage: C.rank() + 3 + """ + if self._m == 1: + return self._n - 1 + return self._n + + def degrees(self): + """ + Return the degrees of ``self``. + + The degrees of a complex reflection group are the degrees of + the fundamental invariants of the ring of polynomial invariants. + + If `m = 1`, then we are in the special case of the symmetric group + and the degrees are `(2, 3, \ldots, n, n+1)`. Otherwise the degrees + are `(m, 2m, \ldots, nm)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.degrees() + [4, 8, 12] + sage: S = ColoredPermutations(1, 3) + sage: S.degrees() + [2, 3] + + We now check that the product of the degrees is equal to the + cardinality of ``self``:: + + sage: prod(C.degrees()) == C.cardinality() + True + sage: prod(S.degrees()) == S.cardinality() + True + """ + if self._m == 1: # Special case for the usual symmetric group + return range(2, self._n + 1) + return [self._m * i for i in range(1, self._n + 1)] + + def codegrees(self): + r""" + Return the codegrees of ``self``. + + Let `G` be a complex reflection group. The codegrees + `d_1^* \leq d_2^* \leq \cdots \leq d_{\ell}^*` of `G` can be + defined by: + + .. MATH:: + + \prod_{i=1}^{\ell} (q - d_i^* - 1) + = \sum_{g \in G} \det(g) q^{\dim(V^g)}, + + where `V` is the natural complex vector space that `G` acts on + and `\ell` is the :meth:`rank`. + + If `m = 1`, then we are in the special case of the symmetric group + and the codegrees are `(n-2, n-3, \ldots 1, 0)`. Otherwise the degrees + are `((n-1)m, (n-2)m, \ldots, m, 0)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.codegrees() + [8, 4, 0] + sage: S = ColoredPermutations(1, 3) + sage: S.codegrees() + [1, 0] + + TESTS: + + We check the polynomial identity:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(3, 2) + sage: f = prod(q - ds - 1 for ds in C.codegrees()) + sage: d = lambda x: sum(1 for e in x.to_matrix().eigenvalues() if e == 1) + sage: g = sum(det(x.to_matrix()) * q**d(x) for x in C) + sage: f == g + True + """ + if self._m == 1: # Special case for the usual symmetric group + return list(reversed(range(self._n - 1))) + return [self._m * i for i in reversed(range(self._n))] + + def number_of_reflection_hyperplanes(self): + """ + Return the number of reflection hyperplanes of ``self``. + + The number of reflection hyperplanes of a complex reflection + group is equal to the sum of the codegrees plus the rank. + + EXAMPLES:: + + sage: C = ColoredPermutations(1, 2) + sage: C.number_of_reflection_hyperplanes() + 1 + sage: C = ColoredPermutations(1, 3) + sage: C.number_of_reflection_hyperplanes() + 3 + sage: C = ColoredPermutations(4, 12) + sage: C.number_of_reflection_hyperplanes() + 276 + """ + return sum(self.codegrees()) + self.rank() + + def fixed_point_polynomial(self, q=None): + r""" + The fixed point polynomial of ``self``. + + The fixed point polynomial `f_G` of a complex reflection group `G` + is counting the dimensions of fixed points subspaces: + + .. MATH:: + + f_G(q) = \sum_{w \in W} q^{\dim V^w}. + + Furthermore, let `d_1, d_2, \ldots, d_{\ell}` be the degrees of `G`, + where `\ell` is the :meth:`rank`. Then the fixed point polynomial + is given by + + .. MATH:: + + f_G(q) = \prod_{i=1}^{\ell} (q + d_i - 1). + + INPUT: + + - ``q`` -- (default: the generator of ``ZZ['q']``) the parameter `q` + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial() + q^3 + 21*q^2 + 131*q + 231 + + sage: S = ColoredPermutations(1, 3) + sage: S.fixed_point_polynomial() + q^2 + 3*q + 2 + + TESTS: + + We check the against the degrees and codegrees:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial(q) == prod(q + d - 1 for d in C.degrees()) + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + return prod(q + d - 1 for d in self.degrees()) + + def is_well_generated(self): + """ + Return if ``self`` is a well-generated complex reflection group. + + A complex reflection group `G` is well-generated if it is + generated by `\ell` reflections. Equivalently, `G` is well-generated + if `d_i + d_i^* = d_{\ell}` for all `1 \leq i \leq \ell`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(2, 8) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(1, 4) + sage: C.is_well_generated() + True + """ + deg = self.degrees() + dstar = self.codegrees() + return all(deg[-1] == d + dstar[i] for i, d in enumerate(deg)) + + Element = ColoredPermutation + +##################################################################### +## Signed permutations + + +class SignedPermutation(ColoredPermutation): + """ + A signed permutation. + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: s4*s1*s2*s3*s4 + [-4, 1, 2, -3] + """ + return repr(list(self)) + + _latex_ = _repr_ + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x * x + [3, -4, 1, -2] + + :: + + sage: s1*s2*s1 == s1*s2*s1 + True + sage: s3*s4*s3*s4 == s4*s3*s4*s3 + True + """ + colors = tuple(self._colors[i] * other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: ~x + [2, 3, -4, -1] + sage: x * ~x == S.one() + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [a for a in x] + [-4, 1, 2, -3] + """ + for i, p in enumerate(self._perm): + yield self._colors[i] * p + + def to_matrix(self): + """ + Return a matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: M = x.to_matrix(); M + [ 0 1 0 0] + [ 0 0 1 0] + [ 0 0 0 -1] + [-1 0 0 0] + + The matrix multiplication is in the *opposite* order:: + + sage: m1,m2,m3,m4 = [g.to_matrix() for g in S.gens()] + sage: M == m4 * m3 * m2 * m1 * m4 + True + """ + return self._perm.to_matrix() * diagonal_matrix(self._colors) + + def has_left_descent(self, i): + """ + Return ``True`` if ``i`` is a left descent of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [x.has_left_descent(i) for i in S.index_set()] + [True, False, False, True] + """ + n = self.parent()._n + if i == n: + return self._colors[-1] == -1 + if self._colors[i - 1] == -1: + return self._colors[i] == 1 or self._perm[i - 1] < self._perm[i] + return self._colors[i] == 1 and self._perm[i - 1] > self._perm[i] + + +class SignedPermutations(ColoredPermutations): + r""" + Group of signed permutations. + + The group of signed permutations is also known as the hyperoctahedral + group, the Coxeter group of type `B_n`, and the 2-colored permutation + group. Thus it can be constructed as the wreath product `S_2 \wr S_n`. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.group_generators() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x^4 == S.one() + True + + This is a finite Coxeter group of type `B_n`:: + + sage: S.canonical_representation() + Finite Coxeter group over Universal Cyclotomic Field with Coxeter matrix: + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + sage: S.long_element() + [-4, -3, -2, -1] + sage: S.long_element().reduced_word() + [4, 3, 4, 2, 3, 4, 1, 2, 3, 4] + + We can also go between the 2-colored permutation group:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.an_element() + [-3, 1, 2] + sage: C(S.an_element()) + [[1, 0, 0], [3, 1, 2]] + sage: S(C(S.an_element())) == S.an_element() + True + sage: S(C.an_element()) + [1, 2, 3] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: x = S(P.an_element()); x + [3, 1, 2] + sage: x.parent() + Signed permutations of 3 + + REFERENCES: + + - :wikipedia:`Hyperoctahedral_group` + """ + def __init__(self, n): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: TestSuite(S).run() + """ + ColoredPermutations.__init__(self, 2, n, FiniteCoxeterGroups()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SignedPermutations(4) + Signed permutations of 4 + """ + return "Signed permutations of {}".format(self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.one() + [1, 2, 3, 4] + """ + return self.element_class(self, [ZZ.one()] * self._n, + self._P.identity()) + + def simple_reflection(self, i): + r""" + Return the ``i``-th simple reflection of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.simple_reflection(1) + [2, 1, 3, 4] + sage: S.simple_reflection(4) + [1, 2, 3, -4] + """ + if i not in self.index_set(): + raise ValueError("i must be in the index set") + if i < self._n: + p = range(1, self._n + 1) + p[i - 1] = i + 1 + p[i] = i + return self.element_class(self, [ZZ.one()] * self._n, self._P(p)) + temp = [ZZ.one()] * self._n + temp[-1] = -ZZ.one() + return self.element_class(self, temp, self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.gens() + ([2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3], [1, 2, 3, -4]) + """ + return tuple(self.simple_reflection(i) for i in self.index_set()) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + TESTS:: + + sage: S = SignedPermutations(3) + sage: x = S([(+1,1), (-1,3), (-1,2)]); x + [1, -3, -2] + sage: x == S([[+1,-1,-1], [1,3,2]]) + True + sage: x == S([1, -3, -2]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (sign, element)") + if k[0] != 1 and k[0] != -1: + raise ValueError("the sign must be +1 or -1") + c.append(ZZ(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) == self._n: + c = [] + p = [] + one = ZZ.one() + for v in x: + if v > 0: + c.append(one) + p.append(v) + else: + c.append(-one) + p.append(-v) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of signs and a permutation") + if any(s != 1 and s != -1 for s in x[0]): + raise ValueError("the sign must be +1 or -1") + return self.element_class(self, [ZZ(v) for v in x[0]], self._P(x[1])) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(2) + sage: [x for x in S] + [[1, 2], [1, -2], [-1, 2], [-1, -2], + [2, 1], [2, -1], [-2, 1], [-2, -1]] + """ + pmone = [ZZ.one(), -ZZ.one()] + for p in self._P: + for c in itertools.product(pmone, repeat=self._n): + yield self.element_class(self, c, p) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.has_coerce_map_from(C) + True + + sage: C = ColoredPermutations(4, 3) + sage: S.has_coerce_map_from(C) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [1]*P._n, x) + if isinstance(C, ColoredPermutations) and C._n == self._n and C._m == 2: + return lambda P, x: P.element_class(P, + [1 if v == 0 else -1 + for v in x._colors], + x._perm) + return super(SignedPermutations, self)._coerce_map_from_(C) + + @cached_method + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.index_set() + (1, 2, 3, 4) + """ + return tuple(range(1, self._n + 1)) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.coxeter_matrix() + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + """ + from sage.combinat.root_system.cartan_type import CartanType + return CartanType(['B', self._n]).coxeter_matrix() + + def long_element(self, index_set=None): + """ + Return the longest element of ``self``, or of the + parabolic subgroup corresponding to the given ``index_set``. + + INPUT: + + - ``index_set`` -- (optional) a subset (as a list or iterable) + of the nodes of the indexing set + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.long_element() + [-4, -3, -2, -1] + """ + if index_set is not None: + return super(SignedPermutations, self).long_element() + p = range(self._n, 0, -1) + return self.element_class(self, [-ZZ.one()] * self._n, self._P(p)) + + Element = SignedPermutation + +# TODO: Make this a subgroup +#class EvenSignedPermutations(SignedPermutations): +# """ +# Group of even signed permutations. +# """ +# def _repr_(self): +# """ +# Return a string representation of ``self``. +# """ +# return "Even signed permtuations of {}".format(self._n) +# +# def __iter__(self): +# """ +# Iterate over ``self``. +# """ +# for s in SignedPermutations.__iter__(self): +# total = 0 +# for pm in s._colors: +# if pm == -1: +# total += 1 +# +# if total % 2 == 0: +# yield s diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 93eaea33942..ee2be8cc2ec 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2774,9 +2774,12 @@ def bell_polynomial(n, k): .. MATH:: - B_{n,k}(x_1, x_2, \ldots, x_{n-k+1}) = \sum_{\sum{j_i}=k, \sum{i j_i} - =n} \frac{n!}{j_1!j_2!\cdots} \frac{x_1}{1!}^j_1 \frac{x_2}{2!}^j_2 - \cdots. + B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = + \sum_{\sum{j_i}=k, \sum{(i+1) j_i}=n} + \frac{n!}{j_0!j_1!\cdots j_{n-k}!} + \left(\frac{x_0}{(0+1)!}\right)^{j_0} + \left(\frac{x_1}{(1+1)!}\right)^{j_1} \cdots + \left(\frac{x_{n-k}}{(n-k+1)!}\right)^{j_{n-k}}. INPUT: @@ -2786,14 +2789,30 @@ def bell_polynomial(n, k): OUTPUT: - - a polynomial in `n-k+1` variables over `\QQ` + - a polynomial in `n-k+1` variables over `\ZZ` EXAMPLES:: sage: bell_polynomial(6,2) - 10*x_3^2 + 15*x_2*x_4 + 6*x_1*x_5 + 10*x2^2 + 15*x1*x3 + 6*x0*x4 sage: bell_polynomial(6,3) - 15*x_2^3 + 60*x_1*x_2*x_3 + 15*x_1^2*x_4 + 15*x1^3 + 60*x0*x1*x2 + 15*x0^2*x3 + + TESTS: + + Check that :trac:`18338` is fixed:: + + sage: bell_polynomial(0,0).parent() + Multivariate Polynomial Ring in x over Integer Ring + + sage: for n in (0..4): + ....: print [bell_polynomial(n,k).coefficients() for k in (0..n)] + [[1]] + [[], [1]] + [[], [1], [1]] + [[], [1], [3], [1]] + [[], [1], [3, 4], [6], [1]] + REFERENCES: @@ -2802,24 +2821,24 @@ def bell_polynomial(n, k): AUTHORS: - Blair Sutton (2009-01-26) + - Thierry Monteil (2015-09-29): the result must always be a polynomial. """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.combinat.partition import Partitions from sage.rings.arith import factorial - vars = ZZ[tuple(['x_'+str(i) for i in range(1, n-k+2)])].gens() - result = 0 + R = PolynomialRing(ZZ, 'x', n-k+1) + vars = R.gens() + result = R.zero() for p in Partitions(n, length=k): - factorial_product = 1 + factorial_product = 1 power_factorial_product = 1 for part, count in p.to_exp_dict().iteritems(): factorial_product *= factorial(count) power_factorial_product *= factorial(part)**count - - coefficient = factorial(n) / (factorial_product * power_factorial_product) + coefficient = factorial(n) // (factorial_product * power_factorial_product) result += coefficient * prod([vars[i - 1] for i in p]) - return result - def fibonacci_sequence(start, stop=None, algorithm=None): r""" Return an iterator over the Fibonacci sequence, for all fibonacci diff --git a/src/sage/combinat/combinatorial_map.py b/src/sage/combinat/combinatorial_map.py index dee526057de..be6fa1e0417 100644 --- a/src/sage/combinat/combinatorial_map.py +++ b/src/sage/combinat/combinatorial_map.py @@ -264,14 +264,14 @@ def _sage_src_lines_(self): sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper sage: import imp - sage: _ = imp.reload(sage.combinat.permutation); + sage: _ = imp.reload(sage.combinat.permutation) sage: p = Permutation([1,3,2,4]) sage: cm = p.left_tableau; cm Combinatorial map: Robinson-Schensted insertion tableau sage: (src, lines) = cm._sage_src_lines_() sage: src[0] " @combinatorial_map(name='Robinson-Schensted insertion tableau')\n" - sage: lines # random + sage: lines # random 2653 """ from sage.misc.sageinspect import sage_getsourcelines diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 1ccbbee4cfe..a49a587798d 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -33,10 +33,12 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.rings.all import ZZ from combinat import CombinatorialElement -from cartesian_product import CartesianProduct -from integer_list import IntegerListsLex +from sage.categories.cartesian_product import cartesian_product + +from integer_lists import IntegerListsLex import __builtin__ from sage.rings.integer import Integer from sage.combinat.combinatorial_map import combinatorial_map @@ -748,10 +750,16 @@ def finer(self): sage: C = Composition([3,2]).finer() sage: C.cardinality() 8 - sage: list(C) + sage: C.list() [[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 1, 1], [1, 2, 2], [2, 1, 1, 1], [2, 1, 2], [3, 1, 1], [3, 2]] + + sage: Composition([]).finer() + {[]} """ - return CartesianProduct(*[Compositions(i) for i in self]).map(Composition.sum) + if not self: + return FiniteEnumeratedSet([self]) + else: + return cartesian_product([Compositions(i) for i in self]).map(Composition.sum) def is_finer(self, co2): """ @@ -1308,7 +1316,7 @@ def wll_gt(self, co2): ############################################################## -class Compositions(Parent, UniqueRepresentation): +class Compositions(UniqueRepresentation, Parent): r""" Set of integer compositions. diff --git a/src/sage/combinat/composition_signed.py b/src/sage/combinat/composition_signed.py index 918fcf2241f..eff46c83d51 100644 --- a/src/sage/combinat/composition_signed.py +++ b/src/sage/combinat/composition_signed.py @@ -16,8 +16,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from composition import Compositions_n, Composition -import cartesian_product from sage.rings.all import Integer from sage.rings.arith import binomial import __builtin__ @@ -130,8 +131,7 @@ def __iter__(self): """ for comp in Compositions_n.__iter__(self): l = len(comp) - a = [[1,-1] for i in range(l)] - for sign in cartesian_product.CartesianProduct(*a): + for sign in itertools.product([1,-1], repeat=l): yield [ sign[i]*comp[i] for i in range(l)] from sage.structure.sage_object import register_unpickle_override diff --git a/src/sage/combinat/core.py b/src/sage/combinat/core.py index 6bf003b0a2d..69991ce40b5 100644 --- a/src/sage/combinat/core.py +++ b/src/sage/combinat/core.py @@ -150,8 +150,9 @@ def __hash__(self): def _latex_(self): """ - Outputs the LaTeX representation of this core as a partition. See the - ``_latex_()`` method of :class:`Partition`. + Output the LaTeX representation of this core as a partition. + + See the ``_latex_`` method of :class:`Partition`. EXAMPLES:: diff --git a/src/sage/combinat/counting.py b/src/sage/combinat/counting.py index 65f425baff4..644d8aeb4ae 100644 --- a/src/sage/combinat/counting.py +++ b/src/sage/combinat/counting.py @@ -7,6 +7,7 @@ - :ref:`sage.combinat.expnums` - :ref:`sage.combinat.q_analogues`, :ref:`sage.combinat.q_bernoulli` - :ref:`sage.combinat.binary_recurrence_sequences` +- :ref:`sage.rings.cfinite_sequence` - :ref:`sage.combinat.combinat` .. TODO:: diff --git a/src/sage/combinat/crystals/affine.py b/src/sage/combinat/crystals/affine.py index 136a1873c32..bc74fad55f0 100644 --- a/src/sage/combinat/crystals/affine.py +++ b/src/sage/combinat/crystals/affine.py @@ -15,6 +15,7 @@ from sage.misc.abstract_method import abstract_method from sage.categories.regular_crystals import RegularCrystals from sage.categories.finite_crystals import FiniteCrystals +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper @@ -349,7 +350,7 @@ def e(self, i): [[3]] sage: b.e(1) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.e0() else: x = self.lift().e(i) @@ -374,7 +375,7 @@ def f(self, i): [[1]] sage: b.f(2) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.f0() else: x = self.lift().f(i) @@ -417,7 +418,7 @@ def epsilon(self, i): sage: [x.epsilon(1) for x in A.list()] [0, 1, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.epsilon0() else: return self.lift().epsilon(i) @@ -455,18 +456,50 @@ def phi(self, i): sage: [x.phi(1) for x in A.list()] [1, 0, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.phi0() else: return self.lift().phi(i) - def __lt__(self, other): + def __eq__(self, other): """ Non elements of the crystal are incomparable with elements of the crystal (or should it return ``NotImplemented``?). Elements of this crystal are compared using the comparison in the underlying classical crystal. + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b==c + False + sage: b==b + True + sage: b==1 + False + """ + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b!=c + True + sage: b!=b + False + sage: b!=1 + True + """ + return not self == other + + def __lt__(self, other): + """" EXAMPLES:: sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) @@ -479,9 +512,74 @@ def __lt__(self, other): sage: bc + False + sage: b>b + False + sage: c>b + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __le__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b<=c + True + sage: b<=b + True + sage: c<=b + False + """ + return parent(self) is parent(other) and self.value <= other.value + + def __ge__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: c>=b + True + sage: b>=b + True + sage: b>=c + False + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: cmp(b,c) + -1 + sage: cmp(b,b) + 0 + + If the parent are different, it uses comparison of the parents:: + + sage: cmp(b,1) == cmp(b.parent(), ZZ) + True + """ + return cmp(parent(self), parent(other)) or cmp(self.value, other.value) AffineCrystalFromClassical.Element = AffineCrystalFromClassicalElement diff --git a/src/sage/combinat/crystals/affinization.py b/src/sage/combinat/crystals/affinization.py index 8bd5d7bf212..62b1fb5c213 100644 --- a/src/sage/combinat/crystals/affinization.py +++ b/src/sage/combinat/crystals/affinization.py @@ -17,6 +17,7 @@ # http://www.gnu.org/licenses/ #**************************************************************************** +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element @@ -24,7 +25,7 @@ from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.rings.infinity import Infinity -class AffinizationOfCrystal(Parent, UniqueRepresentation): +class AffinizationOfCrystal(UniqueRepresentation, Parent): r""" An affiniziation of a crystal. @@ -187,11 +188,23 @@ def _latex_(self): from sage.misc.latex import latex return latex(self._b) + "({})".format(self._m) - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() + sage: mg = A.module_generators[0] + sage: hash(mg) + -6948036233304877976 # 64-bit + -1420700568 # 32-bit """ - Check equality. + return hash(self._b) ^ hash(self._m) - EXAMPLES:: + def __cmp__(self, other): + """ + Comparison. + + TESTS:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -203,17 +216,6 @@ def __eq__(self, other): sage: A = crystals.AffinizationOf(KT) sage: A(KT.module_generators[3], 1).f(0) == A.module_generators[0] True - """ - if not isinstance(other, AffinizationOfCrystal.Element): - return False - return self.parent() == other.parent() \ - and self._b == other._b and self._m == other._m - - def __ne__(self, other): - """ - Check inequality. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -221,14 +223,7 @@ def __ne__(self, other): True sage: mg != mg.f(2).e(2) False - """ - return not self.__eq__(other) - def __lt__(self, other): - """ - Check less than. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: S = A.subcrystal(max_depth=2) @@ -241,8 +236,12 @@ def __lt__(self, other): [[1, 2], [2, 3]](1), [[1, 2], [3, 3]](1), [[2, 2], [3, 3]](2)] + """ - return self._m < other._m or (self._m == other._m and self._b < other._b) + if parent(self) is parent(other): + return cmp(self._m, other._m) or cmp(self._b, other._b) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index 7c71c666b49..1e73f182842 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -714,23 +714,33 @@ def epsilon(self, i): def weight(self): """ - Returns the weight of self. + Return the weight of ``self``. EXAMPLES:: sage: C = crystals.AlcovePaths(['A',2],[2,0]) sage: for i in C: i.weight() - 2*Lambda[1] - Lambda[2] - Lambda[1] - Lambda[2] - -2*Lambda[1] + 2*Lambda[2] - -Lambda[1] - -2*Lambda[2] + (0, 2, 0) + (0, 0, 1) + (0, 1, -1) + (0, -2, 2) + (0, -1, 0) + (0, 0, -2) sage: B = crystals.AlcovePaths(['A',2,1],[1,0,0]) sage: p = B.module_generators[0].f_string([0,1,2]) sage: p.weight() Lambda[0] - delta + + TESTS: + + Check that crystal morphisms work (:trac:`19481`):: + + sage: C1 = crystals.AlcovePaths(['A',2],[1,0]) + sage: C2 = crystals.AlcovePaths(['A',2],[2,0]) + sage: phi = C1.crystal_morphism(C2.module_generators, scaling_factors={1:2, 2:2}) + sage: [phi(x) for x in C1] + [(), ((alpha[1], 0),), ((alpha[1], 0), (alpha[1] + alpha[2], 0))] """ root_space = self.parent().R.root_space() weight = -self.parent().weight @@ -738,7 +748,10 @@ def weight(self): root = root_space(i.root) weight = -i.height*root + weight.reflection(root) - return -weight + WLR = self.parent().weight_lattice_realization() + B = WLR.basis() + return WLR._from_dict({i: Integer(c) for i,c in -weight}, + remove_zeros=False) #def __repr__(self): #return str(self.integer_sequence()) @@ -1206,6 +1219,7 @@ def __classcall_private__(cls, starting_weight, cartan_type = None): format (also necessary for UniqueRepresentation). TESTS:: + sage: from sage.combinat.crystals.alcove_path import RootsWithHeight sage: R = RootsWithHeight(['A',2],[3,2]) sage: S = RootsWithHeight(CartanType(['A',2]), (3,2)) @@ -1218,7 +1232,6 @@ def __classcall_private__(cls, starting_weight, cartan_type = None): sage: B = RootsWithHeight(La[2]) sage: B is C True - """ if cartan_type is not None: cartan_type, starting_weight = CartanType(starting_weight), cartan_type diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index d8856ec9a20..3745064c7b5 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -83,6 +83,7 @@ from sage.combinat.root_system.cartan_type import CartanType from sage.combinat.root_system.root_system import RootSystem from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ class AbstractSingleCrystalElement(Element): r""" @@ -102,6 +103,17 @@ def __lt__(self,other): """ return False + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.elementary.Component("D7") + sage: c = C.highest_weight_vector() + sage: hash(c) # random + 879 + """ + return hash(self.parent()) + def __eq__(self,other): r""" EXAMPLES:: @@ -181,7 +193,7 @@ def f(self,i): """ return None -class TCrystal(Parent, UniqueRepresentation): +class TCrystal(UniqueRepresentation, Parent): r""" The crystal `T_{\lambda}`. @@ -416,7 +428,7 @@ def weight(self): """ return self.parent()._weight -class RCrystal(Parent, UniqueRepresentation): +class RCrystal(UniqueRepresentation, Parent): r""" The crystal `R_{\lambda}`. @@ -672,7 +684,7 @@ def weight(self): """ return self.parent()._weight -class ElementaryCrystal(Parent, UniqueRepresentation): +class ElementaryCrystal(UniqueRepresentation, Parent): r""" The elementary crystal `B_i`. @@ -787,7 +799,7 @@ def _element_constructor_(self, m): sage: B(721) 721 """ - return self.element_class(self, m) + return self.element_class(self, ZZ(m)) def weight_lattice_realization(self): """ @@ -817,6 +829,16 @@ def __init__(self, parent, m): self._m = m Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: B = crystals.elementary.Elementary(['B',7],7) + sage: hash(B(17)) + 17 + """ + return hash(self._m) + def _repr_(self): r""" EXAMPLES:: @@ -986,7 +1008,7 @@ def weight(self): Q = self.parent().weight_lattice_realization() return self._m * Q.simple_root(self.parent()._i) -class ComponentCrystal(Parent,UniqueRepresentation): +class ComponentCrystal(UniqueRepresentation, Parent): r""" The component crystal. diff --git a/src/sage/combinat/crystals/fast_crystals.py b/src/sage/combinat/crystals/fast_crystals.py index 9bf2a3ec596..7372dbd1f9d 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -364,7 +364,17 @@ def _repr_(self): else: raise NotImplementedError - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) + sage: hash(C(0)) + 0 + """ + return hash(self.value) + + def __cmp__(self, other): """ EXAMPLES:: @@ -376,14 +386,6 @@ def __eq__(self, other): False sage: C(0) == D(0) False - """ - return self.__class__ is other.__class__ and \ - self.parent() == other.parent() and \ - self.value == other.value - - def __ne__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: D = crystals.FastRankTwo(['B',2],shape=[2,1]) @@ -393,13 +395,6 @@ def __ne__(self, other): True sage: C(0) != D(0) True - """ - return not self == other - - - def __cmp__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: C(1) < C(2) @@ -411,11 +406,10 @@ def __cmp__(self, other): sage: C(1) <= C(1) True """ - if type(self) is not type(other): - return cmp(type(self), type(other)) - if self.parent() != other.parent(): - return cmp(self.parent(), other.parent()) - return self.parent().cmp_elements(self, other) + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ @@ -436,7 +430,6 @@ def e(self, i): r = self.parent()._rootoperators[self.value][2] return self.parent()(r) if r is not None else None - def f(self, i): """ Returns the action of `f_i` on self. diff --git a/src/sage/combinat/crystals/generalized_young_walls.py b/src/sage/combinat/crystals/generalized_young_walls.py index 14bd73956ec..af4605c219e 100644 --- a/src/sage/combinat/crystals/generalized_young_walls.py +++ b/src/sage/combinat/crystals/generalized_young_walls.py @@ -641,7 +641,7 @@ def in_highest_weight_crystal(self,La): return True -class InfinityCrystalOfGeneralizedYoungWalls(Parent,UniqueRepresentation): +class InfinityCrystalOfGeneralizedYoungWalls(UniqueRepresentation, Parent): r""" The crystal `\mathcal{Y}(\infty)` of generalized Young walls of type `A_n^{(1)}` as defined in [KS10]_. diff --git a/src/sage/combinat/crystals/highest_weight_crystals.py b/src/sage/combinat/crystals/highest_weight_crystals.py index fc8f0974685..d36ad73a966 100644 --- a/src/sage/combinat/crystals/highest_weight_crystals.py +++ b/src/sage/combinat/crystals/highest_weight_crystals.py @@ -63,7 +63,7 @@ def HighestWeightCrystal(dominant_weight, model=None): ` * ``'GeneralizedYoungWalls'`` - :class:`generalized Young walls ` - * ``'RiggedConfigurations'`` - :class:`rigged configuraitons + * ``'RiggedConfigurations'`` - :class:`rigged configurations ` EXAMPLES:: diff --git a/src/sage/combinat/crystals/induced_structure.py b/src/sage/combinat/crystals/induced_structure.py index d7770927072..ba05ede86ad 100644 --- a/src/sage/combinat/crystals/induced_structure.py +++ b/src/sage/combinat/crystals/induced_structure.py @@ -27,7 +27,7 @@ from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper -class InducedCrystal(Parent, UniqueRepresentation): +class InducedCrystal(UniqueRepresentation, Parent): r""" A crystal induced from an injection. @@ -108,14 +108,14 @@ def __classcall_private__(cls, X, phi, inverse=None, from_crystal=False): TESTS:: - sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,3)) - sage: G = GelfandTsetlinPatterns(4, 3) - sage: phi = lambda x: D(x.to_tableau()) - sage: phi_inv = lambda x: G(x.to_tableau()) - sage: I1 = crystals.Induced(G, phi, phi_inv) - sage: I2 = crystals.Induced(G, phi, phi_inv) - sage: I1 is I2 - True + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,3)) + sage: G = GelfandTsetlinPatterns(4, 3) + sage: phi = lambda x: D(x.to_tableau()) + sage: phi_inv = lambda x: G(x.to_tableau()) + sage: I1 = crystals.Induced(G, phi, phi_inv) + sage: I2 = crystals.Induced(G, phi, phi_inv) + sage: I1 is I2 + True """ if from_crystal: return InducedFromCrystal(X, phi, inverse) @@ -396,7 +396,7 @@ def weight(self): """ return self.parent()._phi(self.value).weight() -class InducedFromCrystal(Parent, UniqueRepresentation): +class InducedFromCrystal(UniqueRepresentation, Parent): r""" A crystal induced from an injection. diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index 474a8a82f2e..c945e4cc32b 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -149,7 +149,7 @@ def KirillovReshetikhinCrystal(cartan_type, r, s, model='KN'): There are a variety of models for Kirillov-Reshetikhin crystals. There is one using the classical crystal with :func:`Kashiwara-Nakashima tableaux `. - There is one using :class:`rigged configurations `. + There is one using :class:`rigged configurations `. Another tableaux model comes from the bijection between rigged configurations and tensor products of tableaux called :class:`Kirillov-Reshetikhin tableaux ` diff --git a/src/sage/combinat/crystals/monomial_crystals.py b/src/sage/combinat/crystals/monomial_crystals.py index a1c2afa155a..876a40600b7 100644 --- a/src/sage/combinat/crystals/monomial_crystals.py +++ b/src/sage/combinat/crystals/monomial_crystals.py @@ -162,6 +162,18 @@ def _repr_(self): return_str += "Y(%s,%s) "%(L[x][0][0],L[x][0][1]) return return_str + def __hash__(self): + r""" + TESTS:: + + sage: M = crystals.infinity.NakajimaMonomials(['C',5]) + sage: m1 = M.module_generators[0].f(1) + sage: hash(m1) + 4715601665014767730 # 64-bit + -512614286 # 32-bit + """ + return hash(frozenset(tuple(self._dict.iteritems()))) + def __eq__(self,other): r""" EXAMPLES:: @@ -759,7 +771,7 @@ def f(self,i): d[(i,kf)] = d.get((i,kf),0) - 1 return self.__class__(self.parent(), d) -class InfinityCrystalOfNakajimaMonomials(Parent,UniqueRepresentation): +class InfinityCrystalOfNakajimaMonomials(UniqueRepresentation, Parent): r""" Let `Y_{i,k}`, for `i \in I` and `k \in \ZZ`, be a commuting set of variables, and let `\boldsymbol{1}` be a new variable which commutes with diff --git a/src/sage/combinat/crystals/spins.py b/src/sage/combinat/crystals/spins.py index af6610e0c2e..4b6e4bc5742 100644 --- a/src/sage/combinat/crystals/spins.py +++ b/src/sage/combinat/crystals/spins.py @@ -181,7 +181,6 @@ def __init__(self, ct, element_class, case): self.rename("The minus crystal of spins for type %s"%ct) self.Element = element_class -# super(GenericCrystalOfSpins, self).__init__(category = FiniteEnumeratedSets()) Parent.__init__(self, category = ClassicalCrystals()) if case == "minus": @@ -189,27 +188,8 @@ def __init__(self, ct, element_class, case): generator.append(-1) else: generator = [1]*ct[1] - self.module_generators = (self._element_constructor_(tuple(generator)),) - self._list = list(self) -# self._digraph = ClassicalCrystal.digraph(self) - self._digraph = super(GenericCrystalOfSpins, self).digraph() - self._digraph_closure = self.digraph().transitive_closure() - - def __call__(self, value): - """ - Parse input for ``cached_method``. + self.module_generators = (self.element_class(self, tuple(generator)),) - EXAMPLES:: - - sage: C = crystals.Spins(['B',3]) - sage: C([1,1,1]) - +++ - """ - if value.__class__ == self.element_class and value.parent() == self: - return value - return self._element_constructor_(tuple(value)) - - @cached_method def _element_constructor_(self, value): """ Construct an element of ``self`` from ``value``. @@ -217,21 +197,14 @@ def _element_constructor_(self, value): EXAMPLES:: sage: C = crystals.Spins(['B',3]) - sage: C((1,1,1)) + sage: x = C((1,1,1)); x +++ + sage: y = C([1,1,1]); y + +++ + sage: x == y + True """ - return self.element_class(self, value) - - def list(self): - """ - Return a list of the elements of ``self``. - - EXAMPLES:: - - sage: crystals.Spins(['B',3]).list() - [+++, ++-, +-+, -++, +--, -+-, --+, ---] - """ - return self._list + return self.element_class(self, tuple(value)) def digraph(self): """ @@ -242,6 +215,12 @@ def digraph(self): sage: crystals.Spins(['B',3]).digraph() Digraph on 8 vertices """ + try: + return self._digraph + except AttributeError: + pass + self._digraph = super(GenericCrystalOfSpins, self).digraph() + self._digraph.copy(immutable=True) return self._digraph def lt_elements(self, x,y): @@ -266,8 +245,13 @@ def lt_elements(self, x,y): False """ if x.parent() is not self or y.parent() is not self: - raise ValueError("Both elements must be in this crystal") - if self._digraph_closure.has_edge(x,y): + raise ValueError("both elements must be in this crystal") + try: + GC = self._digraph_closure + except AttributeError: + GC = self.digraph().transitive_closure() + self._digraph_closure = GC + if GC.has_edge(x,y): return True return False @@ -432,14 +416,14 @@ def e(self, i): ret = [self.value[x] for x in range(rank)] ret[i-1] = 1 ret[i] = -1 - return self.parent()(ret) + return self.__class__(self.parent(), tuple(ret)) elif i == rank: if self.value[i-1] == -1: ret = [self.value[x] for x in range(rank)] ret[i-1] = 1 - return self.parent()(ret) - else: - return None + return self.__class__(self.parent(), tuple(ret)) + + return None def f(self, i): r""" @@ -459,14 +443,14 @@ def f(self, i): ret = [self.value[x] for x in range(rank)] ret[i-1] = -1 ret[i] = 1 - return self.parent()(ret) + return self.__class__(self.parent(), tuple(ret)) elif i == rank: if self.value[i-1] == 1: ret = [self.value[x] for x in range(rank)] ret[i-1] = -1 - return self.parent()(ret) - else: - return None + return self.__class__(self.parent(), tuple(ret)) + + return None class Spin_crystal_type_D_element(Spin): r""" @@ -497,15 +481,15 @@ def e(self, i): ret = [self.value[x] for x in range(rank)] ret[i-1] = 1 ret[i] = -1 - return self.parent()(ret) + return self.__class__(self.parent(), tuple(ret)) elif i == rank: if self.value[i-2] == -1 and self.value[i-1] == -1: ret = [self.value[x] for x in range(rank)] ret[i-2] = 1 ret[i-1] = 1 - return self.parent()(ret) - else: - return None + return self.__class__(self.parent(), tuple(ret)) + + return None def f(self, i): r""" @@ -532,12 +516,12 @@ def f(self, i): ret = [self.value[x] for x in range(rank)] ret[i-1] = -1 ret[i] = 1 - return self.parent()(ret) + return self.__class__(self.parent(), tuple(ret)) elif i == rank: if self.value[i-2] == 1 and self.value[i-1] == 1: ret = [self.value[x] for x in range(rank)] ret[i-2] = -1 ret[i-1] = -1 - return self.parent()(ret) - else: - return None + return self.__class__(self.parent(), tuple(ret)) + + return None diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py index 01854b2af76..031b1d6c1d2 100644 --- a/src/sage/combinat/crystals/subcrystal.py +++ b/src/sage/combinat/crystals/subcrystal.py @@ -25,6 +25,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper from sage.categories.crystals import Crystals @@ -33,7 +34,7 @@ from sage.rings.integer import Integer from sage.rings.infinity import infinity -class Subcrystal(Parent, UniqueRepresentation): +class Subcrystal(UniqueRepresentation, Parent): """ A subcrystal `X` of an ambient crystal `Y` is a crystal formed by taking a subset of `Y` and whose crystal structure is induced by `Y`. @@ -127,6 +128,8 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, generators = ambient.module_generators category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() if virtualization is not None: if scaling_factors is None: @@ -250,6 +253,18 @@ def cardinality(self): sage: S = B.subcrystal(max_depth=4) sage: S.cardinality() 22 + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.infinity.Tableaux(['A',3]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.cardinality() + Traceback (most recent call last): + ... + NotImplementedError: unknown cardinality """ if self._cardinality is not None: return self._cardinality @@ -261,7 +276,10 @@ def cardinality(self): except AttributeError: if self in FiniteCrystals(): return Integer(len(self.list())) - card = super(Subcrystal, self).cardinality() + try: + card = super(Subcrystal, self).cardinality() + except AttributeError: + raise NotImplementedError("unknown cardinality") if card == infinity: self._cardinality = card return card @@ -285,9 +303,9 @@ class Element(ElementWrapper): """ An element of a subcrystal. Wraps an element in the ambient crystal. """ - def __lt__(self, other): + def __eq__(self, other): """ - Check less than. + Check sorting EXAMPLES:: @@ -307,11 +325,87 @@ def __lt__(self, other): [[-1, -1]](1), [[-1, -1]](2)] """ - if not isinstance(other, Subcrystal.Element): - return False - if other.parent() is not self.parent(): - return False - return self.value < other.value + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]!=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value!=S[j].value]) + True + """ + return parent(self) is not parent(other) or self.value != other.value + + def __lt__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>S[j].value]) + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __ge__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]>=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>=S[j].value]) + True + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j,cmp(S[i],S[j])) for i in range(len(S)) for j in range(len(S))] + ....: == [(i,j,cmp(S[i].value,S[j].value)) for i in range(len(S)) for j in range(len(S))]) + True + """ + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 8c746d07ebc..95968325cad 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -36,11 +36,11 @@ from sage.structure.element import parent from sage.structure.global_options import GlobalOptions from sage.categories.category import Category +from sage.categories.cartesian_product import cartesian_product from sage.categories.classical_crystals import ClassicalCrystals from sage.categories.regular_crystals import RegularCrystals from sage.categories.sets_cat import Sets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.combinat import CombinatorialElement from sage.combinat.partition import Partition from sage.combinat.tableau import Tableau @@ -84,9 +84,9 @@ def _repr_(self): """ EXAMPLES:: - sage: from sage.combinat.crystals.tensor_product import TestParent - sage: TestParent() - A parent for tests + sage: from sage.combinat.crystals.tensor_product import TestParent + sage: TestParent() + A parent for tests """ return "A parent for tests" @@ -808,7 +808,7 @@ def __init__(self, crystals, **options): raise ValueError("you need to specify the Cartan type if the tensor product list is empty") else: self._cartan_type = crystals[0].cartan_type() - self.cartesian_product = CartesianProduct(*self.crystals) + self.cartesian_product = cartesian_product(self.crystals) self.module_generators = self def _repr_(self): @@ -1808,15 +1808,15 @@ def __classcall_private__(cls, cartan_type, shapes = None, shape = None): def __init__(self, cartan_type, shapes): """ - Construct the crystal of all tableaux of the given shapes + Construct the crystal of all tableaux of the given shapes. INPUT: - - ``cartan_type`` - (data coercible into) a Cartan type - - ``shapes`` - a list (or iterable) of shapes - - ``shape` ` - a shape + - ``cartan_type`` -- (data coercible into) a Cartan type + - ``shapes`` -- a list (or iterable) of shapes + - ``shape`` -- a shape - shapes themselves are lists (or iterable) of integers + Shapes themselves are lists (or iterable) of integers. EXAMPLES:: diff --git a/src/sage/combinat/crystals/virtual_crystal.py b/src/sage/combinat/crystals/virtual_crystal.py index eef6051c311..9f72c2f4291 100644 --- a/src/sage/combinat/crystals/virtual_crystal.py +++ b/src/sage/combinat/crystals/virtual_crystal.py @@ -168,6 +168,16 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, sage: V2 = psi2.image() sage: V1 is V2 True + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.Tableaux(['A',3], shape=[2,1,1]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.category() + Category of finite crystals """ if cartan_type is None: cartan_type = ambient.cartan_type() @@ -181,6 +191,8 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, scaling_factors = Family(scaling_factors) category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() return super(Subcrystal, cls).__classcall__(cls, ambient, virtualization, scaling_factors, contained, tuple(generators), cartan_type, diff --git a/src/sage/combinat/derangements.py b/src/sage/combinat/derangements.py index 733950ed1be..395187c82a5 100644 --- a/src/sage/combinat/derangements.py +++ b/src/sage/combinat/derangements.py @@ -70,7 +70,7 @@ def to_permutation(self): raise ValueError("Can only convert to a permutation for derangements of [1, 2, ..., n]") return Permutation(list(self)) -class Derangements(Parent, UniqueRepresentation): +class Derangements(UniqueRepresentation, Parent): r""" The class of all derangements of a set or multiset. diff --git a/src/sage/combinat/descent_algebra.py b/src/sage/combinat/descent_algebra.py index 235fd96fe01..6030acd213e 100644 --- a/src/sage/combinat/descent_algebra.py +++ b/src/sage/combinat/descent_algebra.py @@ -30,7 +30,7 @@ from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra from sage.combinat.ncsf_qsym.ncsf import NonCommutativeSymmetricFunctions -class DescentAlgebra(Parent, UniqueRepresentation): +class DescentAlgebra(UniqueRepresentation, Parent): r""" Solomon's descent algebra. diff --git a/src/sage/combinat/designs/__init__.py b/src/sage/combinat/designs/__init__.py index 58b3bb856d9..e049d59220a 100644 --- a/src/sage/combinat/designs/__init__.py +++ b/src/sage/combinat/designs/__init__.py @@ -25,6 +25,7 @@ - :ref:`sage.combinat.designs.difference_family` - :ref:`sage.combinat.designs.difference_matrices` - :ref:`sage.combinat.designs.steiner_quadruple_systems` +- :ref:`sage.combinat.designs.twographs` - :ref:`sage.combinat.designs.database` **Technical things** diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index f83e07ec611..3f2edba17e9 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -113,10 +113,10 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): [[0, 1, 2, 3, 4, 65], [0, 5, 24, 25, 39, 57], [0, 6, 27, 38, 44, 55], ... sage: designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet Incidence structure with 66 points and 143 blocks - sage: designs.balanced_incomplete_block_design(141, 6) + sage: designs.balanced_incomplete_block_design(216, 6) Traceback (most recent call last): ... - NotImplementedError: I don't know how to build a (141,6,1)-BIBD! + NotImplementedError: I don't know how to build a (216,6,1)-BIBD! TESTS:: @@ -146,10 +146,10 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): For `k > 5` there are currently very few constructions:: - sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is True] - [1, 6, 31, 91, 121] - sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is Unknown] - [51, 61, 66, 76, 81, 96, 106, 111, 126, 136, 141] + sage: [v for v in xrange(300) if designs.balanced_incomplete_block_design(v,6,existence=True) is True] + [1, 6, 31, 66, 76, 91, 96, 106, 111, 121, 126, 136, 141, 151, 156, 171, 181, 186, 196, 201, 211, 241, 271] + sage: [v for v in xrange(300) if designs.balanced_incomplete_block_design(v,6,existence=True) is Unknown] + [51, 61, 81, 166, 216, 226, 231, 246, 256, 261, 276, 286, 291] Here are some constructions with `k \geq 7` and `v` a prime power:: @@ -222,6 +222,11 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): if existence: return True return BlockDesign(v,BIBD_constructions[(v,k,1)](), copy=False) + if BIBD_from_arc_in_desarguesian_projective_plane(v,k,existence=True): + if existence: + return True + B = BIBD_from_arc_in_desarguesian_projective_plane(v,k) + return BalancedIncompleteBlockDesign(v, B, copy=False) if BIBD_from_TD(v,k,existence=True): if existence: return True @@ -395,21 +400,21 @@ def BIBD_from_TD(v,k,existence=False): sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(25,5,existence=True) True - sage: _ = designs.BlockDesign(25,BIBD_from_TD(25,5)) + sage: _ = BlockDesign(25,BIBD_from_TD(25,5)) Second construction:: sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(21,5,existence=True) True - sage: _ = designs.BlockDesign(21,BIBD_from_TD(21,5)) + sage: _ = BlockDesign(21,BIBD_from_TD(21,5)) Third construction:: sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(85,5,existence=True) True - sage: _ = designs.BlockDesign(85,BIBD_from_TD(85,5)) + sage: _ = BlockDesign(85,BIBD_from_TD(85,5)) No idea:: @@ -1102,6 +1107,114 @@ def BIBD_5q_5_for_q_prime_power(q): return B +def BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=False): + r""" + Returns a `(n,k,1)`-BIBD from a maximal arc in a projective plane. + + This function implements a construction from Denniston [Denniston69]_, who + describes a maximal :meth:`arc + ` in a + :func:`Desarguesian Projective Plane + ` of + order `2^k`. From two powers of two `n,q` with `n` + of the class + :class:`MixedIntegerLinearProgram `. + + - ``verbose`` -- integer (default: ``0``). Sets the level of + verbosity. Set to 0 by default, which means quiet. + + EXAMPLES:: + + sage: B = designs.balanced_incomplete_block_design(21, 5) + sage: a2 = B.arc() + sage: a2 # random + [5, 9, 10, 12, 15, 20] + sage: len(a2) + 6 + sage: a4 = B.arc(4) + sage: a4 # random + [0, 1, 2, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20] + sage: len(a4) + 16 + + The `2`-arc and `4`-arc above are maximal. One can check that they + intersect the blocks in either 0 or `s` points. Or equivalently that the + traces are again BIBD:: + + sage: r = (21-1)/(5-1) + sage: 1 + r*1 + 6 + sage: 1 + r*3 + 16 + + sage: B.trace(a2).is_t_design(2, return_parameters=True) + (True, (2, 6, 2, 1)) + sage: B.trace(a4).is_t_design(2, return_parameters=True) + (True, (2, 16, 4, 1)) + + Some other examples which are not maximal:: + + sage: B = designs.balanced_incomplete_block_design(25, 4) + sage: a2 = B.arc(2) + sage: r = (25-1)/(4-1) + sage: print len(a2), 1 + r + 8 9 + sage: sa2 = set(a2) + sage: set(len(sa2.intersection(b)) for b in B.blocks()) + {0, 1, 2} + sage: B.trace(a2).is_t_design(2) + False + + sage: a3 = B.arc(3) + sage: print len(a3), 1 + 2*r + 15 17 + sage: sa3 = set(a3) + sage: set(len(sa3.intersection(b)) for b in B.blocks()) == set([0,3]) + False + sage: B.trace(a3).is_t_design(3) + False + + TESTS: + + Test consistency with relabeling:: + + sage: b = designs.balanced_incomplete_block_design(7,3) + sage: b.relabel(list("abcdefg")) + sage: set(b.arc()).issubset(b.ground_set()) + True + """ + s = int(s) + + # trivial cases + if s <= 0: + return [] + elif s >= max(self.block_sizes()): + return self._points[:] + + # linear program + from sage.numerical.mip import MixedIntegerLinearProgram + + p = MixedIntegerLinearProgram(solver=solver) + b = p.new_variable(binary=True) + p.set_objective(p.sum(b[i] for i in range(len(self._points)))) + for i in self._blocks: + p.add_constraint(p.sum(b[k] for k in i) <= s) + p.solve(log=verbose) + return [self._points[i] for (i,j) in p.get_values(b).items() if j == 1] diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index 9fa37ad3ae6..523d25953de 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -78,7 +78,7 @@ def tdesign_params(t, v, k, L): EXAMPLES:: - sage: BD = designs.BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: from sage.combinat.designs.block_design import tdesign_params sage: tdesign_params(2,7,3,1) (2, 7, 7, 3, 3, 1) @@ -165,7 +165,7 @@ def are_hyperplanes_in_projective_geometry_parameters(v, k, lmbda, return_parame return (True, (q,d)) if return_parameters else True -def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): +def ProjectiveGeometryDesign(n, d, F, algorithm=None, point_coordinates=True, check=True): """ Return a projective geometry design. @@ -188,6 +188,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): GAP's "design" package must be available in this case, and that it can be installed with the ``gap_packages`` spkg. + - ``point_coordinates`` -- ``True`` by default. Ignored and assumed to be ``False`` if + ``algorithm="gap"``. If ``True``, the ground set is indexed by coordinates in `F^{n+1}`. + Otherwise the ground set is indexed by integers, + EXAMPLES: The set of `d`-dimensional subspaces in a `n`-dimensional projective space @@ -203,6 +207,18 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): sage: PG.is_t_design(return_parameters=True) (True, (2, 85, 5, 1)) + Use coordinates:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3)) + sage: PG.blocks()[0] + [(1, 0, 0), (1, 0, 1), (1, 0, 2), (0, 0, 1)] + + Use indexing by integers:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3),point_coordinates=0) + sage: PG.blocks()[0] + [0, 1, 2, 12] + Check that the constructor using gap also works:: sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) @@ -224,7 +240,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): m.set_immutable() b.append(points[m]) blocks.append(b) - return BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + B = BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + if point_coordinates: + B.relabel({i:p[0] for p,i in points.iteritems()}) + return B if algorithm == "gap": # Requires GAP's Design from sage.interfaces.gap import gap gap.load_package("design") @@ -236,7 +255,7 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): gB.append([x-1 for x in b]) return BlockDesign(v, gB, name="ProjectiveGeometryDesign", check=check) -def DesarguesianProjectivePlaneDesign(n, check=True): +def DesarguesianProjectivePlaneDesign(n, point_coordinates=True, check=True): r""" Return the Desarguesian projective plane of order ``n`` as a 2-design. @@ -248,6 +267,9 @@ def DesarguesianProjectivePlaneDesign(n, check=True): - ``n`` -- an integer which must be a power of a prime number + - ``point_coordinates`` (boolean) -- whether to label the points with their + homogeneous coordinates (default) or with integers. + - ``check`` -- (boolean) Whether to check that output is correct before returning it. As this is expected to be useless (but we are cautious guys), you may want to disable it whenever you want speed. Set to @@ -271,8 +293,9 @@ def DesarguesianProjectivePlaneDesign(n, check=True): Traceback (most recent call last): ... ValueError: the order of a finite field must be a prime power + """ - K = FiniteField(n, 'x') + K = FiniteField(n, 'a') n2 = n**2 relabel = {x:i for i,x in enumerate(K)} Kiter = relabel # it is much faster to iterate throug a dict than through @@ -316,7 +339,20 @@ def DesarguesianProjectivePlaneDesign(n, check=True): if not is_projective_plane(blcks): raise RuntimeError('There is a problem in the function DesarguesianProjectivePlane') from bibd import BalancedIncompleteBlockDesign - return BalancedIncompleteBlockDesign(n2+n+1, blcks, check=check) + B = BalancedIncompleteBlockDesign(n2+n+1, blcks, check=check) + + if point_coordinates: + zero = K.zero() + one = K.one() + d = {affine_plane(x,y): (x,y,one) + for x in Kiter + for y in Kiter} + d.update({line_infinity(x): (x,one,zero) + for x in Kiter}) + d[n2+n]=(one,zero,zero) + B.relabel(d) + + return B def q3_minus_one_matrix(K): r""" @@ -572,10 +608,10 @@ def projective_plane_to_OA(pplane, pt=None, check=True): EXAMPLES:: sage: from sage.combinat.designs.block_design import projective_plane_to_OA - sage: p2 = designs.DesarguesianProjectivePlaneDesign(2) + sage: p2 = designs.DesarguesianProjectivePlaneDesign(2,point_coordinates=False) sage: projective_plane_to_OA(p2) [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]] - sage: p3 = designs.DesarguesianProjectivePlaneDesign(3) + sage: p3 = designs.DesarguesianProjectivePlaneDesign(3,point_coordinates=False) sage: projective_plane_to_OA(p3) [[0, 0, 0, 0], [0, 1, 2, 1], @@ -587,7 +623,7 @@ def projective_plane_to_OA(pplane, pt=None, check=True): [2, 1, 0, 2], [2, 2, 2, 0]] - sage: pp = designs.DesarguesianProjectivePlaneDesign(16) + sage: pp = designs.DesarguesianProjectivePlaneDesign(16,point_coordinates=False) sage: _ = projective_plane_to_OA(pp, pt=0) sage: _ = projective_plane_to_OA(pp, pt=3) sage: _ = projective_plane_to_OA(pp, pt=7) @@ -697,7 +733,7 @@ def projective_plane(n, check=True, existence=False): if existence: return True else: - return DesarguesianProjectivePlaneDesign(n, check=check) + return DesarguesianProjectivePlaneDesign(n, point_coordinates=False, check=check) def AffineGeometryDesign(n, d, F): r""" @@ -760,6 +796,34 @@ def AffineGeometryDesign(n, d, F): gB.append([x-1 for x in b]) return BlockDesign(v, gB, name="AffineGeometryDesign") +def CremonaRichmondConfiguration(): + r""" + Return the Cremona-Richmond configuration + + The Cremona-Richmond configuration is a set system whose incidence graph + is equal to the + :meth:`~sage.graphs.graph_generators.GraphGenerators.TutteCoxeterGraph`. It + is a generalized quadrangle of parameters `(2,2)`. + + For more information, see the + :wikipedia:`Cremona-Richmond_configuration`. + + EXAMPLE:: + + sage: H = designs.CremonaRichmondConfiguration(); H + Incidence structure with 15 points and 15 blocks + sage: g = graphs.TutteCoxeterGraph() + sage: H.incidence_graph().is_isomorphic(g) + True + """ + from sage.graphs.generators.smallgraphs import TutteCoxeterGraph + from sage.combinat.designs.incidence_structures import IncidenceStructure + g = TutteCoxeterGraph() + H = IncidenceStructure([g.neighbors(v) + for v in g.bipartite_sets()[0]]) + H.relabel() + return H + def WittDesign(n): """ INPUT: diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index bd268a58618..e2ad23153ce 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -21,6 +21,9 @@ - :func:`RBIBD(120,8,1) ` +- `(v,k,\lambda)`-BIBD: +{LIST_OF_BIBD} + - `(v,k,\lambda)`-difference families: {LIST_OF_DF} @@ -2788,6 +2791,10 @@ def QDM_57_9_1_1_8(): [0,11,31,35],[0,12,26,34,],[0,5,30,33]]}, ( 91, 6, 1): {(91,): [[0,1,3,7,25,38], [0,16,21,36,48,62], [0,30,40,63,74,82]]}, + +( 91, 7, 1): # from the La Jolla covering repository, attributed to Jan de Heer and Steve Muir + {(91,): [[8, 9, 14, 25, 58, 81, 85], [5, 33, 35, 42, 45, 67, 88], [4, 17, 30, 43, 56, 69, 82]]}, + (121, 5, 1): {(121,): [[0,14,26,51,60],[0,15,31,55,59],[0,10,23,52,58], [0,3,36,56,57],[0,7,18,45,50],[0,8,30,47,49]]}, @@ -4114,6 +4121,438 @@ def RBIBD_120_8_1(): equiv = [[M.nonzero_positions_in_row(x) for x in S] for S in equiv] return [B for S in equiv for B in S] +def BIBD_45_9_8(from_code=False): + r""" + Return a `(45,9,1)`-BIBD. + + This BIBD is obtained from the codewords of minimal weight in the + :func:`~sage.coding.code_constructions.ExtendedQuadraticResidueCode` of + length 48. This construction appears in VII.11.2 from [DesignHandbook]_, + which cites [HT95]_. + + INPUT: + + - ``from_code`` (boolean) -- whether to build the design from hardcoded data + (default) or from the code object (much longer). + + EXAMPLE:: + + sage: from sage.combinat.designs.database import BIBD_45_9_8 + sage: from sage.combinat.designs.bibd import BalancedIncompleteBlockDesign + sage: B = BalancedIncompleteBlockDesign(45, BIBD_45_9_8(),lambd=8); B + (45,9,8)-Balanced Incomplete Block Design + + TESTS: + + From the definition (takes around 12s):: + + sage: B2 = Hypergraph(BIBD_45_9_8(from_code=True)) # not tested + sage: B2.is_isomorphic(B) # not tested + True + + REFERENCE: + + .. [HT95] W. Huffman and V. Tonchev, + The existence of extremal self-dual `[50, 25, 10]` codes and + quasi-symmetric `2-(49, 9, 6)` designs, + Designs, Codes and Cryptography + September 1995, Volume 6, Issue 2, pp 97-106 + """ + if from_code: + from sage.coding.code_constructions import ExtendedQuadraticResidueCode + from sage.rings.finite_rings.constructor import FiniteField + C = ExtendedQuadraticResidueCode(47,FiniteField(2)) + min_weight = [map(int,x)[3:] for x in C + if x.hamming_weight() == 12 and + x[0]==1 and x[1]==1 and x[2]==1] + + return [[i for i,v in enumerate(x) if v] for x in min_weight] + + from sage.rings.integer import Integer + B = ['acs1v', 'l8lsx', '4ga1vw', '6q9amr', 'nb3ui8', 'sgjocw', '11vsoy2', '28791ts', '30tm1z8', '38ktnwh', + '3saz8jk', '41qkwme', '4g3jxmt', '56qhwuc', '711w45k', '8nz2gx4', '903uha8', '957z8dc', '9wejz7k', 'fs905ic', + 'ftzzh28', 'gb4g448', 'hvreal0', 'nqlhxu8', 'rmluazm', 'vlyqayx', 'w52detk', 'zisjk02', 'zw9811c', '10i7qfl1', + '13ibtse8', '1rbsbvvc', '1sdy0o5c', '1z14s09e', '2nbz5a80', '2uuhib2a', '2wkn4r9d', '3iaaat5w', '3iiwq53s', + '3j9ubv43', '3mpxpngz', '3qamndc0', '3saomh3t', '3uhhi5cw', '4334rx4x', '4dxy3xts', '4tn9w2z1', '4vlr2h00', + '59f1meqm', '59h6udc1', '5cep4nc0', '5ddcxsw2', '70msua7k', '70ofjm82', '70p8jig0', '721o664h', '72jutmfk', + '74jowaad', '78ihrfgo', '7meufihs', '7wv5mtxj', '84akgj0w', '8m9vyb60', '8s0c6p04', '8soi6m8g', '9kawy0ow', + 'awnpg9a8', 'biu8xww0', 'e1lptwxx', 'e79x2we8', 'eh0t1q9y', 'eh65daci', 'ehxytwjk', 'extc1udk', 'f4toqhpg', + 'fgeqg214', 'ftiem9lk', 'fw77kcnc', 'h5kt9cf4', 'hjwhwym8', 'hz8d60xs', 'jb6bp0g0', 'l22bzw1w', 'l3pj9hq8', + 'lbj1fubp', 'lxal1lk2', 's27vq70q', 's2bb5mki', 's2w95y0w', 's3cek9og', 's4703jk4', 's67g5qf5', 's8kgdkat', + 'sckruupw', 'se4vzkao', 'si57d0vl', 'sjhd20i8', 'sqne2mf6', 'sxtju9ds', 'ttd710kw', 'ttkayw5e', 'u96baslc', + 'vtdhrbj5', 'y79i706c', 'zycu7tsa', '10uwf8sh4', '11boo6mmc', '12sxyeebs', '163xyccg3', '16cpesdfk', + '18q18bpc0', '1k4hvvgq4', '1k5f63ok4', '1k5olig3u', '1k6fsqalm', '1kacr2gi8', '1kcc6rzu1', '1kkpot632', + '1kwdghpts', '1l2644l68', '1l3yxmj9s', '1m04wgmyo', '1mtm16z5s', '1np6u1q0w', '1nuo1tbfk', '1oy4n1mo0', + '1r5lsxju0', '1sx57vdfq', '1v4j675ds', '1y5oldkzm', '1ydfr4jno', '1ylc38ah4', '1z14mw0td', '223vcx1xc', + '26xq9hn29', '2c7wa6r0w', '2cbc8qbcw', '2jn9ojll5', '2qjlkoz69', '2tr1zn5ds', '348vfurgh', '348vlaoc0', + '348ynt0qx', '34ahl37ds', '34b3cgc8y', '34ooa1ix0', '34r4ejl82', '35p5m8r28', '360i7uazl', '36289j761', + '3650mzlzg', '36aev2c00', '36noxmex2', '36vlw3k3k', '37rw4rghs', '37t554ikq', '387avhseb', '3b9o5lbwi', + '3ewmteale', '3ibz0r8n4', '3id5iv5ky', '3ihxwcvvc', '3k5k1k174', '3pau9ujnl', '3wf1e2dck', '43rfm4du8', + '47pqff6yo', '4e2i4y684', '4hio30v0o', '4odb0lr5s', '4odcmkvt0', '4p94elixc', '4p9zffz0k', '4qciqf9mp', + '4ywafln9c', '5hf4nw08w', '68ijggco4', '68jq73cxs', '68maap98g', '68prdfhqg', '68qm8divl', '691ibd2ps', + '69dbnd8ur', '69esd0djg', '69w6eo0sh', '6ad6zcetk', '6aonwwkjk', '6aozhe8zl', '6cvyitslw', '6dr7i6olg', + '6fibvzxtw', '6fmd4bv28', '6gmqtkr9e', '6j14n6n7k', '6miukvtc1', '6mjvifon4', '6mormb3fm', '6mr9hvhna', + '6q533lm6w', '6rsie7cbk', '6tjgpxic0', '70k7ao9m0', '7103zqlvk', '71i1x52bm', '7447g0dfw', '7sogja9z4', + '7up5z9m9u', '7w7esu6fm', '7zmqtlrpd', '81tsbnzsw', '8kofgi1he', '8mhi35nc1', '9cv1pjiaw', '9d6ef1dah', + '9dftsor9c', '9du8c1vcw', '9jr5vsnj4', 'a8b405mps', 'ajqhmxkj4', 'ax2xsvfic'] + B = [Integer(x,base=36) for x in B] + return [[i for i in range(45) if x&(1<` `W` on 24 points. We define + two points `a,b`, and consider: + + - The collection `W_a` of all blocks of `W` containing `a` but not + containing `b`. + + - The collection `W_b` of all blocks of `W` containing `b` but not + containing `a`. + + The design is then obtained from the incidence structure produced by the + blocks `A\in W_a` and `B\in W_b` whose intersection has cardinality 2. This + construction, due to M.Smith, can be found in [KY04]_ or in 10.A.(v) of [BvL84]_. + + EXAMPLE:: + + sage: H = designs.HigmanSimsDesign(); H # optional - gap_packages + Incidence structure with 176 points and 176 blocks + sage: H.is_t_design(return_parameters=1) # optional - gap_packages + (True, (2, 176, 50, 14)) + + Make sure that the automorphism group of this designs is isomorphic to the + automorphism group of the + :func:`~sage.graphs.generators.smallgraphs.HigmanSimsGraph`. Note that the + first of those permutation groups acts on 176 points, while the second acts + on 100:: + + sage: gH = H.automorphism_group() # optional - gap_packages + sage: gG = graphs.HigmanSimsGraph().automorphism_group() # optional - gap_packages + sage: gG.is_isomorphic(gG) # long time # optional - gap_packages + True + + REFERENCE: + + .. [KY04] S. Klee and L. Yates, + Tight Subdesigns of the Higman-Sims Design, + Rose-Hulman Undergraduate Math. J 5.2 (2004). + https://www.rose-hulman.edu/mathjournal/archives/2004/vol5-n2/paper9/v5n2-9pd.pdf + """ + from sage.combinat.designs.block_design import WittDesign + from incidence_structures import IncidenceStructure + W = WittDesign(24) + a,b = 0,1 + Wa = [set(B) for B in W + if (a in B and + b not in B)] + Wb = [set(B) for B in W + if (b in B and + a not in B)] + + H = [[i for i,A in enumerate(Wa) if len(A&B) != 2] + for B in Wb] + + H = IncidenceStructure(H) + + return H + +def BIBD_196_6_1(): + r""" + Return a (196,6,1)-BIBD. + + This constructions appears in II.3.32 from [DesignHandbook]_. + + EXAMPLE:: + + sage: from sage.combinat.designs.database import BIBD_196_6_1 + sage: from sage.combinat.designs.bibd import BalancedIncompleteBlockDesign + sage: BalancedIncompleteBlockDesign(196, BIBD_196_6_1()) + (196,6,1)-Balanced Incomplete Block Design + """ + from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet + from incidence_structures import IncidenceStructure + a = 'a' + bibd = [((0,0), ( 2,0), (12,0), (45,0), ( 3,1), (11,a)), + ((0,0), ( 3,0), ( 8,0), ( 5,1), (17,1), (39,a)), + ((0,0), ( 9,0), (36,0), (24,1), (44,1), (37,a)), + ((0,0), (15,0), (34,1), (41,1), (47,2), (18,a)), + ((0,0), ( 7,0), (31,0), (13,1), (35,2), (41,a)), + ((0,0), (14,0), (32,1), (10,2), (22,a), (44,a)), + ((0,0), (23,0), (21,1), (39,1), (19,a), (25,a)), + ((0,0), (33,1), ( 0,a), ( 5,a), (29,a), (47,a)), + ((0,0), ( 1,0), ( 0,1), (30,1), ( 0,2), (18,2)), + ((8,0), (19,0), (44,1), (31,1), (46,2), (48,2))] + + gens = lambda B: [frozenset(((x*30)%49,(y+1)%3 if y!=a else a) for x,y in B), + frozenset(((x+1) %49, y) for x,y in B)] + bibd = RecursivelyEnumeratedSet(map(frozenset,bibd), successors=gens) + return IncidenceStructure(bibd)._blocks + +def BIBD_201_6_1(): + r""" + Return a (201,6,1)-BIBD. + + This constructions appears in II.3.32 from [DesignHandbook]_. + + EXAMPLE:: + + sage: from sage.combinat.designs.database import BIBD_201_6_1 + sage: from sage.combinat.designs.bibd import BalancedIncompleteBlockDesign + sage: BalancedIncompleteBlockDesign(201, BIBD_201_6_1()) + (201,6,1)-Balanced Incomplete Block Design + """ + from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet + from incidence_structures import IncidenceStructure + bibd = [((0,0), ( 1,0), ( 4,2), ( 9,2), (34,2), (62,2)), + ((0,1), ( 2,1), (15,1), ( 8,2), (27,2), (49,2)), + ((0,0), ( 3,0), (22,0), (54,1), (13,2), (40,2)), + ((0,0), (36,0), (40,0), (31,1), (34,1), ( 5,2)), + ((0,0), (50,0), (55,0), ( 6,1), (24,1), (26,2)), + ((0,0), ( 2,0), ( 3,1), (14,1), (35,1), (25,2)), + ((3,1), (20,1), (44,1), (36,2), (39,2), (59,2)), + ((0,0), ( 0,1), (30,1), (38,1), (66,1), ( 0,2))] + + gens = lambda B: [frozenset(((x*29)%67,y) for x,y in B), + frozenset(((x+1) %67,y) for x,y in B)] + bibd = RecursivelyEnumeratedSet(map(frozenset,bibd), successors=gens) + return IncidenceStructure(bibd)._blocks + # Index of the BIBD constructions # # Associates to triple (v,k,lambda) a function that return a @@ -4122,9 +4561,28 @@ def RBIBD_120_8_1(): # This dictionary is used by designs.BalancedIncompleteBlockDesign BIBD_constructions = { + ( 45,9,8): BIBD_45_9_8, + ( 66,6,1): BIBD_66_6_1, + ( 76,6,1): BIBD_76_6_1, + ( 96,6,1): BIBD_96_6_1, (120,8,1): RBIBD_120_8_1, + (106,6,1): BIBD_106_6_1, + (111,6,1): BIBD_111_6_1, + (126,6,1): BIBD_126_6_1, + (136,6,1): BIBD_136_6_1, + (141,6,1): BIBD_141_6_1, + (171,6,1): BIBD_171_6_1, + (176,50,14): HigmanSimsDesign, + (196,6,1): BIBD_196_6_1, + (201,6,1): BIBD_201_6_1, } +# Create the list of DF for the documentation +_all_l = sorted(set(l for v,k,l in BIBD_constructions.keys())) +LIST_OF_BIBD = "\n".join(" - `\lambda={}`:\n ".format(l) + + ", ".join("`({},{},{})`".format(v,k,l) for v,k,_ in sorted(BIBD_constructions) if _ == l) + for l in _all_l) + # Evenly Distributed Sets (EDS) # # For the definition see the documentation of the class @@ -4417,9 +4875,10 @@ def RBIBD_120_8_1(): LIST_OF_OA_CONSTRUCTIONS = LIST_OF_OA_CONSTRUCTIONS, LIST_OF_MOLS_CONSTRUCTIONS = LIST_OF_MOLS_CONSTRUCTIONS, LIST_OF_VMT_VECTORS = LIST_OF_VMT_VECTORS, + LIST_OF_BIBD = LIST_OF_BIBD, LIST_OF_DF = LIST_OF_DF, LIST_OF_DM = LIST_OF_DM, LIST_OF_QDM = LIST_OF_QDM, LIST_OF_EDS = LIST_OF_EDS) -del LIST_OF_OA_CONSTRUCTIONS, LIST_OF_MOLS_CONSTRUCTIONS, LIST_OF_VMT_VECTORS,LIST_OF_DF, LIST_OF_DM, LIST_OF_QDM, LIST_OF_EDS +del LIST_OF_OA_CONSTRUCTIONS, LIST_OF_MOLS_CONSTRUCTIONS, LIST_OF_VMT_VECTORS,LIST_OF_DF, LIST_OF_DM, LIST_OF_QDM, LIST_OF_EDS, LIST_OF_BIBD del PolynomialRing, ZZ, a, diff --git a/src/sage/combinat/designs/design_catalog.py b/src/sage/combinat/designs/design_catalog.py index e35bee6e2ae..69f34ad615c 100644 --- a/src/sage/combinat/designs/design_catalog.py +++ b/src/sage/combinat/designs/design_catalog.py @@ -40,10 +40,12 @@ :meth:`~sage.combinat.designs.block_design.ProjectiveGeometryDesign` :meth:`~sage.combinat.designs.block_design.DesarguesianProjectivePlaneDesign` :meth:`~sage.combinat.designs.block_design.HughesPlane` + :meth:`~sage.combinat.designs.database.HigmanSimsDesign` :meth:`~sage.combinat.designs.bibd.balanced_incomplete_block_design` :meth:`~sage.combinat.designs.resolvable_bibd.resolvable_balanced_incomplete_block_design` :meth:`~sage.combinat.designs.resolvable_bibd.kirkman_triple_system` :meth:`~sage.combinat.designs.block_design.AffineGeometryDesign` + :meth:`~sage.combinat.designs.block_design.CremonaRichmondConfiguration` :meth:`~sage.combinat.designs.block_design.WittDesign` :meth:`~sage.combinat.designs.block_design.HadamardDesign` :meth:`~sage.combinat.designs.block_design.Hadamard3Design` @@ -78,7 +80,10 @@ WittDesign, HadamardDesign, Hadamard3Design, - HughesPlane) + HughesPlane, + CremonaRichmondConfiguration) + +from database import HigmanSimsDesign from sage.combinat.designs.steiner_quadruple_systems import steiner_quadruple_system @@ -92,14 +97,20 @@ from sage.combinat.designs.difference_family import difference_family from difference_matrices import difference_matrix -from sage.combinat.designs.incidence_structures import IncidenceStructure +from sage.misc.superseded import deprecated_function_alias, deprecated_callable_import +deprecated_callable_import(19096, + 'sage.combinat.designs.incidence_structures', + globals(), + locals(), + ["IncidenceStructure"], + ("This alias will soon be removed. You can call the same object by removing 'designs.' in your command")) + Hypergraph = BlockDesign = IncidenceStructure # just an alias from sage.combinat.designs.bibd import balanced_incomplete_block_design, steiner_triple_system from sage.combinat.designs.resolvable_bibd import resolvable_balanced_incomplete_block_design, kirkman_triple_system from sage.combinat.designs.group_divisible_designs import group_divisible_design # deprecated in june 2014 (#16446) -from sage.misc.superseded import deprecated_function_alias, deprecated_callable_import BalancedIncompleteBlockDesign = deprecated_function_alias(16446, balanced_incomplete_block_design) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 9e79cd02f49..7048b9e2447 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1139,7 +1139,7 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch 83: (2,1) 85: (4,1), (7,2), (7,3), (8,2) 89: (2,1), (4,3), (8,7) - 91: (6,1) + 91: (6,1), (7,1) 97: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,3) TESTS: diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index 6d670a62307..56e822814e7 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -52,7 +52,7 @@ def IncidenceStructureFromMatrix(M, name=None): """ Deprecated function that builds an incidence structure from a matrix. - You should now use ``designs.IncidenceStructure(incidence_matrix=M)``. + You should now use ``IncidenceStructure(incidence_matrix=M)``. INPUT: @@ -61,17 +61,17 @@ def IncidenceStructureFromMatrix(M, name=None): EXAMPLES:: - sage: BD1 = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD1 = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: M = BD1.incidence_matrix() sage: BD2 = IncidenceStructureFromMatrix(M) doctest:...: DeprecationWarning: IncidenceStructureFromMatrix is deprecated. - Please use designs.IncidenceStructure(incidence_matrix=M) instead. + Please use IncidenceStructure(incidence_matrix=M) instead. See http://trac.sagemath.org/16553 for details. sage: BD1 == BD2 True """ from sage.misc.superseded import deprecation - deprecation(16553, 'IncidenceStructureFromMatrix is deprecated. Please use designs.IncidenceStructure(incidence_matrix=M) instead.') + deprecation(16553, 'IncidenceStructureFromMatrix is deprecated. Please use IncidenceStructure(incidence_matrix=M) instead.') return IncidenceStructure(incidence_matrix=M, name=name) class IncidenceStructure(object): @@ -119,7 +119,7 @@ class IncidenceStructure(object): An incidence structure can be constructed by giving the number of points and the list of blocks:: - sage: designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) Incidence structure with 7 points and 7 blocks Only providing the set of blocks is sufficient. In this case, the ground set @@ -132,14 +132,14 @@ class IncidenceStructure(object): points and columns by blocks):: sage: m = matrix([[0,1,0],[0,0,1],[1,0,1],[1,1,1]]) - sage: designs.IncidenceStructure(m) + sage: IncidenceStructure(m) Incidence structure with 4 points and 3 blocks The points can be any (hashable) object:: sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')] sage: B = [(V[0],V[1],V[2]), (V[1],V[2]), (V[0],V[2])] - sage: I = designs.IncidenceStructure(V, B) + sage: I = IncidenceStructure(V, B) sage: I.ground_set() [(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')] sage: I.blocks() @@ -148,13 +148,13 @@ class IncidenceStructure(object): The order of the points and blocks does not matter as they are sorted on input (see :trac:`11333`):: - sage: A = designs.IncidenceStructure([0,1,2], [[0],[0,2]]) - sage: B = designs.IncidenceStructure([1,0,2], [[0],[2,0]]) + sage: A = IncidenceStructure([0,1,2], [[0],[0,2]]) + sage: B = IncidenceStructure([1,0,2], [[0],[2,0]]) sage: B == A True - sage: C = designs.BlockDesign(2, [[0], [1,0]]) - sage: D = designs.BlockDesign(2, [[0,1], [0]]) + sage: C = BlockDesign(2, [[0], [1,0]]) + sage: D = BlockDesign(2, [[0,1], [0]]) sage: C == D True @@ -163,7 +163,7 @@ class IncidenceStructure(object): ..., v-1}`:: sage: blocks = [[0,1],[2,0],[1,2]] # a list of lists of integers - sage: I = designs.IncidenceStructure(3, blocks, copy=False) + sage: I = IncidenceStructure(3, blocks, copy=False) sage: I._blocks is blocks True """ @@ -172,18 +172,18 @@ def __init__(self, points=None, blocks=None, incidence_matrix=None, r""" TESTS:: - sage: designs.IncidenceStructure(3, [[4]]) + sage: IncidenceStructure(3, [[4]]) Traceback (most recent call last): ... ValueError: Block [4] is not contained in the point set - sage: designs.IncidenceStructure(3, [[0,1],[0,2]], test=True) + sage: IncidenceStructure(3, [[0,1],[0,2]], test=True) doctest:...: DeprecationWarning: the keyword test is deprecated, use check instead See http://trac.sagemath.org/16553 for details. Incidence structure with 3 points and 2 blocks - sage: designs.IncidenceStructure(2, [[0,1,2,3,4,5]], test=False) + sage: IncidenceStructure(2, [[0,1,2,3,4,5]], test=False) Incidence structure with 2 points and 1 blocks We avoid to convert to integers when the points are not (but compare @@ -194,7 +194,7 @@ def __init__(self, points=None, blocks=None, incidence_matrix=None, sage: [e0,e1,e2,e3,e4] == range(5) # coercion makes them equal True sage: blocks = [[e0,e1,e2],[e0,e1],[e2,e4]] - sage: I = designs.IncidenceStructure(V, blocks) + sage: I = IncidenceStructure(V, blocks) sage: type(I.ground_set()[0]) sage: type(I.blocks()[0][0]) @@ -280,7 +280,7 @@ def __iter__(self): [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] - sage: b = designs.IncidenceStructure('ab', ['a','ab']) + sage: b = IncidenceStructure('ab', ['a','ab']) sage: it = iter(b) sage: next(it) ['a'] @@ -300,7 +300,7 @@ def __repr__(self): EXAMPLES:: - sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD Incidence structure with 7 points and 7 blocks """ @@ -316,9 +316,9 @@ def __eq__(self, other): TESTS:: sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]] - sage: BD1 = designs.IncidenceStructure(7, blocks) + sage: BD1 = IncidenceStructure(7, blocks) sage: M = BD1.incidence_matrix() - sage: BD2 = designs.IncidenceStructure(incidence_matrix=M) + sage: BD2 = IncidenceStructure(incidence_matrix=M) sage: BD1 == BD2 True @@ -328,9 +328,9 @@ def __eq__(self, other): True sage: sorted([e2,e1]) == [e2,e1] True - sage: I1 = designs.IncidenceStructure([e1,e2], [[e1],[e1,e2]]) - sage: I2 = designs.IncidenceStructure([e1,e2], [[e2,e1],[e1]]) - sage: I3 = designs.IncidenceStructure([e2,e1], [[e1,e2],[e1]]) + sage: I1 = IncidenceStructure([e1,e2], [[e1],[e1,e2]]) + sage: I2 = IncidenceStructure([e1,e2], [[e2,e1],[e1]]) + sage: I3 = IncidenceStructure([e2,e1], [[e1,e2],[e1]]) sage: I1 == I2 and I2 == I1 and I1 == I3 and I3 == I1 and I2 == I3 and I3 == I2 True """ @@ -360,9 +360,9 @@ def __ne__(self, other): EXAMPLES:: - sage: BD1 = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD1 = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: M = BD1.incidence_matrix() - sage: BD2 = designs.IncidenceStructure(incidence_matrix=M) + sage: BD2 = IncidenceStructure(incidence_matrix=M) sage: BD1 != BD2 False """ @@ -398,7 +398,7 @@ def __contains__(self, block): True sage: ["Am", "I", "finally", "done ?"] in IS False - sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2)) + sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2), point_coordinates=False) sage: [3,8,7] in IS True sage: [3,8,9] in IS @@ -759,7 +759,7 @@ def ground_set(self): EXAMPLES:: - sage: designs.IncidenceStructure(3, [[0,1],[0,2]]).ground_set() + sage: IncidenceStructure(3, [[0,1],[0,2]]).ground_set() [0, 1, 2] """ return self._points[:] @@ -772,7 +772,7 @@ def num_points(self): sage: designs.DesarguesianProjectivePlaneDesign(2).num_points() 7 - sage: B = designs.IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) + sage: B = IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) sage: B.num_points() 4 """ @@ -786,7 +786,7 @@ def num_blocks(self): sage: designs.DesarguesianProjectivePlaneDesign(2).num_blocks() 7 - sage: B = designs.IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) + sage: B = IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) sage: B.num_blocks() 5 """ @@ -798,7 +798,7 @@ def blocks(self): EXAMPLES:: - sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.blocks() [[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]] @@ -814,10 +814,10 @@ def block_sizes(self): EXAMPLES:: - sage: BD = designs.IncidenceStructure(8, [[0,1,3],[1,4,5,6],[1,2],[5,6,7]]) + sage: BD = IncidenceStructure(8, [[0,1,3],[1,4,5,6],[1,2],[5,6,7]]) sage: BD.block_sizes() [3, 2, 4, 3] - sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.block_sizes() [3, 3, 3, 3, 3, 3, 3] """ @@ -934,15 +934,115 @@ def degrees(self, size=None): else: return d + def is_regular(self,r=None): + r""" + Test whether the incidence structure is `r`-regular. + + An incidence structure is said to be `r`-regular if all its points are + incident with exactly `r` blocks. + + INPUT: + + - ``r`` (integer) + + OUTPUT: + + If ``r`` is defined, a boolean is returned. If ``r`` is set to ``None`` + (default), the method returns either ``False`` or the integer ``r`` such + that the incidence structure is `r`-regular. + + .. WARNING:: + + In case of `0`-regular incidence structure, beware that ``if not + H.is_regular()`` is a satisfied condition. + + EXAMPLES:: + + sage: designs.balanced_incomplete_block_design(7,3).is_regular() + 3 + sage: designs.balanced_incomplete_block_design(7,3).is_regular(r=3) + True + sage: designs.balanced_incomplete_block_design(7,3).is_regular(r=4) + False + + TESTS:: + + sage: IncidenceStructure([]).is_regular() + Traceback (most recent call last): + ... + ValueError: This incidence structure has no points. + """ + if self.num_points() == 0: + raise ValueError("This incidence structure has no points.") + count = [0]*self.num_points() + for b in self._blocks: + for x in b: + count[x] += 1 + count = set(count) + if len(count) != 1: + return False + elif r is None: + return count.pop() + else: + return count.pop() == r + + def is_uniform(self,k=None): + r""" + Test whether the incidence structure is `k`-uniform + + An incidence structure is said to be `k`-uniform if all its blocks have + size `k`. + + INPUT: + + - ``k`` (integer) + + OUTPUT: + + If ``k`` is defined, a boolean is returned. If ``k`` is set to ``None`` + (default), the method returns either ``False`` or the integer ``k`` such + that the incidence structure is `r`-regular. + + .. WARNING:: + + In case of `0`-uniform incidence structure, beware that ``if not + H.is_uniform()`` is a satisfied condition. + + EXAMPLES:: + + sage: designs.balanced_incomplete_block_design(7,3).is_uniform() + 3 + sage: designs.balanced_incomplete_block_design(7,3).is_uniform(k=3) + True + sage: designs.balanced_incomplete_block_design(7,3).is_uniform(k=4) + False + + TESTS:: + + sage: IncidenceStructure([]).is_regular() + Traceback (most recent call last): + ... + ValueError: This incidence structure has no points. + """ + if self.num_blocks() == 0: + raise ValueError("This incidence structure has no blocks.") + sizes = set(self.block_sizes()) + if len(sizes) != 1: + return False + elif k is None: + return sizes.pop() + else: + return sizes.pop() == k + def is_connected(self): r""" Test whether the design is connected. EXAMPLES:: - sage: designs.IncidenceStructure(3, [[0,1],[0,2]]).is_connected() + sage: IncidenceStructure(3, [[0,1],[0,2]]).is_connected() True - sage: designs.IncidenceStructure(4, [[0,1],[2,3]]).is_connected() + sage: IncidenceStructure(4, [[0,1],[2,3]]).is_connected() False """ from sage.sets.disjoint_set import DisjointSet @@ -959,17 +1059,17 @@ def is_simple(self): EXAMPLES:: - sage: designs.IncidenceStructure(3, [[0,1],[1,2],[0,2]]).is_simple() + sage: IncidenceStructure(3, [[0,1],[1,2],[0,2]]).is_simple() True - sage: designs.IncidenceStructure(3, [[0],[0]]).is_simple() + sage: IncidenceStructure(3, [[0],[0]]).is_simple() False sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')] sage: B = [[V[0],V[1]], [V[1],V[2]]] - sage: I = designs.IncidenceStructure(V, B) + sage: I = IncidenceStructure(V, B) sage: I.is_simple() True - sage: I2 = designs.IncidenceStructure(V, B*2) + sage: I2 = IncidenceStructure(V, B*2) sage: I2.is_simple() False """ @@ -982,7 +1082,7 @@ def _gap_(self): EXAMPLES:: - sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD._gap_() 'BlockDesign(7,[[1, 2, 3], [1, 4, 5], [1, 6, 7], [2, 4, 6], [2, 5, 7], [3, 4, 7], [3, 5, 6]])' """ @@ -991,6 +1091,42 @@ def _gap_(self): gB = [[x+1 for x in b] for b in self._blocks] return "BlockDesign("+str(v)+","+str(gB)+")" + def intersection_graph(self,sizes=None): + r""" + Return the intersection graph of the incidence structure. + + The vertices of this graph are the :meth:`blocks` of the incidence + structure. Two of them are adjacent if the size of their intersection + belongs to the set ``sizes``. + + INPUT: + + - ``sizes`` -- a list/set of integers. For convenience, setting + ``sizes`` to ``5`` has the same effect as ``sizes=[5]``. When set to + ``None`` (default), behaves as ``sizes=PositiveIntegers()``. + + EXAMPLE: + + The intersection graph of a + :func:`~sage.combinat.designs.bibd.balanced_incomplete_block_design` is + a :meth:`strongly regular graph ` (when it is + not trivial):: + + sage: BIBD = designs.balanced_incomplete_block_design(19,3) + sage: G = BIBD.intersection_graph(1) + sage: G.is_strongly_regular(parameters=True) + (57, 24, 11, 9) + """ + from sage.sets.positive_integers import PositiveIntegers + from sage.graphs.graph import Graph + from sage.sets.set import Set + if sizes is None: + sizes = PositiveIntegers() + elif sizes in PositiveIntegers(): + sizes = (sizes,) + V = map(Set,self) + return Graph([V,lambda x,y: len(x&y) in sizes],loops=False) + def incidence_matrix(self): r""" Return the incidence matrix `A` of the design. A is a `(v \times b)` @@ -999,7 +1135,7 @@ def incidence_matrix(self): EXAMPLES:: - sage: BD = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.block_sizes() [3, 3, 3, 3, 3, 3, 3] sage: BD.incidence_matrix() @@ -1011,7 +1147,7 @@ def incidence_matrix(self): [0 0 1 1 0 0 1] [0 0 1 0 1 1 0] - sage: I = designs.IncidenceStructure('abc', ('ab','abc','ac','c')) + sage: I = IncidenceStructure('abc', ('ab','abc','ac','c')) sage: I.incidence_matrix() [1 1 1 0] [1 1 0 0] @@ -1025,27 +1161,134 @@ def incidence_matrix(self): A[i, j] = 1 return A - def incidence_graph(self): - """ - Return the incidence graph of the design, where the incidence - matrix of the design is the adjacency matrix of the graph. + def incidence_graph(self,labels=False): + r""" + Return the incidence graph of the incidence structure + + A point and a block are adjacent in this graph whenever they are + incident. + + INPUT: + + - ``labels`` (boolean) -- whether to return a graph whose vertices are + integers, or labelled elements. + + - ``labels is False`` (default) -- in this case the first vertices + of the graphs are the elements of :meth:`ground_set`, and appear + in the same order. Similarly, the following vertices represent the + elements of :meth:`blocks`, and appear in the same order. + + - ``labels is True``, the points keep their original labels, and the + blocks are :func:`Set ` objects. + + Note that the labelled incidence graph can be incorrect when + blocks are repeated, and on some (rare) occasions when the + elements of :meth:`ground_set` mix :func:`Set` and non-:func:`Set + ` objects. EXAMPLE:: - sage: BD = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.incidence_graph() Bipartite graph on 14 vertices sage: A = BD.incidence_matrix() sage: Graph(block_matrix([[A*0,A],[A.transpose(),A*0]])) == BD.incidence_graph() True - REFERENCE: + TESTS: + + With ``labels = True``:: + + sage: BD.incidence_graph(labels=True).has_edge(0,Set([0,1,2])) + True + """ + if labels: + from sage.graphs.graph import Graph + from sage.sets.set import Set + G = Graph() + G.add_vertices(self.ground_set()) + for b in self.blocks(): + b = Set(b) + G.add_vertex(b) + G.add_edges((b,x) for x in b) + return G + + else: + from sage.graphs.bipartite_graph import BipartiteGraph + A = self.incidence_matrix() + return BipartiteGraph(A) + + def complement(self,uniform=False): + r""" + Return the complement of the incidence structure. + + Two different definitions of "complement" are made available, according + to the value of ``uniform``. + + INPUT: + + - ``uniform`` (boolean) -- + + - if set to ``False`` (default), returns the incidence structure whose + blocks are the complements of all blocks of the incidence structure. - - Sage Reference Manual on Graphs + - If set to ``True`` and the incidence structure is `k`-uniform, + returns the incidence structure whose blocks are all `k`-sets of the + ground set that do not appear in ``self``. + + EXAMPLES: + + The complement of a + :class:`~sage.combinat.designs.bibd.BalancedIncompleteBlockDesign` is + also a `2`-design:: + + sage: bibd = designs.balanced_incomplete_block_design(13,4) + sage: bibd.is_t_design(return_parameters=True) + (True, (2, 13, 4, 1)) + sage: bibd.complement().is_t_design(return_parameters=True) + (True, (2, 13, 9, 6)) + + The "uniform" complement of a graph is a graph:: + + sage: g = graphs.PetersenGraph() + sage: G = IncidenceStructure(g.edges(labels=False)) + sage: H = G.complement(uniform=True) + sage: h = Graph(H.blocks()) + sage: g == h + False + sage: g == h.complement() + True + + TESTS:: + + sage: bibd.relabel({i:str(i) for i in bibd.ground_set()}) + sage: bibd.complement().ground_set() + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] """ - from sage.graphs.bipartite_graph import BipartiteGraph - A = self.incidence_matrix() - return BipartiteGraph(A) + if uniform: + k = self.is_uniform() + if k is False: + raise ValueError("The incidence structure is not uniform.") + + blocks = [] + num_blocks = self.num_blocks() + i = 0 + from itertools import combinations + for B in combinations(range(self.num_points()),k): + B = list(B) + while i` for some positive integer `s`. + + - it is `t+1`-:meth:`regular ` for some positive integer `t`. + + For more information, see the :wikipedia:`Generalized_quadrangle`. + + .. NOTE:: + + Some references (e.g. [PT09]_ or [GQwiki]_) only allow *regular* + generalized quadrangles. To use such a definition, see the + ``parameters`` optional argument described below, or the methods + :meth:`is_regular` and :meth:`is_uniform`. + + INPUT: + + - ``verbose`` (boolean) -- whether to print an explanation when the + instance is not a generalized quadrangle. + + - ``parameters`` (boolean; ``False``) -- if set to ``True``, the + function returns a pair ``(s,t)`` instead of ``True`` answers. In this + case, `s` and `t` are the integers defined above if they exist (each + can be set to ``False`` otherwise). + + EXAMPLE:: + + sage: h = designs.CremonaRichmondConfiguration() + sage: h.is_generalized_quadrangle() + True + + This is actually a *regular* generalized quadrangle:: + + sage: h.is_generalized_quadrangle(parameters=True) + (2, 2) + + TESTS:: + + sage: H = IncidenceStructure((2*graphs.CompleteGraph(3)).edges(labels=False)) + sage: H.is_generalized_quadrangle(verbose=True) + Some point is at distance >3 from some block. + False + + sage: G = graphs.CycleGraph(5) + sage: B = list(G.subgraph_search_iterator(graphs.PathGraph(3))) + sage: H = IncidenceStructure(B) + sage: H.is_generalized_quadrangle(verbose=True) + Two blocks intersect on >1 points. + False + + sage: hypergraphs.CompleteUniform(4,2).is_generalized_quadrangle(verbose=1) + Some point has two projections on some line. + False + """ + # The distance between a point and a line in the incidence graph is odd + # and must be <= 3. Thus, the diameter is at most 4 + g = self.incidence_graph() + if g.diameter() > 4: + if verbose: + print "Some point is at distance >3 from some block." + return False + + # There is a unique projection of a point on a line. Thus, the girth of + # g is at least 7 + girth = g.girth() + if girth == 4: + if verbose: + print "Two blocks intersect on >1 points." + return False + elif girth == 6: + if verbose: + print "Some point has two projections on some line." + return False + + if parameters: + s = self.is_uniform() + t = self.is_regular() + s = s-1 if (s is not False and s>=2) else False + t = t-1 if (t is not False and t>=2) else False + return (s,t) + else: + return True + def dual(self, algorithm=None): """ Return the dual of the incidence structure. @@ -1422,7 +1759,7 @@ def dual(self, algorithm=None): TESTS:: - sage: D = designs.IncidenceStructure(4, [[0,2],[1,2,3],[2,3]]) + sage: D = IncidenceStructure(4, [[0,2],[1,2,3],[2,3]]) sage: D Incidence structure with 4 points and 3 blocks sage: D.dual() @@ -1430,7 +1767,7 @@ def dual(self, algorithm=None): sage: print D.dual(algorithm="gap") # optional - gap_packages Incidence structure with 3 points and 4 blocks sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]] - sage: BD = designs.IncidenceStructure(7, blocks, name="FanoPlane"); + sage: BD = IncidenceStructure(7, blocks, name="FanoPlane"); sage: BD Incidence structure with 7 points and 7 blocks sage: print BD.dual(algorithm="gap") # optional - gap_packages @@ -1479,7 +1816,7 @@ def automorphism_group(self): A non self-dual example:: - sage: IS = designs.IncidenceStructure(range(4), [[0,1,2,3],[1,2,3]]) + sage: IS = IncidenceStructure(range(4), [[0,1,2,3],[1,2,3]]) sage: IS.automorphism_group().cardinality() 6 sage: IS.dual().automorphism_group().cardinality() @@ -1487,10 +1824,10 @@ def automorphism_group(self): Examples with non-integer points:: - sage: I = designs.IncidenceStructure('abc', ('ab','ac','bc')) + sage: I = IncidenceStructure('abc', ('ab','ac','bc')) sage: I.automorphism_group() Permutation Group with generators [('b','c'), ('a','b')] - sage: designs.IncidenceStructure([[(1,2),(3,4)]]).automorphism_group() + sage: IncidenceStructure([[(1,2),(3,4)]]).automorphism_group() Permutation Group with generators [((1,2),(3,4))] """ from sage.graphs.graph import Graph @@ -1660,7 +1997,7 @@ def parameters(self): EXAMPLES:: - sage: I = designs.IncidenceStructure('abc', ['ab','ac','bc']) + sage: I = IncidenceStructure('abc', ['ab','ac','bc']) sage: I.parameters() doctest:...: DeprecationWarning: .parameters() is deprecated. Use `is_t_design` instead @@ -1713,7 +2050,7 @@ def block_design_checker(self, t, v, k, lmbda, type=None): EXAMPLES:: - sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.is_t_design(return_parameters=True) (True, (2, 7, 3, 1)) sage: BD.block_design_checker(2, 7, 3, 1) @@ -1758,6 +2095,99 @@ def block_design_checker(self, t, v, k, lmbda, type=None): deprecation(16553, "block_design_checker(type='connected') is deprecated, please use .is_connected() instead") return self.incidence_graph().is_connected() + def coloring(self, k=None, solver=None, verbose=0): + r""" + Compute a (weak) `k`-coloring of the hypergraph + + A weak coloring of a hypergraph `\mathcal H` is an assignment of colors + to its vertices such that no set is monochromatic. + + INPUT: + + - ``k`` (integer) -- compute a coloring with `k` colors if an integer is + provided, otherwise returns an optimal coloring (i.e. with the minimum + possible number of colors). + + - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) + solver to be used. If set to ``None``, the default one is used. For + more information on LP solvers and which default solver is used, see + the method + :meth:`~sage.numerical.mip.MixedIntegerLinearProgram.solve` + of the class + :class:`~sage.numerical.mip.MixedIntegerLinearProgram`. + + - ``verbose`` -- non-negative integer (default: ``0``). Set the level + of verbosity you want from the linear program solver. Since the + problem is `NP`-complete, its solving may take some time depending on + the graph. A value of 0 means that there will be no message printed by + the solver. + + EXAMPLES: + + The Fano plane has chromatic number 3:: + + sage: len(designs.steiner_triple_system(7).coloring()) + 3 + + One admissible 3-coloring:: + + sage: designs.steiner_triple_system(7).coloring() # not tested - architecture-dependent + [[0, 2, 5, 1], [4, 3], [6]] + + The chromatic number of a graph is equal to the chromatic number of its + 2-uniform corresponding hypergraph:: + + sage: g = graphs.PetersenGraph() + sage: H = IncidenceStructure(g.edges(labels=False)) + sage: len(g.coloring()) + 3 + sage: len(H.coloring()) + 3 + """ + if k is None: + for k in range(self.num_points()+1): + try: + return self.coloring(k) + except ValueError: + pass + + if k == 0: + if self.num_points(): + raise ValueError("Only empty hypergraphs are 0-chromatic") + return [] + elif any(len(x) == 1 for x in self._blocks): + raise RuntimeError("No coloring can be defined " + "when there is a set of size 1") + elif k == 1: + if any(x for x in self._blocks): + raise ValueError("This hypergraph contains a set. " + "It is not 1-chromatic") + return [self.ground_set()] + + from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException + p = MixedIntegerLinearProgram(solver=solver) + b = p.new_variable(binary=True) + + for x in range(self.num_points()): + p.add_constraint(p.sum(b[x,i] for i in range(k)) == 1) + + for s in self._blocks: + for i in range(k): + p.add_constraint(p.sum(b[x,i] for x in s) <= len(s)-1) + + try: + p.solve(log=verbose) + except MIPSolverException: + raise ValueError("This hypergraph is not {}-colorable".format(k)) + + col = [[] for i in range(k)] + + for (x,i),v in p.get_values(b).iteritems(): + if v: + col[i].append(self._points[x]) + + return col + def edge_coloring(self): r""" Compute a proper edge-coloring. diff --git a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py index bce8794da58..fc75fb6d961 100644 --- a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py +++ b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py @@ -1,11 +1,11 @@ r""" Orthogonal arrays (build recursive constructions) -This module implements several constructions of :mod:`Orthogonal Arrays -`. As their input can be complex, they -all have a counterpart in the -:mod:`~sage.combinat.designs.orthogonal_arrays_find_recursive` module that -automatically computes it. +This module implements several constructions of +:mod:`Orthogonal Arrays`. +As their input can be complex, they all have a counterpart in the +:mod:`~sage.combinat.designs.orthogonal_arrays_find_recursive` module +that automatically computes it. All these constructions are automatically queried when the :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` function is @@ -801,7 +801,7 @@ def thwart_lemma_4_1(k,n,m,explain_construction=False): q = n K = FiniteField(q, 'x') relabel = {x:i for i,x in enumerate(K)} - PG = DesarguesianProjectivePlaneDesign(q,check=False).blocks(copy=False) + PG = DesarguesianProjectivePlaneDesign(q,check=False,point_coordinates=False).blocks(copy=False) if q % 3 == 0: t = K.one() @@ -1144,6 +1144,7 @@ def _reorder_matrix(matrix): The input must be a `N \times k` matrix with entries in `\{0,\ldots,N-1\}` such that: + - the symbols on each row are distinct (and hence can be identified with subsets of `\{0,\ldots,N-1\}`), - each symbol appear exactly `k` times. diff --git a/src/sage/combinat/designs/steiner_quadruple_systems.py b/src/sage/combinat/designs/steiner_quadruple_systems.py index 59e661c08c9..5c4c23afa84 100644 --- a/src/sage/combinat/designs/steiner_quadruple_systems.py +++ b/src/sage/combinat/designs/steiner_quadruple_systems.py @@ -769,7 +769,7 @@ def _SQS14(): EXAMPLE:: sage: from sage.combinat.designs.steiner_quadruple_systems import _SQS14 - sage: sqs14 = designs.IncidenceStructure(_SQS14()) + sage: sqs14 = IncidenceStructure(_SQS14()) sage: sqs14.is_t_design(3,14,4,1) True """ @@ -802,7 +802,7 @@ def _SQS38(): EXAMPLE:: sage: from sage.combinat.designs.steiner_quadruple_systems import _SQS38 - sage: sqs38 = designs.IncidenceStructure(_SQS38()) + sage: sqs38 = IncidenceStructure(_SQS38()) sage: sqs38.is_t_design(3,38,4,1) True """ diff --git a/src/sage/combinat/designs/subhypergraph_search.pyx b/src/sage/combinat/designs/subhypergraph_search.pyx index 589d474b4b3..f14bae6a5dc 100644 --- a/src/sage/combinat/designs/subhypergraph_search.pyx +++ b/src/sage/combinat/designs/subhypergraph_search.pyx @@ -117,8 +117,9 @@ Methods # http://www.gnu.org/licenses/ #***************************************************************************** -from libc.stdlib cimport free,malloc,calloc,qsort +from libc.stdlib cimport qsort from libc.stdint cimport uint64_t +include "sage/ext/stdsage.pxi" ctypedef struct hypergraph: int n @@ -164,9 +165,9 @@ cdef void h_free(hypergraph h): r""" Free the hypergraph """ - free(h.names) - free(h.set_space) - free(h.sets) + sage_free(h.names) + sage_free(h.set_space) + sage_free(h.sets) h.names = NULL h.set_space = NULL h.sets = NULL @@ -180,9 +181,9 @@ cdef hypergraph h_init(int n,list H): h.n = n h.m = len(H) h.limbs = (n+63)/64 # =ceil(n/64) - h.names = malloc(sizeof(int)*n) - h.sets = malloc(h.m*sizeof(uint64_t *)) - h.set_space = calloc(h.m*(h.limbs+1),sizeof(uint64_t)) + h.names = sage_malloc(sizeof(int)*n) + h.sets = sage_malloc(h.m*sizeof(uint64_t *)) + h.set_space = sage_calloc(h.m*(h.limbs+1),sizeof(uint64_t)) # Consistency check for S in H: @@ -369,13 +370,13 @@ cdef class SubHypergraphSearch: self.tmp1 = h_init(n1,H1._blocks) # No actual need to fill them, self.tmp2 = h_init(n2,H2._blocks) # only allocate the memory - self.step = malloc((n2+1)*sizeof(int)) + self.step = sage_malloc((n2+1)*sizeof(int)) # all possible traces/induced subgraphs for h2 # # (calloc sets all internal pointers to NULL) - self.h2_traces = calloc(n2+1,sizeof(hypergraph)) - self.h2_induced = calloc(n2+1,sizeof(hypergraph)) + self.h2_traces = sage_calloc(n2+1,sizeof(hypergraph)) + self.h2_induced = sage_calloc(n2+1,sizeof(hypergraph)) if (self.h1.n == -1 or self.h2.n == -1 or @@ -418,9 +419,9 @@ cdef class SubHypergraphSearch: h_free(self.h2) h_free(self.tmp1) h_free(self.tmp2) - free(self.step) - free(self.h2_traces) - free(self.h2_induced) + sage_free(self.step) + sage_free(self.h2_traces) + sage_free(self.h2_induced) def relabel_heuristic(self): r""" diff --git a/src/sage/combinat/designs/twographs.py b/src/sage/combinat/designs/twographs.py new file mode 100644 index 00000000000..e719f3dc484 --- /dev/null +++ b/src/sage/combinat/designs/twographs.py @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- +r""" +Two-graphs + +A two-graph on `n` points is a family `T \subset \binom {[n]}{3}` +of `3`-sets, such that any `4`-set `S\subset [n]` of size four +contains an even number of elements of `T`. Any graph `([n],E)` +gives rise to a two-graph +`T(E)=\{t \in \binom {[n]}{3} : \left| \binom {t}{2} \cap E \right|\ odd \}`, +and any two graphs with the same two-graph can be obtained one +from the other by :meth:`Seidel switching `. +This defines an equivalence relation on the graphs on `[n]`, +called Seidel switching equivalence. +Conversely, given a two-graph `T`, one can construct a graph +`\Gamma` in the corresponding Seidel switching class with an +isolated vertex `w`. The graph `\Gamma \setminus w` is called +the :meth:`descendant ` of `T` w.r.t. `v`. + +`T` is called regular if each two-subset of `[n]` is contained +in the same number alpha of triples of `T`. + +This module implements a direct construction of a two-graph from a list of +triples, constrution of descendant graphs, regularity checking, and other +things such as constructing the complement two-graph, cf. [BH12]_. + +AUTHORS: + +- Dima Pasechnik (Aug 2015) + +Index +----- + +This module's methods are the following : + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~TwoGraph.is_regular_twograph` | tests if ``self`` is a regular two-graph, i.e. a 2-design + :meth:`~TwoGraph.complement` | returns the complement of ``self`` + :meth:`~TwoGraph.descendant` | returns the descendant graph at `w` + +This module's functions are the following : + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :func:`~taylor_twograph` | constructs Taylor's two-graph for `U_3(q)` + :func:`~is_twograph` | checks that the incidence system is a two-graph + :func:`~twograph_descendant` | returns the descendant graph w.r.t. a given vertex of the two-graph of a given graph + +Methods +--------- +""" +from sage.combinat.designs.incidence_structures import IncidenceStructure +from itertools import combinations + +class TwoGraph(IncidenceStructure): + r""" + Two-graphs class. + + A two-graph on `n` points is a 3-uniform hypergraph, i.e. a family `T + \subset \binom {[n]}{3}` of `3`-sets, such that any `4`-set `S\subset [n]` + of size four contains an even number of elements of `T`. For more + information, see the documentation of the + :mod:`~sage.combinat.designs.twographs` module. + + """ + def __init__(self, points=None, blocks=None, incidence_matrix=None, + name=None, check=False, copy=True): + r""" + Constructor of the class + + TESTS:: + + sage: from sage.combinat.designs.twographs import TwoGraph + sage: TwoGraph([[1,2]]) + Incidence structure with 2 points and 1 blocks + sage: TwoGraph([[1,2]], check=True) + Traceback (most recent call last): + ... + AssertionError: the structure is not a 2-graph! + sage: p=graphs.PetersenGraph().twograph() + sage: TwoGraph(p, check=True) + Incidence structure with 10 points and 60 blocks + """ + IncidenceStructure.__init__(self, points=points, blocks=blocks, + incidence_matrix=incidence_matrix, + name=name, check=False, copy=copy) + if check: # it is a very slow, O(|points|^4), test... + from sage.combinat.designs.twographs import is_twograph + assert is_twograph(self), "the structure is not a 2-graph!" + + def is_regular_twograph(self, alpha=False): + r""" + Tests if the :class:`TwoGraph` is regular, i.e. is a 2-design. + + Namely, each pair of elements of :meth:`ground_set` is contained in + exactly ``alpha`` triples. + + INPUT: + + - ``alpha`` -- (optional, default is ``False``) return the value of + ``alpha``, if possible. + + EXAMPLES:: + + sage: p=graphs.PetersenGraph().twograph() + sage: p.is_regular_twograph(alpha=True) + 4 + sage: p.is_regular_twograph() + True + sage: p=graphs.PathGraph(5).twograph() + sage: p.is_regular_twograph(alpha=True) + False + sage: p.is_regular_twograph() + False + """ + r, (_,_,_,a) = self.is_t_design(t=2, k=3, return_parameters=True) + if r and alpha: + return a + return r + + def descendant(self, v): + """ + The descendant :class:`graph ` at ``v`` + + The :mod:`switching class of graphs ` + corresponding to ``self`` contains a graph ``D`` with ``v`` its own connected + component; removing ``v`` from ``D``, one obtains the descendant graph of + ``self`` at ``v``, which is constructed by this method. + + INPUT: + + - ``v`` -- an element of :meth:`ground_set` + + EXAMPLES:: + + sage: p=graphs.PetersenGraph().twograph().descendant(0) + sage: p.is_strongly_regular(parameters=True) + (9, 4, 1, 2) + """ + from sage.graphs.graph import Graph + return Graph(map(lambda y: filter(lambda z: z != v, y), + filter(lambda x: v in x, self.blocks()))) + + def complement(self): + """ + The two-graph which is the complement of ``self`` + + That is, the two-graph constisting exactly of triples not in ``self``. + Note that this is different from :meth:`complement + ` + of the :class:`parent class + `. + + EXAMPLES:: + + sage: p=graphs.CompleteGraph(8).line_graph().twograph() + sage: pc = p.complement(); pc + Incidence structure with 28 points and 1260 blocks + + TESTS:: + + sage: from sage.combinat.designs.twographs import is_twograph + sage: is_twograph(pc) + True + """ + return super(TwoGraph, self).complement(uniform=True) + +def taylor_twograph(q): + r""" + constructing Taylor's two-graph for `U_3(q)`, `q` odd prime power + + The Taylor's two-graph `T` has the `q^3+1` points of the projective plane over `F_{q^2}` + singular w.r.t. the non-degenerate Hermitean form `S` preserved by `U_3(q)` as its ground set; + the triples are `\{x,y,z\}` satisfying the condition that `S(x,y)S(y,z)S(z,x)` is square + (respectively non-square) if `q \cong 1 \mod 4` (respectively if `q \cong 3 \mod 4`). + See §7E of [BvL84]_. + + There is also a `2-(q^3+1,q+1,1)`-design on these `q^3+1` points, known as the unital of + order `q`, also invariant under `U_3(q)`. + + INPUT: + + - ``q`` -- a power of an odd prime + + EXAMPLES:: + + sage: from sage.combinat.designs.twographs import taylor_twograph + sage: T=taylor_twograph(3); T + Incidence structure with 28 points and 1260 blocks + """ + from sage.graphs.generators.classical_geometries import TaylorTwographSRG + return TaylorTwographSRG(q).twograph() + +def is_twograph(T): + r""" + Checks that the incidence system `T` is a two-graph + + INPUT: + + - ``T`` -- an :class:`incidence structure ` + + EXAMPLES: + + a two-graph from a graph:: + + sage: from sage.combinat.designs.twographs import (is_twograph, TwoGraph) + sage: p=graphs.PetersenGraph().twograph() + sage: is_twograph(p) + True + + a non-regular 2-uniform hypergraph which is a two-graph:: + + sage: is_twograph(TwoGraph([[1,2,3],[1,2,4]])) + True + + TESTS: + + wrong size of blocks:: + + sage: is_twograph(designs.projective_plane(3)) + False + + a triple system which is not a two-graph:: + + sage: is_twograph(designs.projective_plane(2)) + False + """ + if not T.is_uniform(3): + return False + + # A structure for a fast triple existence check + v_to_blocks = {v:set() for v in range(T.num_points())} + for B in T._blocks: + B = frozenset(B) + for x in B: + v_to_blocks[x].add(B) + + has_triple = lambda (x,y,z) : bool(v_to_blocks[x]&v_to_blocks[y]&v_to_blocks[z]) + + # Check that every quadruple contains an even number of triples + from __builtin__ import sum + for quad in combinations(range(T.num_points()),4): + if sum(map(has_triple,combinations(quad,3))) % 2 == 1: + return False + + return True + +def twograph_descendant(G, v, name=None): + r""" + Returns the descendant graph w.r.t. vertex `v` of the two-graph of `G` + + In the :mod:`switching class ` of `G`, + construct a graph `\Delta` with `v` an isolated vertex, and return the subgraph + `\Delta \setminus v`. It is equivalent to, although much faster than, computing the + :meth:`TwoGraph.descendant` of :meth:`two-graph of G `, as the + intermediate two-graph is not constructed. + + INPUT: + + - ``G`` -- a :class:`graph ` + + - ``v`` -- a vertex of ``G`` + + - ``name`` -- (optional) ``None`` - no name, otherwise derive from the construction + + EXAMPLES: + + one of s.r.g.'s from the :mod:`database `:: + + sage: from sage.combinat.designs.twographs import twograph_descendant + sage: A=graphs.strongly_regular_graph(280,135,70) # optional - gap_packages internet + sage: twograph_descendant(A, 0).is_strongly_regular(parameters=True) # optional - gap_packages internet + (279, 150, 85, 75) + + TESTS:: + + sage: T8 = graphs.CompleteGraph(8).line_graph() + sage: v = T8.vertices()[0] + sage: twograph_descendant(T8, v)==T8.twograph().descendant(v) + True + sage: twograph_descendant(T8, v).is_strongly_regular(parameters=True) + (27, 16, 10, 8) + sage: p = graphs.PetersenGraph() + sage: twograph_descendant(p,5) + Graph on 9 vertices + sage: twograph_descendant(p,5,name=True) + descendant of Petersen graph at 5: Graph on 9 vertices + """ + G = G.seidel_switching(G.neighbors(v),inplace=False) + G.delete_vertex(v) + if name: + G.name('descendant of '+G.name()+' at '+str(v)) + else: + G.name('') + return G diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 2f746260f25..b4f977d86c0 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -6,6 +6,8 @@ - Mike Hansen (2007): Initial version - Stephen Doty, Aaron Lauve, George H. Seelinger (2012): Implementation of partition, Brauer, Temperley--Lieb, and ideal partition algebras +- Stephen Doty, Aaron Lauve, George H. Seelinger (2015): Implementation of + ``*Diagram`` classes and other methods to improve diagram algebras. """ #***************************************************************************** @@ -18,23 +20,62 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.categories.all import FiniteDimensionalAlgebrasWithBasis +from sage.categories.algebras import Algebras +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.structure.element import generic_power from sage.combinat.free_module import (CombinatorialFreeModule, CombinatorialFreeModuleElement) +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.combinat.combinat import bell_number, catalan_number +from sage.structure.global_options import GlobalOptions from sage.combinat.set_partition import SetPartitions, SetPartition +from sage.combinat.partition import Partitions +from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra_n +from sage.combinat.permutation import Permutations from sage.sets.set import Set from sage.graphs.graph import Graph from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.flatten import flatten from sage.rings.all import ZZ -import math + +BrauerDiagramOptions = GlobalOptions(name='Brauer diagram', + doc=r""" + Set and display the global options for Brauer diagram (algebras). If no + parameters are set, then the function returns a copy of the options + dictionary. + + The ``options`` to diagram algebras can be accessed as the method + :obj:`BrauerAlgebra.global_options` of :class:`BrauerAlgebra` and + related classes. + """, + end_doc=r""" + EXAMPLES:: + + sage: R. = QQ[] + sage: BA = BrauerAlgebra(2, q) + sage: E = BA([[1,2],[-1,-2]]) + sage: E + B{{-2, -1}, {1, 2}} + sage: BrauerAlgebra.global_options(display="compact") + sage: E + B[12/12;] + sage: BrauerAlgebra.global_options.reset() + """, + display=dict(default="normal", + description='Specifies how the Brauer diagrams should be printed', + values=dict(normal="Using the normal representation", + compact="Using the compact representation"), + case_sensitive=False), +) def partition_diagrams(k): r""" - Return a list of all partition diagrams of order ``k``. + Return a generator of all partition diagrams of order ``k``. A partition diagram of order `k \in \ZZ` to is a set partition of - `\{1, \dots, k, -1, \ldots, -k\}`. If we have `k - 1/2 \in ZZ`, then + `\{1, \ldots, k, -1, \ldots, -k\}`. If we have `k - 1/2 \in ZZ`, then a partition diagram of order `k \in 1/2 \ZZ` is a set partition of `\{1, \ldots, k+1/2, -1, \ldots, -(k+1/2)\}` with `k+1/2` and `-(k+1/2)` in the same block. See [HR2005]_. @@ -46,33 +87,36 @@ def partition_diagrams(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.partition_diagrams(2) + sage: [SetPartition(p) for p in da.partition_diagrams(2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, -1, 1}, {2}}, {{-2}, {-1, 1, 2}}, {{-2, 1, 2}, {-1}}, {{-2, 1}, {-1, 2}}, {{-2, 2}, {-1, 1}}, {{-2, -1}, {1, 2}}, {{-2, -1}, {1}, {2}}, {{-2}, {-1, 2}, {1}}, {{-2, 2}, {-1}, {1}}, {{-2}, {-1, 1}, {2}}, {{-2, 1}, {-1}, {2}}, {{-2}, {-1}, {1, 2}}, {{-2}, {-1}, {1}, {2}}] - sage: da.partition_diagrams(3/2) + sage: [SetPartition(p) for p in da.partition_diagrams(3/2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, 2}, {-1, 1}}, {{-2, 1, 2}, {-1}}, {{-2, 2}, {-1}, {1}}] """ if k in ZZ: - return SetPartitions( range(1, k+1) + [-j for j in range(1, k+1)] ).list() - # Else k in 1/2 ZZ - L = [] - k += ZZ(1) / ZZ(2) - for sp in SetPartitions( range(1, k+1) + [-j for j in range(1, k)] ): - sp = list(sp) - for i in range(len(sp)): - if k in sp[i]: - sp[i] += Set([-k]) - break - L.append(SetPartition(sp)) - return L + S = SetPartitions( range(1, k+1) + [-j for j in range(1, k+1)] ) + for p in Partitions(2*k): + for i in S._iterator_part(p): + yield i + elif k + ZZ(1)/ZZ(2) in ZZ: # Else k in 1/2 ZZ + k = ZZ(k + ZZ(1) / ZZ(2)) + S = SetPartitions( range(1, k+1) + [-j for j in range(1, k)] ) + for p in Partitions(2*k-1): + for sp in S._iterator_part(p): + sp = list(sp) + for i in range(len(sp)): + if k in sp[i]: + sp[i] += Set([-k]) + break + yield sp def brauer_diagrams(k): r""" - Return a list of all Brauer diagrams of order ``k``. + Return a generator of all Brauer diagrams of order ``k``. A Brauer diagram of order `k` is a partition diagram of order `k` with block size 2. @@ -84,26 +128,26 @@ def brauer_diagrams(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.brauer_diagrams(2) + sage: [SetPartition(p) for p in da.brauer_diagrams(2)] [{{-2, 1}, {-1, 2}}, {{-2, 2}, {-1, 1}}, {{-2, -1}, {1, 2}}] - sage: da.brauer_diagrams(5/2) + sage: [SetPartition(p) for p in da.brauer_diagrams(5/2)] [{{-3, 3}, {-2, 1}, {-1, 2}}, {{-3, 3}, {-2, 2}, {-1, 1}}, {{-3, 3}, {-2, -1}, {1, 2}}] """ if k in ZZ: - return [SetPartition(list(x)) for x in - SetPartitions( range(1,k+1) + [-j for j in range(1,k+1)], - [2 for j in range(1,k+1)] )] - # Else k in 1/2 ZZ - L = [] - k += ZZ(1) / ZZ(2) - for i in SetPartitions( range(1, k) + [-j for j in range(1, k)], - [2 for j in range(1, k)] ): - L.append(SetPartition(list(i) + [Set([k, -k])])) - return L + S = SetPartitions( range(1,k+1) + [-j for j in range(1,k+1)], + [2 for j in range(1,k+1)] ) + for i in S._iterator_part(S.parts): + yield list(i) + elif k + ZZ(1) / ZZ(2) in ZZ: # Else k in 1/2 ZZ + k = ZZ(k + ZZ(1) / ZZ(2)) + S = SetPartitions( range(1, k) + [-j for j in range(1, k)], + [2 for j in range(1, k)] ) + for i in S._iterator_part(S.parts): + yield list(i) + [[k, -k]] def temperley_lieb_diagrams(k): r""" - Return a list of all Temperley--Lieb diagrams of order ``k``. + Return a generator of all Temperley--Lieb diagrams of order ``k``. A Temperley--Lieb diagram of order `k` is a partition diagram of order `k` with block size 2 and is planar. @@ -115,21 +159,19 @@ def temperley_lieb_diagrams(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.temperley_lieb_diagrams(2) + sage: [SetPartition(p) for p in da.temperley_lieb_diagrams(2)] [{{-2, 2}, {-1, 1}}, {{-2, -1}, {1, 2}}] - sage: da.temperley_lieb_diagrams(5/2) + sage: [SetPartition(p) for p in da.temperley_lieb_diagrams(5/2)] [{{-3, 3}, {-2, 2}, {-1, 1}}, {{-3, 3}, {-2, -1}, {1, 2}}] """ B = brauer_diagrams(k) - T = [] for i in B: - if is_planar(i) == True: - T.append(i) - return T + if is_planar(i): + yield i def planar_diagrams(k): r""" - Return a list of all planar diagrams of order ``k``. + Return a generator of all planar diagrams of order ``k``. A planar diagram of order `k` is a partition diagram of order `k` that has no crossings. @@ -137,26 +179,24 @@ def planar_diagrams(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.planar_diagrams(2) + sage: [SetPartition(p) for p in da.planar_diagrams(2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, -1, 1}, {2}}, {{-2}, {-1, 1, 2}}, {{-2, 1, 2}, {-1}}, {{-2, 2}, {-1, 1}}, {{-2, -1}, {1, 2}}, {{-2, -1}, {1}, {2}}, {{-2}, {-1, 2}, {1}}, {{-2, 2}, {-1}, {1}}, {{-2}, {-1, 1}, {2}}, {{-2, 1}, {-1}, {2}}, {{-2}, {-1}, {1, 2}}, {{-2}, {-1}, {1}, {2}}] - sage: da.planar_diagrams(3/2) + sage: [SetPartition(p) for p in da.planar_diagrams(3/2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, 2}, {-1, 1}}, {{-2, 1, 2}, {-1}}, {{-2, 2}, {-1}, {1}}] """ A = partition_diagrams(k) - P = [] for i in A: - if is_planar(i) == True: - P.append(i) - return P + if is_planar(i): + yield i def ideal_diagrams(k): r""" - Return a list of all "ideal" diagrams of order ``k``. + Return a generator of all "ideal" diagrams of order ``k``. An ideal diagram of order `k` is a partition diagram of order `k` with propagating number less than `k`. @@ -164,20 +204,972 @@ def ideal_diagrams(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.ideal_diagrams(2) + sage: [SetPartition(p) for p in da.ideal_diagrams(2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, -1, 1}, {2}}, {{-2}, {-1, 1, 2}}, {{-2, 1, 2}, {-1}}, {{-2, -1}, {1, 2}}, {{-2, -1}, {1}, {2}}, {{-2}, {-1, 2}, {1}}, {{-2, 2}, {-1}, {1}}, {{-2}, {-1, 1}, {2}}, {{-2, 1}, {-1}, {2}}, {{-2}, {-1}, {1, 2}}, {{-2}, {-1}, {1}, {2}}] - sage: da.ideal_diagrams(3/2) + sage: [SetPartition(p) for p in da.ideal_diagrams(3/2)] [{{-2, -1, 1, 2}}, {{-2, -1, 2}, {1}}, {{-2, 1, 2}, {-1}}, {{-2, 2}, {-1}, {1}}] """ A = partition_diagrams(k) - I = [] for i in A: if propagating_number(i) < k: - I.append(i) - return I + yield i + +class AbstractPartitionDiagram(SetPartition): + r""" + Abstract base class for partition diagrams. + + This class represents a single partition diagram, that is used as a + basis key for a diagram algebra element. A partition diagram should + be a partition of the set `\{1, \ldots, k, -1, \ldots, -k\}`. Each + such set partition is regarded as a graph on nodes + `\{1, \ldots, k, -1, \ldots, -k\}` arranged in two rows, with nodes + `1, \ldots, k` in the top row from left to right and with nodes + `-1, \ldots, -k` in the bottom row from left to right, and an edge + connecting two nodes if and only if the nodes lie in the same + subset of the set partition. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd1 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) + sage: pd2 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) + sage: pd1 + {{-2, -1}, {1, 2}} + sage: pd1 == pd2 + True + sage: pd1 == [[1,2],[-1,-2]] + True + sage: pd1 == ((-2,-1),(2,1)) + True + sage: pd1 == SetPartition([[1,2],[-1,-2]]) + True + sage: pd3 = da.AbstractPartitionDiagram(pd, [[1,-2],[-1,2]]) + sage: pd1 == pd3 + False + sage: pd4 = da.AbstractPartitionDiagram(pd, [[1,2],[3,4]]) + Traceback (most recent call last): + ... + ValueError: this does not represent two rows of vertices + """ + def __init__(self, parent, d): + r""" + Initialize ``self``. + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd1 = da.AbstractPartitionDiagram(pd, ((-2,-1),(1,2)) ) + """ + self._base_diagram = tuple(sorted(tuple(sorted(i)) for i in d)) + super(AbstractPartitionDiagram, self).__init__(parent, self._base_diagram) + + def check(self): + r""" + Check the validity of the input for the diagram. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd1 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) # indirect doctest + sage: pd2 = da.AbstractPartitionDiagram(pd, [[1,2],[3,4]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: this does not represent two rows of vertices + """ + if self._base_diagram: + tst = sorted(flatten(self._base_diagram)) + if len(tst) % 2 != 0 or tst != range(-len(tst)/2,0) + range(1,len(tst)/2+1): + raise ValueError("this does not represent two rows of vertices") + + def __eq__(self, other): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd1 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) + sage: pd2 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) + sage: pd1 == pd2 + True + sage: pd1 == [[1,2],[-1,-2]] + True + sage: pd1 == ((-2,-1),(2,1)) + True + sage: pd1 == SetPartition([[1,2],[-1,-2]]) + True + sage: pd3 = da.AbstractPartitionDiagram(pd, [[1,-2],[-1,2]]) + sage: pd1 == pd3 + False + """ + try: + return self._base_diagram == other._base_diagram + except AttributeError: + pass + + try: + other2 = self.parent(other) + return self._base_diagram == other2._base_diagram + except (TypeError, ValueError, AttributeError): + return False + + def __ne__(self, other): + """ + Check not equals. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd1 = da.AbstractPartitionDiagram(pd, [[1,2],[-1,-2]]) + sage: pd2 = da.AbstractPartitionDiagram(pd, [[1,-2],[-1,2]]) + sage: pd1 != pd2 + True + sage: pd1 != ((-2,-1),(2,1)) + False + """ + return not self.__eq__(other) + + def base_diagram(self): + r""" + Return the underlying implementation of the diagram. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd([[1,2],[-1,-2]]).base_diagram() == ((-2,-1),(1,2)) + True + """ + return self._base_diagram # note, this works because self._base_diagram is immutable + + def diagram(self): + r""" + Return the underlying implementation of the diagram. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd([[1,2],[-1,-2]]).base_diagram() == pd([[1,2],[-1,-2]]).diagram() + True + """ + return self.base_diagram() + + def compose(self, other): + r""" + Compose ``self`` with ``other``. + + The composition of two diagrams `X` and `Y` is given by placing + `X` on top of `Y` and removing all loops. + + OUTPUT: + + A tuple where the first entry is the composite diagram and the + second entry is how many loop were removed. + + .. NOTE:: + + This is not really meant to be called directly, but it works + to call it this way if desired. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd([[1,2],[-1,-2]]).compose(pd([[1,2],[-1,-2]])) + ({{-2, -1}, {1, 2}}, 1) + """ + (composite_diagram, loops_removed) = set_partition_composition(self._base_diagram, other._base_diagram) + return (self.__class__(self.parent(), composite_diagram), loops_removed) + + def propagating_number(self): + r""" + Return the propagating number of the diagram. + + The propagating number is the number of blocks with both a + positive and negative number. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: d1 = pd([[1,-2],[2,-1]]) + sage: d1.propagating_number() + 2 + sage: d2 = pd([[1,2],[-2,-1]]) + sage: d2.propagating_number() + 0 + """ + return ZZ(sum(1 for part in self._base_diagram if min(part) < 0 and max(part) > 0)) + +class BrauerDiagram(AbstractPartitionDiagram): + r""" + A Brauer diagram. + + A Brauer diagram for an integer `k` is a partition of the set + `\{1, \ldots, k, -1, \ldots, -k\}` with block size 2. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd1 = bd([[1,2],[-1,-2]]) + sage: bd2 = bd([[1,2,-1,-2]]) + Traceback (most recent call last): + ... + ValueError: all blocks must be of size 2 + """ + def __init__(self, parent, d): + r""" + Initialize ``self``. + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd1 = da.BrauerDiagram(bd, ((-2,-1),(1,2)) ) + """ + super(BrauerDiagram, self).__init__(parent,d) + + def check(self): + r""" + Check the validity of the input for ``self``. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd1 = bd([[1,2],[-1,-2]]) # indirect doctest + sage: bd2 = bd([[1,2,-1,-2]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: all blocks must be of size 2 + """ + super(BrauerDiagram, self).check() + if any(len(i) != 2 for i in self): + raise ValueError("all blocks must be of size 2") + + def _repr_(self): + r""" + Return a string representation of a Brauer diagram. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd1 = bd([[1,2],[-1,-2]]); bd1 + {{-2, -1}, {1, 2}} + """ + return self.parent().global_options.dispatch(self, '_repr_', 'display') + + def _repr_normal(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd([[1,2],[-1,-2]])._repr_normal() + '{{-2, -1}, {1, 2}}' + """ + return super(BrauerDiagram, self)._repr_() + + def _repr_compact(self): + """ + Return a compact string representation of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd([[1,2],[-1,-2]])._repr_compact() + '[12/12;]' + sage: bd([[1,-2],[2,-1]])._repr_compact() + '[/;21]' + """ + (top, bot, thru) = self.involution_permutation_triple() + bot.reverse() + s1 = ".".join("".join(str(b) for b in block) for block in top) + s2 = ".".join("".join(str(abs(k)) for k in sorted(block,reverse=True)) + for block in bot) + s3 = "".join(str(x) for x in thru) + return "[{}/{};{}]".format(s1,s2,s3) + + def involution_permutation_triple(self, curt=True): + r""" + Return the involution permutation triple of ``self``. + + From Graham-Lehrer (see `class: BrauerDiagrams`), a Brauer diagram + is a triple `(D_1, D_2, \pi)`, where: + + - `D_1` is a partition of the top nodes; + - `D_2` is a partition of the bottom nodes; + - `\pi` is the induced permutation on the free nodes. + + INPUT: + + - ``curt`` -- (default: ``True``) if ``True``, then return bijection + on free nodes as a one-line notation (standardized to look like a + permutation), else, return the honest mapping, a list of pairs + `(i, -j)` describing the bijection on free nodes + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: elm = bd([[1,2],[-2,-3],[3,-1]]) + sage: elm.involution_permutation_triple() + ([(1, 2)], [(-3, -2)], [1]) + sage: elm.involution_permutation_triple(curt=False) + ([(1, 2)], [(-3, -2)], [[3, -1]]) + """ + diagram = self.diagram() + top = [] + bottom = [] + for v in diagram: + if min(v)>0: + top+=[v] + if max(v)<0: + bottom+=[v] + if curt: + perm = self.perm() + else: + perm = self.bijection_on_free_nodes() + return (top,bottom,perm) + + def bijection_on_free_nodes(self, two_line=False): + r""" + Return the induced bijection - as a list of `(x,f(x))` values - + from the free nodes on the top at the Brauer diagram to the free + nodes at the bottom of ``self``. + + OUTPUT: + + If ``two_line`` is ``True``, then the output is the induced + bijection as a two-row list ``(inputs, outputs)``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: elm = bd([[1,2],[-2,-3],[3,-1]]) + sage: elm.bijection_on_free_nodes() + [[3, -1]] + sage: elm2 = bd([[1,-2],[2,-3],[3,-1]]) + sage: elm2.bijection_on_free_nodes(two_line=True) + [[1, 2, 3], [-2, -3, -1]] + """ + terms = sorted(sorted(list(v), reverse=True) for v in self.diagram() + if max(v) > 0 and min(v) < 0) + if two_line: + terms = [[t[i] for t in terms] for i in range(2)] + return terms + + def perm(self): + r""" + Return the induced bijection on the free nodes of ``self`` in + one-line notation, re-indexed and treated as a permutation. + + .. SEEALSO:: + + :meth:`bijection_on_free_nodes` + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: elm = bd([[1,2],[-2,-3],[3,-1]]) + sage: elm.perm() + [1] + """ + long_form = self.bijection_on_free_nodes() + if not long_form: + return long_form + + short_form = [abs(v[1]) for v in long_form] + # given any list [i1,i2,...,ir] with distinct positive integer entries, + # return naturally associated permutation of [r]. + # probably already defined somewhere in Permutations/Compositions/list/etc. + std = range(1,len(short_form)+1) + j = 0 + for i in range(max(short_form)+1): + if i in short_form: + j += 1 + std[short_form.index(i)] = j + return std + + def is_elementary_symmetric(self): + r""" + Check if is elementary symmetric. + + Let `(D_1, D_2, \pi)` be the Graham-Lehrer representation + of the Brauer diagram `d`. We say `d` is *elementary symmetric* + if `D_1 = D_2` and `\pi` is the identity. + + .. TODO:: Come up with a better name? + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: elm = bd([[1,2],[-1,-2],[3,-3]]) + sage: elm.is_elementary_symmetric() + True + sage: elm2 = bd([[1,2],[-1,-3],[3,-2]]) + sage: elm2.is_elementary_symmetric() + False + """ + (D1,D2,pi) = self.involution_permutation_triple() + D1 = sorted(sorted(abs(y) for y in x) for x in D1) + D2 = sorted(sorted(abs(y) for y in x) for x in D2) + return D1 == D2 and pi == list(range(1,len(pi)+1)) + +class AbstractPartitionDiagrams(Parent, UniqueRepresentation): + r""" + This is a class that generates partition diagrams. + + Thee primary use of this class is to serve as basis keys for + diagram algebras, but diagrams also have properties in their + own right. Furthermore, this class is meant to be extended to + create more efficient contains methods. + + INPUT: + + - ``diagram_func`` -- generator; a function that can create the type + of diagram desired + - ``order`` -- integer or integer `+ 1/2`; the order of the diagrams + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd + Partition diagrams of order 2 + sage: [i for i in pd] + [{{-2, -1, 1, 2}}, + {{-2, -1, 2}, {1}}, + {{-2, -1, 1}, {2}}, + {{-2}, {-1, 1, 2}}, + {{-2, 1, 2}, {-1}}, + {{-2, 1}, {-1, 2}}, + {{-2, 2}, {-1, 1}}, + {{-2, -1}, {1, 2}}, + {{-2, -1}, {1}, {2}}, + {{-2}, {-1, 2}, {1}}, + {{-2, 2}, {-1}, {1}}, + {{-2}, {-1, 1}, {2}}, + {{-2, 1}, {-1}, {2}}, + {{-2}, {-1}, {1, 2}}, + {{-2}, {-1}, {1}, {2}}] + sage: pd.an_element() in pd + True + sage: elm = pd([[1,2],[-1,-2]]) + sage: elm in pd + True + """ + Element = AbstractPartitionDiagram + + def __init__(self, diagram_func, order, category=None): + r""" + See :class:`AbstractPartitionDiagrams` for full documentation. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: TestSuite(pd).run() # long time + """ + if category is None: + category = FiniteEnumeratedSets() + Parent.__init__(self, category=category) + self.diagram_func = diagram_func + self.order = order + + def __iter__(self): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: for i in pd: print i # indirect doctest + {{-2, -1, 1, 2}} + {{-2, -1, 2}, {1}} + {{-2, -1, 1}, {2}} + {{-2}, {-1, 1, 2}} + {{-2, 1, 2}, {-1}} + {{-2, 1}, {-1, 2}} + {{-2, 2}, {-1, 1}} + {{-2, -1}, {1, 2}} + {{-2, -1}, {1}, {2}} + {{-2}, {-1, 2}, {1}} + {{-2, 2}, {-1}, {1}} + {{-2}, {-1, 1}, {2}} + {{-2, 1}, {-1}, {2}} + {{-2}, {-1}, {1, 2}} + {{-2}, {-1}, {1}, {2}} + """ + for i in self.diagram_func(self.order): + yield self.element_class(self, i) + + def _repr_(self): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + Partition diagrams of order 2 + """ + name = self.diagram_func.__name__.replace("_diagrams","").replace("_","").title() + return "{} diagrams of order {}".format(name, self.order) + + def __contains__(self, obj): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: pd.an_element() in pd + True + sage: elm = pd([[1,2],[-1,-2]]) + sage: elm in pd # indirect doctest + True + """ + if not hasattr(obj, '_base_diagram'): + try: + obj = self._element_constructor_(obj) + except (ValueError, TypeError): + return False + if len(obj.base_diagram()) > 0: + tst = sorted(flatten(obj.base_diagram())) + if len(tst)%2 != 0 or tst != range(-len(tst)/2,0) + range(1,len(tst)/2+1): + return False + return True + return self.order == 0 + + def _element_constructor_(self, d): + r""" + Construct an element of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.AbstractPartitionDiagrams(da.partition_diagrams, 2) + sage: elm = pd([[1,2],[-1,-2]]); elm + {{-2, -1}, {1, 2}} + sage: pd([{1,2},{-1,-2}]) == elm + True + sage: pd( ((1,2),(-1,-2)) ) == elm + True + sage: pd( SetPartition([[1,2],[-1,-2]]) ) == elm + True + """ + return self.element_class(self, d) + +class PartitionDiagrams(AbstractPartitionDiagrams): + r""" + This class represents all partition diagrams of integer or integer + `+ 1/2` order. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.PartitionDiagrams(3) + sage: pd.an_element() in pd + True + sage: pd.cardinality() == len(pd.list()) + True + """ + def __init__(self, order, category=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.PartitionDiagrams(2) + sage: TestSuite(pd).run() # long time + """ + super(PartitionDiagrams, self).__init__(partition_diagrams, order, category=category) + + def cardinality(self): + r""" + The cardinality of partition diagrams of integer order `n` is + the `2n`-th Bell number. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pd = da.PartitionDiagrams(3) + sage: pd.cardinality() + 203 + """ + if self.order in ZZ: + return bell_number(2*self.order) + return bell_number(2*(self.order-1/2)) + +class BrauerDiagrams(AbstractPartitionDiagrams): + r""" + This class represents all Brauer diagrams of integer or integer + `+1/2` order. For more information on Brauer diagrams, + see `class: BrauerAlgebra`. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: bd.an_element() in bd + True + sage: bd.cardinality() == len(bd.list()) + True + + These diagrams also come equipped with a compact representation based + on their bipartition triple representation. See the + :meth:`from_involution_permutation_triple` method for more information. + + :: + + sage: bd = da.BrauerDiagrams(3) + sage: bd.global_options(display="compact") + sage: bd.list() + [[/;321], + [/;312], + [23/12;1], + [/;231], + [/;132], + [13/12;1], + [/;213], + [/;123], + [12/12;1], + [23/23;1], + [13/23;1], + [12/23;1], + [23/13;1], + [13/13;1], + [12/13;1]] + sage: bd.global_options.reset() + """ + Element = BrauerDiagram + global_options = BrauerDiagramOptions + + def __init__(self, order, category=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: TestSuite(bd).run() # long time + """ + super(BrauerDiagrams, self).__init__(brauer_diagrams, order, category=category) + + def __contains__(self, obj): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd.an_element() in bd + True + sage: bd([[1,2],[-1,-2]]) in bd + True + sage: [[1,2,-1,-2]] in bd + False + """ + return super(BrauerDiagrams, self).__contains__(obj) and [len(i) for i in obj] == [2]*self.order + + def _element_constructor_(self, d): + r""" + Construct an element of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(2) + sage: bd([[1,2],[-1,-2]]) + {{-2, -1}, {1, 2}} + """ + return self.element_class(self, d) + + def cardinality(self): + r""" + Return the cardinality of ``self``. + + The number of Brauer diagrams of integer order `k` is `(2k-1)!!`. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(3) + sage: bd.cardinality() + 15 + """ + if self.order in ZZ: + return (2*self.order-1).multifactorial(2) + else: + return (2*(self.order-1/2)-1).multifactorial(2) + + def symmetric_diagrams(self,l=None,perm=None): + r""" + Return the list of brauer diagrams with symmetric placement of `l` arcs, + and with free nodes permuted according to `perm`. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(4) + sage: bd.symmetric_diagrams(l=1,perm=[2,1]) + [{{-4, -3}, {-2, 1}, {-1, 2}, {3, 4}}, + {{-4, -2}, {-3, 1}, {-1, 3}, {2, 4}}, + {{-4, 1}, {-3, -2}, {-1, 4}, {2, 3}}, + {{-4, -1}, {-3, 2}, {-2, 3}, {1, 4}}, + {{-4, 2}, {-3, -1}, {-2, 4}, {1, 3}}, + {{-4, 3}, {-3, 4}, {-2, -1}, {1, 2}}] + """ + # perm = permutation on free nodes + # l = number of arcs + n = self.order + if l is None: + l = 0 + if perm is None: + perm = range(1, n+1-2*l) + out = [] + partition_shape = [2]*l + [1]*(n-2*l) + for sp in SetPartitions(n, partition_shape): + sp0 = [block for block in sp if len(block) == 2] + diag = self.from_involution_permutation_triple((sp0,sp0,perm)) + out.append(diag) + return out + + def from_involution_permutation_triple(self, D1_D2_pi): + r""" + Construct a Bruaer diagram of ``self`` from an involution + permutation triple. + + A Brauer diagram can be represented as a triple where the first + entry is a list of arcs on the top row of the diagram, the second + entry is a list of arcs on the bottom row of the diagram, and the + third entry is a permutation on the remaining nodes. This triple + is called the *involution permutation triple*. For more + information, see [GL1996]_. + + INPUT: + + - ``D1_D2_pi``-- a list or tuple where the first entry is a list of + arcs on the top of the diagram, the second entry is a list of arcs + on the bottom of the diagram, and the third entry is a permutation + on the free nodes. + + REFERENCES: + + .. [GL1996] J.J. Graham and G.I. Lehrer, Cellular algebras. + Inventiones mathematicae 123 (1996), 1--34. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: bd = da.BrauerDiagrams(4) + sage: bd.from_involution_permutation_triple([[[1,2]],[[3,4]],[2,1]]) + {{-4, -3}, {-2, 3}, {-1, 4}, {1, 2}} + """ + try: + (D1,D2,pi) = tuple(D1_D2_pi) + except ValueError: + raise ValueError("argument %s not in correct form; must be a tuple (D1, D2, pi)" % D1_D2_pi) + D1 = [[abs(x) for x in b] for b in D1 if len(b) == 2] # not needed if argument correctly passed at outset. + D2 = [[abs(x) for x in b] for b in D2 if len(b) == 2] # ditto. + nD2 = [map(lambda i: -i,b) for b in D2] + pi = list(pi) + nn = set(range(1, self.order+1)) + dom = sorted(nn.difference(flatten([list(x) for x in D1]))) + rng = sorted(nn.difference(flatten([list(x) for x in D2]))) + SP0 = D1 + nD2 + if len(pi) != len(dom) or pi not in Permutations(): + raise ValueError("in the tuple (D1, D2, pi)={}, pi must be a permutation of {} (indicating a permutation on the free nodes of the diagram)".format( + (D1,D2,pi), self.order-2*len(D1))) + Perm = [[dom[i], -rng[val-1]] for i,val in enumerate(pi)] + SP = SP0 + Perm + return self(SP) # could pass 'SetPartition' ? + +class TemperleyLiebDiagrams(AbstractPartitionDiagrams): + r""" + All Temperley-Lieb diagrams of integer or integer `+1/2` order. + + For more information on Temperley-Lieb diagrams, see + `class: TemperleyLiebAlgebra`. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: td = da.TemperleyLiebDiagrams(3) + sage: td.an_element() in td + True + sage: td.cardinality() == len(td.list()) + True + """ + def __init__(self, order): + r""" + See :class:`TemperleyLiebDiagrams` for full documentation. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: td = da.TemperleyLiebDiagrams(2) + sage: TestSuite(td).run() # long time + """ + super(TemperleyLiebDiagrams, self).__init__(temperley_lieb_diagrams, order) + + def cardinality(self): + r""" + Return the cardinality of ``self``. + + The number of Temperley--Lieb diagrams of integer order `k` is the + `k`-th Catalan number. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: td = da.TemperleyLiebDiagrams(3) + sage: td.cardinality() + 5 + """ + if self.order in ZZ: + return catalan_number(self.order) + else: + return catalan_number(self.order-1/2) + + def __contains__(self, obj): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: td = da.TemperleyLiebDiagrams(2) + sage: td.an_element() in td + True + sage: td([[1,2],[-1,-2]]) in td + True + sage: [[1,2],[-1,-2]] in td + True + sage: [[1,-2],[-1,2]] in td + False + """ + if not hasattr(obj, '_base_diagram'): + obj = self._element_constructor_(obj) + if obj not in BrauerDiagrams(self.order): + return False + if not is_planar(obj): + return False + return True + +class PlanarDiagrams(AbstractPartitionDiagrams): + r""" + All planar diagrams of integer or integer `+1/2` order. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pld = da.PlanarDiagrams(3) + sage: pld.an_element() in pld + True + sage: pld.cardinality() == len(pld.list()) + True + """ + def __init__(self, order): + r""" + See :class:`PlanarDiagrams` for full documentation. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pld = da.PlanarDiagrams(2) + sage: TestSuite(pld).run() # long time + """ + super(PlanarDiagrams, self).__init__(planar_diagrams, order) + + def cardinality(self): + r""" + Return the cardinality of ``self``. + + The number of all planar diagrams of order `k` is the + `2k`-th Catalan number. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: pld = da.PlanarDiagrams(3) + sage: pld.cardinality() + 132 + """ + if self.order in ZZ: + return catalan_number(2*self.order) + else: + return catalan_number(2*self.order-1) + + def __contains__(self, obj): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: pld = da.PlanarDiagrams(2) + sage: pld.an_element() in pld + True + sage: pld([[1,2],[-1,-2]]) in pld + True + sage: [[1,2],[-1,-2]] in pld + True + sage: [[1,-2],[-1,2]] in pld + False + """ + if not hasattr(obj, '_base_diagram'): + obj = self._element_constructor_(obj) + return super(PlanarDiagrams, self).__contains__(obj) and is_planar(obj) + +class IdealDiagrams(AbstractPartitionDiagrams): + r""" + All "ideal" diagrams of integer or integer `+1/2` order. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: id = da.IdealDiagrams(3) + sage: id.an_element() in id + True + sage: id.cardinality() == len(id.list()) + True + """ + def __init__(self, order): + r""" + See :class:`TemperleyLiebDiagrams` for full documentation. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: id = da.IdealDiagrams(2) + sage: TestSuite(id).run() # long time + """ + super(IdealDiagrams, self).__init__(ideal_diagrams, order) + + def __contains__(self, obj): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: id = da.IdealDiagrams(2) + sage: id.an_element() in id + True + sage: id([[1,2],[-1,-2]]) in id + True + sage: [[1,2],[-1,-2]] in id + True + sage: [[1,-2],[-1,2]] in id + False + """ + if not hasattr(obj, '_base_diagram'): + obj = self._element_constructor_(obj) + return super(IdealDiagrams, self).__contains__(obj) and obj.propagating_number() < self.order class DiagramAlgebra(CombinatorialFreeModule): r""" @@ -189,23 +1181,23 @@ class DiagramAlgebra(CombinatorialFreeModule): sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) sage: sorted(D.basis()) - [P[{{-2}, {-1}, {1}, {2}}], - P[{{-2}, {-1}, {1, 2}}], - P[{{-2}, {-1, 1}, {2}}], - P[{{-2}, {-1, 1, 2}}], - P[{{-2}, {-1, 2}, {1}}], - P[{{-2, -1}, {1}, {2}}], - P[{{-2, -1}, {1, 2}}], - P[{{-2, -1, 1}, {2}}], - P[{{-2, -1, 1, 2}}], - P[{{-2, -1, 2}, {1}}], - P[{{-2, 1}, {-1}, {2}}], - P[{{-2, 1}, {-1, 2}}], - P[{{-2, 1, 2}, {-1}}], - P[{{-2, 2}, {-1}, {1}}], - P[{{-2, 2}, {-1, 1}}]] + [P{{-2}, {-1}, {1}, {2}}, + P{{-2}, {-1}, {1, 2}}, + P{{-2}, {-1, 1}, {2}}, + P{{-2}, {-1, 1, 2}}, + P{{-2}, {-1, 2}, {1}}, + P{{-2, -1}, {1}, {2}}, + P{{-2, -1}, {1, 2}}, + P{{-2, -1, 1}, {2}}, + P{{-2, -1, 1, 2}}, + P{{-2, -1, 2}, {1}}, + P{{-2, 1}, {-1}, {2}}, + P{{-2, 1}, {-1, 2}}, + P{{-2, 1, 2}, {-1}}, + P{{-2, 2}, {-1}, {1}}, + P{{-2, 2}, {-1, 1}}] """ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): r""" @@ -217,23 +1209,24 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): - ``q`` -- the deformation parameter - ``base_ring`` -- the base ring - ``prefix`` -- the prefix of our monomials - - ``diagrams`` -- the *function* which will generate all diagrams + - ``diagrams`` -- the object representing all the diagrams (i.e. indices for the basis elements) TESTS:: sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) sage: TestSuite(D).run() """ self._prefix = prefix self._q = base_ring(q) self._k = k - if category is None: - category = FiniteDimensionalAlgebrasWithBasis(base_ring) - CombinatorialFreeModule.__init__(self, base_ring, diagrams(k), - category=category, prefix=prefix) + self._base_diagrams = diagrams + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() + category = category.or_subcategory(category) + CombinatorialFreeModule.__init__(self, base_ring, diagrams, + category=category, prefix=prefix, bracket=False) def _element_constructor_(self, set_partition): r""" @@ -243,21 +1236,32 @@ def _element_constructor_(self, set_partition): sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) sage: sp = da.to_set_partition( [[1,2], [-1,-2]] ) sage: b_elt = D(sp); b_elt - P[{{-2, -1}, {1, 2}}] + P{{-2, -1}, {1, 2}} sage: b_elt in D True sage: D([[1,2],[-1,-2]]) == b_elt True sage: D([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R,2) + sage: D(S([2,1])) + P{{-2, 1}, {-1, 2}} + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2(S([1,2])) + P{{-2, 2}, {-1, 1}} + sage: D2(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ - if set_partition in self.basis().keys(): - return CombinatorialFreeModule._element_constructor_(self, set_partition) - - sp = SetPartition(set_partition) # attempt conversion + if self.basis().keys().is_parent_of(set_partition): + return self.basis()[set_partition] + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return self._apply_module_morphism(set_partition, self._perm_to_Blst, self) + sp = self._base_diagrams(set_partition) # attempt conversion if sp in self.basis().keys(): return self.basis()[sp] @@ -271,16 +1275,37 @@ def __getitem__(self, i): sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) - sage: sp = da.to_set_partition( [[1,2], [-1,-2]] ) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) + sage: sp = da.PartitionDiagrams(2)( [[1,2], [-1,-2]] ) sage: D[sp] - P[{{-2, -1}, {1, 2}}] + P{{-2, -1}, {1, 2}} """ - i = to_set_partition(i) + i = self._base_diagrams(i) if i in self.basis().keys(): return self.basis()[i] raise ValueError("{0} is not an index of a basis element".format(i)) + def _perm_to_Blst(self, w): + """ + Convert the permutation ``w`` to an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R,2) + sage: import sage.combinat.diagram_algebras as da + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2._perm_to_Blst([2,1]) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element + """ + ## 'perm' is a permutation in one-line notation + ## turns w into an expression suitable for the element constructor. + u = sorted(w) + p = [[u[i],-x] for i,x in enumerate(w)] + return self[p] + def order(self): r""" Return the order of ``self``. @@ -302,36 +1327,42 @@ def set_partitions(self): Return the collection of underlying set partitions indexing the basis elements of a given diagram algebra. + .. TODO:: Is this really necessary? + TESTS:: sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) - sage: list(D.set_partitions()) == da.partition_diagrams(2) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) + sage: list(D.set_partitions()) == list(da.PartitionDiagrams(2)) True """ return self.basis().keys() def product_on_basis(self, d1, d2): r""" - Returns the product `D_{d_1} D_{d_2}` by two basis diagrams. + Return the product `D_{d_1} D_{d_2}` by two basis diagrams. TESTS:: sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) - sage: sp = SetPartition([[1,2],[-1,-2]]) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) + sage: sp = da.PartitionDiagrams(2)([[1,2],[-1,-2]]) sage: D.product_on_basis(sp, sp) - x*P[{{-2, -1}, {1, 2}}] + x*P{{-2, -1}, {1, 2}} """ - (composite_diagram, loops_removed) = set_partition_composition(d1, d2) + if not self._indices.is_parent_of(d1): + d1 = self._indices(d1) + if not self._indices.is_parent_of(d2): + d2 = self._indices(d2) + (composite_diagram, loops_removed) = d1.compose(d2) return self.term(composite_diagram, self._q**loops_removed) @cached_method def one_basis(self): r""" - The following constructs the identity element of the diagram algebra. + The following constructs the identity element of ``self``. It is not called directly; instead one should use ``DA.one()`` if ``DA`` is a defined diagram algebra. @@ -340,11 +1371,11 @@ def one_basis(self): sage: import sage.combinat.diagram_algebras as da sage: R. = QQ[] - sage: D = da.DiagramAlgebra(2, x, R, 'P', da.partition_diagrams) + sage: D = da.DiagramAlgebra(2, x, R, 'P', da.PartitionDiagrams(2)) sage: D.one_basis() {{-2, 2}, {-1, 1}} """ - return identity_set_partition(self._k) + return self._base_diagrams(identity_set_partition(self._k)) def _latex_term(self, diagram): r""" @@ -355,7 +1386,7 @@ def _latex_term(self, diagram): sage: R. = ZZ[] sage: P = PartitionAlgebra(2, x, R) - sage: latex(P([[1,2],[-2,-1]])) # indirect doctest + sage: latex(P([[1,2],[-2,-1]])) # indirect doctest \begin{tikzpicture}[scale = 0.5,thick, baseline={(0,-1ex/2)}] \tikzstyle{vertex} = [shape = circle, minimum size = 7pt, inner sep = 1pt] \node[vertex] (G--2) at (1.5, -1) [shape = circle, draw] {}; @@ -385,7 +1416,7 @@ def sgn(x): l2.append(j) output = "\\begin{tikzpicture}[scale = 0.5,thick, baseline={(0,-1ex/2)}] \n\\tikzstyle{vertex} = [shape = circle, minimum size = 7pt, inner sep = 1pt] \n" #setup beginning of picture for i in l2: #add nodes - output = output + "\\node[vertex] (G-%s) at (%s, %s) [shape = circle, draw] {}; \n" % (i, (abs(i)-1)*1.5, sgn(i)) + output = output + "\\node[vertex] (G-{}) at ({}, {}) [shape = circle, draw] {{}}; \n".format(i, (abs(i)-1)*1.5, sgn(i)) for i in l1: #add edges if len(i) > 1: l4 = list(i) @@ -419,14 +1450,16 @@ def sgn(x): else: outVec = (sgn(xdiff)*1, -1*y1) inVec = (-1*sgn(xdiff), -1*y2) - output = output + "\\draw (G-%s) .. controls +%s and +%s .. (G-%s); \n" % (j[0], outVec, inVec,j[1]) + output = output + "\\draw (G-{}) .. controls +{} and +{} .. (G-{}); \n".format(j[0], outVec, inVec, j[1]) output = output + "\\end{tikzpicture} \n" #end picture return output - + # The following subclass provides a few additional methods for # partition algebra elements. class Element(CombinatorialFreeModuleElement): r""" + An element of a diagram algebra. + This subclass provides a few additional methods for partition algebra elements. Most element methods are already implemented elsewhere. @@ -448,7 +1481,7 @@ def diagram(self): raise ValueError("this is only defined for basis elements") PA = self.parent() ans = self.support_of_term() - if ans not in partition_diagrams(PA.order()): + if ans not in PA.basis().keys(): raise ValueError("element should be keyed by a diagram") return ans @@ -474,7 +1507,7 @@ class PartitionAlgebra(DiagramAlgebra): algebra with (`R`-module) basis indexed by the collection of set partitions of `\{1, \ldots, k, -1, \ldots, -k\}`. Each such set partition can be represented by a graph on nodes `\{1, \ldots, k, -1, - \ldots, -k\}` arranged in two rows, with nodes `1, \dots, k` in the + \ldots, -k\}` arranged in two rows, with nodes `1, \ldots, k` in the top row from left to right and with nodes `-1, \ldots, -k` in the bottom row from left to right, and edges drawn such that the connected components of the graph are precisely the parts of the set partition. @@ -550,26 +1583,27 @@ class PartitionAlgebra(DiagramAlgebra): sage: R. = ZZ[] sage: P = PartitionAlgebra(2, x, R) sage: P - Partition Algebra of rank 2 with parameter x over Univariate Polynomial Ring in x over Integer Ring + Partition Algebra of rank 2 with parameter x + over Univariate Polynomial Ring in x over Integer Ring sage: P.basis().list() - [P[{{-2, -1, 1, 2}}], P[{{-2, -1, 2}, {1}}], - P[{{-2, -1, 1}, {2}}], P[{{-2}, {-1, 1, 2}}], - P[{{-2, 1, 2}, {-1}}], P[{{-2, 1}, {-1, 2}}], - P[{{-2, 2}, {-1, 1}}], P[{{-2, -1}, {1, 2}}], - P[{{-2, -1}, {1}, {2}}], P[{{-2}, {-1, 2}, {1}}], - P[{{-2, 2}, {-1}, {1}}], P[{{-2}, {-1, 1}, {2}}], - P[{{-2, 1}, {-1}, {2}}], P[{{-2}, {-1}, {1, 2}}], - P[{{-2}, {-1}, {1}, {2}}]] + [P{{-2, -1, 1, 2}}, P{{-2, -1, 2}, {1}}, + P{{-2, -1, 1}, {2}}, P{{-2}, {-1, 1, 2}}, + P{{-2, 1, 2}, {-1}}, P{{-2, 1}, {-1, 2}}, + P{{-2, 2}, {-1, 1}}, P{{-2, -1}, {1, 2}}, + P{{-2, -1}, {1}, {2}}, P{{-2}, {-1, 2}, {1}}, + P{{-2, 2}, {-1}, {1}}, P{{-2}, {-1, 1}, {2}}, + P{{-2, 1}, {-1}, {2}}, P{{-2}, {-1}, {1, 2}}, + P{{-2}, {-1}, {1}, {2}}] sage: E = P([[1,2],[-2,-1]]); E - P[{{-2, -1}, {1, 2}}] - sage: E in P.basis() + P{{-2, -1}, {1, 2}} + sage: E in P.basis().list() True sage: E^2 - x*P[{{-2, -1}, {1, 2}}] + x*P{{-2, -1}, {1, 2}} sage: E^5 - x^4*P[{{-2, -1}, {1, 2}}] + x^4*P{{-2, -1}, {1, 2}} sage: (P([[2,-2],[-1,1]]) - 2*P([[1,2],[-1,-2]]))^2 - (4*x-4)*P[{{-2, -1}, {1, 2}}] + P[{{-2, 2}, {-1, 1}}] + (4*x-4)*P{{-2, -1}, {1, 2}} + P{{-2, 2}, {-1, 1}} One can work with partition algebras using a symbol for the parameter, leaving the base ring unspecified. This implies that the underlying @@ -590,7 +1624,7 @@ class PartitionAlgebra(DiagramAlgebra): sage: P = PA.basis().list() sage: PA.one() - P[{{-2, 2}, {-1, 1}}] + P{{-2, 2}, {-1, 1}} sage: PA.one()*P[7] == P[7] True sage: P[7]*PA.one() == P[7] @@ -606,7 +1640,7 @@ class PartitionAlgebra(DiagramAlgebra): Partition Algebra of rank 2 with parameter q over Univariate Polynomial Ring in q over Real Field with 53 bits of precision sage: PA([[1,2],[-1,-2]]) - 1.00000000000000*B[{{-2, -1}, {1, 2}}] + 1.00000000000000*B{{-2, -1}, {1, 2}} sage: PA = PartitionAlgebra(2, 5, base_ring=ZZ, prefix='B') sage: PA Partition Algebra of rank 2 with parameter 5 over Integer Ring @@ -621,9 +1655,17 @@ class PartitionAlgebra(DiagramAlgebra): sage: g = SetPartitionsAk(1).list() sage: a = A[g[1]] sage: a - P[{{-1}, {1}}] + P{{-1}, {1}} sage: a*a - 17*P[{{-1}, {1}}] + 17*P{{-1}, {1}} + + Symmetric group algebra elements can also be coerced into the + partition algebra:: + + sage: S = SymmetricGroupAlgebra(SR, 2) + sage: A = PartitionAlgebra(2, x, SR) + sage: S([2,1])*A([[1,-1],[2,-2]]) + P{{-2, 1}, {-1, 2}} REFERENCES: @@ -663,7 +1705,7 @@ def __init__(self, k, q, base_ring, prefix): self._k = k self._prefix = prefix self._q = base_ring(q) - DiagramAlgebra.__init__(self, k, q, base_ring, prefix, partition_diagrams) + DiagramAlgebra.__init__(self, k, q, base_ring, prefix, PartitionDiagrams(k)) def _repr_(self): """ @@ -673,10 +1715,36 @@ def _repr_(self): sage: R. = QQ[] sage: PartitionAlgebra(2, q, R) - Partition Algebra of rank 2 with parameter q over Univariate Polynomial Ring in q over Rational Field + Partition Algebra of rank 2 with parameter q + over Univariate Polynomial Ring in q over Rational Field """ - return "Partition Algebra of rank %s with parameter %s over %s"%(self._k, - self._q, self.base_ring()) + return "Partition Algebra of rank {} with parameter {} over {}".format( + self._k, self._q, self.base_ring()) + + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = PartitionAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + """ + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(PartitionAlgebra, self)._coerce_map_from_(R) class SubPartitionAlgebra(DiagramAlgebra): """ @@ -694,15 +1762,11 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): True """ DiagramAlgebra.__init__(self, k, q, base_ring, prefix, diagrams, category) - amb = self.ambient() - self.module_morphism(self.lift, codomain=amb, - category=self.category()).register_as_coercion() - #These methods allow for a sub algebra to be correctly identified in a partition algebra + #These methods allow for a subalgebra to be correctly identified in a partition algebra def ambient(self): r""" Return the partition algebra ``self`` is a sub-algebra of. - Generally, this method is not called directly. EXAMPLES:: @@ -711,13 +1775,12 @@ def ambient(self): sage: BA.ambient() Partition Algebra of rank 2 with parameter x over Symbolic Ring """ - return PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + return self.lift.codomain() - def lift(self, x): + @lazy_attribute + def lift(self): r""" - Lift a diagram subalgebra element to the corresponding element - in the ambient space. This method is not intended to be called - directly. + Return the lift map from diagram subalgebra to the ambient space. EXAMPLES:: @@ -725,24 +1788,20 @@ def lift(self, x): sage: BA = BrauerAlgebra(2, x, R) sage: E = BA([[1,2],[-1,-2]]) sage: lifted = BA.lift(E); lifted - B[{{-2, -1}, {1, 2}}] + B{{-2, -1}, {1, 2}} sage: lifted.parent() is BA.ambient() True """ - if x not in self: - raise ValueError("{0} is not in {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = 0 - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.ambient().monomial(monomial_indices[i]) - return result + amb = PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + phi = self.module_morphism(lambda d: amb.monomial(d), + codomain=amb, category=self.category()) + phi.register_as_coercion() + return phi def retract(self, x): r""" Retract an appropriate partition algebra element to the - corresponding element in the partition subalgebra. This method - is not intended to be called directly. + corresponding element in the partition subalgebra. EXAMPLES:: @@ -753,14 +1812,10 @@ def retract(self, x): sage: BA.retract(E) in BA True """ - if x not in self.ambient() or not set(x.support()).issubset(set(self.basis().keys())): + if ( x not in self.ambient() + or any(i not in self._indices for i in x.support()) ): raise ValueError("{0} cannot retract to {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = self.zero() - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.monomial(monomial_indices[i]) - return result + return self._from_dict(x._monomial_coefficients, remove_zeros=False) class BrauerAlgebra(SubPartitionAlgebra): r""" @@ -788,25 +1843,39 @@ class BrauerAlgebra(SubPartitionAlgebra): EXAMPLES: - We now define the Brauer algebra of rank `2` with parameter ``x`` over - `\ZZ`:: + We now define the Brauer algebra of rank `2` with parameter ``x`` + over `\ZZ`:: sage: R. = ZZ[] sage: B = BrauerAlgebra(2, x, R) sage: B - Brauer Algebra of rank 2 with parameter x over Univariate Polynomial Ring in x over Integer Ring + Brauer Algebra of rank 2 with parameter x + over Univariate Polynomial Ring in x over Integer Ring sage: B.basis() - Finite family {{{-2, -1}, {1, 2}}: B[{{-2, -1}, {1, 2}}], {{-2, 1}, {-1, 2}}: B[{{-2, 1}, {-1, 2}}], {{-2, 2}, {-1, 1}}: B[{{-2, 2}, {-1, 1}}]} + Lazy family (Term map from Brauer diagrams of order 2 to Brauer Algebra + of rank 2 with parameter x over Univariate Polynomial Ring in x + over Integer Ring(i))_{i in Brauer diagrams of order 2} sage: b = B.basis().list() sage: b - [B[{{-2, 1}, {-1, 2}}], B[{{-2, 2}, {-1, 1}}], B[{{-2, -1}, {1, 2}}]] + [B{{-2, 1}, {-1, 2}}, B{{-2, 2}, {-1, 1}}, B{{-2, -1}, {1, 2}}] sage: b[2] - B[{{-2, -1}, {1, 2}}] + B{{-2, -1}, {1, 2}} sage: b[2]^2 - x*B[{{-2, -1}, {1, 2}}] + x*B{{-2, -1}, {1, 2}} sage: b[2]^5 - x^4*B[{{-2, -1}, {1, 2}}] + x^4*B{{-2, -1}, {1, 2}} + + Note, also that since the symmetric group algebra is contained in + the Brauer algebra, there is also a conversion between the two. :: + + sage: R. = ZZ[] + sage: B = BrauerAlgebra(2, x, R) + sage: S = SymmetricGroupAlgebra(R, 2) + sage: S([2,1])*B([[1,-1],[2,-2]]) + B{{-2, 1}, {-1, 2}} """ + global_options = BrauerDiagramOptions + @staticmethod def __classcall_private__(cls, k, q, base_ring=None, prefix="B"): r""" @@ -835,7 +1904,7 @@ def __init__(self, k, q, base_ring, prefix): sage: BA = BrauerAlgebra(2, q, R) sage: TestSuite(BA).run() """ - SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, brauer_diagrams) + SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, BrauerDiagrams(k)) def _repr_(self): """ @@ -845,9 +1914,37 @@ def _repr_(self): sage: R. = QQ[] sage: BrauerAlgebra(2, q, R) - Brauer Algebra of rank 2 with parameter q over Univariate Polynomial Ring in q over Rational Field + Brauer Algebra of rank 2 with parameter q + over Univariate Polynomial Ring in q over Rational Field + """ + return "Brauer Algebra of rank {} with parameter {} over {}".format( + self._k, self._q, self.base_ring()) + + # TODO: Make a mixin class for diagram algebras that have coercions from SGA? + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = BrauerAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field """ - return "Brauer Algebra of rank %s with parameter %s over %s"%(self._k, self._q, self.base_ring()) + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(BrauerAlgebra, self)._coerce_map_from_(R) def _element_constructor_(self, set_partition): r""" @@ -859,7 +1956,7 @@ def _element_constructor_(self, set_partition): sage: BA = BrauerAlgebra(2, q, R) sage: sp = SetPartition([[1,2], [-1,-2]]) sage: b_elt = BA(sp); b_elt - B[{{-2, -1}, {1, 2}}] + B{{-2, -1}, {1, 2}} sage: b_elt in BA True sage: BA([[1,2],[-1,-2]]) == b_elt @@ -870,6 +1967,45 @@ def _element_constructor_(self, set_partition): set_partition = to_Brauer_partition(set_partition, k = self.order()) return DiagramAlgebra._element_constructor_(self, set_partition) + def jucys_murphy(self, j): + r""" + Return the ``j``-th generalized Jucys-Murphy element of ``self``. + + The `j`-th Jucys-Murphy element of a Brauer algebra is simply + the `j`-th Jucys-Murphy element of the symmetric group algebra + with an extra `(z-1)/2` term, where ``z`` is the parameter + of the Brauer algebra. + + REFERENCES: + + .. [Naz96] Maxim Nazarov, Young's Orthogonal Form for Brauer's + Centralizer Algebra. Journal of Algebra 182 (1996), 664--693. + + EXAMPLES:: + + sage: z = var('z') + sage: B = BrauerAlgebra(3,z) + sage: B.jucys_murphy(1) + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + sage: B.jucys_murphy(3) + -B{{-3, -2}, {-1, 1}, {2, 3}} - B{{-3, -1}, {-2, 2}, {1, 3}} + + B{{-3, 1}, {-2, 2}, {-1, 3}} + B{{-3, 2}, {-2, 3}, {-1, 1}} + + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + """ + if j < 1: + raise ValueError("Jucys-Murphy index must be positive") + k = self.order() + if j > k: + raise ValueError("Jucys-Murphy index cannot be greater than the order of the algebra") + I = lambda x: self._indices(to_Brauer_partition(x, k=k)) + R = self.base_ring() + one = R.one() + d = {self.one_basis(): R( (self._q-1) / 2 )} + for i in range(1,j): + d[I([[i,-j],[j,-i]])] = one + d[I([[i,j],[-i,-j]])] = -one + return self._from_dict(d, remove_zeros=True) + class TemperleyLiebAlgebra(SubPartitionAlgebra): r""" A Temperley--Lieb algebra. @@ -901,14 +2037,18 @@ class TemperleyLiebAlgebra(SubPartitionAlgebra): sage: R. = ZZ[] sage: T = TemperleyLiebAlgebra(2, x, R); T - Temperley-Lieb Algebra of rank 2 with parameter x over Univariate Polynomial Ring in x over Integer Ring + Temperley-Lieb Algebra of rank 2 with parameter x + over Univariate Polynomial Ring in x over Integer Ring sage: T.basis() - Finite family {{{-2, 2}, {-1, 1}}: T[{{-2, 2}, {-1, 1}}], {{-2, -1}, {1, 2}}: T[{{-2, -1}, {1, 2}}]} + Lazy family (Term map from Temperleylieb diagrams of order 2 + to Temperley-Lieb Algebra of rank 2 with parameter x + over Univariate Polynomial Ring in x over + Integer Ring(i))_{i in Temperleylieb diagrams of order 2} sage: b = T.basis().list() sage: b - [T[{{-2, 2}, {-1, 1}}], T[{{-2, -1}, {1, 2}}]] + [T{{-2, 2}, {-1, 1}}, T{{-2, -1}, {1, 2}}] sage: b[1] - T[{{-2, -1}, {1, 2}}] + T{{-2, -1}, {1, 2}} sage: b[1]^2 == x*b[1] True sage: b[1]^5 == x^4*b[1] @@ -942,7 +2082,7 @@ def __init__(self, k, q, base_ring, prefix): sage: TL = TemperleyLiebAlgebra(2, q, R) sage: TestSuite(TL).run() """ - SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, temperley_lieb_diagrams) + SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, TemperleyLiebDiagrams(k)) def _repr_(self): """ @@ -952,10 +2092,11 @@ def _repr_(self): sage: R. = QQ[] sage: TemperleyLiebAlgebra(2, q, R) - Temperley-Lieb Algebra of rank 2 with parameter q over Univariate Polynomial Ring in q over Rational Field + Temperley-Lieb Algebra of rank 2 with parameter q + over Univariate Polynomial Ring in q over Rational Field """ - return "Temperley-Lieb Algebra of rank %s with parameter %s over %s"%(self._k, - self._q, self.base_ring()) + return "Temperley-Lieb Algebra of rank {} with parameter {} over {}".format( + self._k, self._q, self.base_ring()) def _element_constructor_(self, set_partition): r""" @@ -967,14 +2108,23 @@ def _element_constructor_(self, set_partition): sage: TL = TemperleyLiebAlgebra(2, q, R) sage: sp = SetPartition([[1,2], [-1,-2]]) sage: b_elt = TL(sp); b_elt - T[{{-2, -1}, {1, 2}}] + T{{-2, -1}, {1, 2}} sage: b_elt in TL True sage: TL([[1,2],[-1,-2]]) == b_elt True sage: TL([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R, 2) + sage: TL(S([1,2])) + T{{-2, 2}, {-1, 1}} + sage: TL(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return SubPartitionAlgebra._element_constructor_(self, set_partition) set_partition = to_Brauer_partition(set_partition, k = self.order()) return SubPartitionAlgebra._element_constructor_(self, set_partition) @@ -1011,13 +2161,13 @@ class PlanarAlgebra(SubPartitionAlgebra): sage: Pl = PlanarAlgebra(2, x, R); Pl Planar Algebra of rank 2 with parameter x over Univariate Polynomial Ring in x over Integer Ring sage: Pl.basis().list() - [Pl[{{-2, -1, 1, 2}}], Pl[{{-2, -1, 2}, {1}}], - Pl[{{-2, -1, 1}, {2}}], Pl[{{-2}, {-1, 1, 2}}], - Pl[{{-2, 1, 2}, {-1}}], Pl[{{-2, 2}, {-1, 1}}], - Pl[{{-2, -1}, {1, 2}}], Pl[{{-2, -1}, {1}, {2}}], - Pl[{{-2}, {-1, 2}, {1}}], Pl[{{-2, 2}, {-1}, {1}}], - Pl[{{-2}, {-1, 1}, {2}}], Pl[{{-2, 1}, {-1}, {2}}], - Pl[{{-2}, {-1}, {1, 2}}], Pl[{{-2}, {-1}, {1}, {2}}]] + [Pl{{-2, -1, 1, 2}}, Pl{{-2, -1, 2}, {1}}, + Pl{{-2, -1, 1}, {2}}, Pl{{-2}, {-1, 1, 2}}, + Pl{{-2, 1, 2}, {-1}}, Pl{{-2, 2}, {-1, 1}}, + Pl{{-2, -1}, {1, 2}}, Pl{{-2, -1}, {1}, {2}}, + Pl{{-2}, {-1, 2}, {1}}, Pl{{-2, 2}, {-1}, {1}}, + Pl{{-2}, {-1, 1}, {2}}, Pl{{-2, 1}, {-1}, {2}}, + Pl{{-2}, {-1}, {1, 2}}, Pl{{-2}, {-1}, {1}, {2}}] sage: E = Pl([[1,2],[-1,-2]]) sage: E^2 == x*E True @@ -1052,7 +2202,7 @@ def __init__(self, k, q, base_ring, prefix): sage: PlA = PlanarAlgebra(2, q, R) sage: TestSuite(PlA).run() """ - SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, planar_diagrams) + SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, PlanarDiagrams(k)) def _repr_(self): """ @@ -1062,9 +2212,10 @@ def _repr_(self): sage: R. = ZZ[] sage: Pl = PlanarAlgebra(2, x, R); Pl - Planar Algebra of rank 2 with parameter x over Univariate Polynomial Ring in x over Integer Ring + Planar Algebra of rank 2 with parameter x + over Univariate Polynomial Ring in x over Integer Ring """ - return "Planar Algebra of rank %s with parameter %s over %s"%(self._k, + return "Planar Algebra of rank {} with parameter {} over {}".format(self._k, self._q, self.base_ring()) class PropagatingIdeal(SubPartitionAlgebra): @@ -1087,15 +2238,16 @@ class PropagatingIdeal(SubPartitionAlgebra): sage: R. = QQ[] sage: I = PropagatingIdeal(2, x, R); I - Propagating Ideal of rank 2 with parameter x over Univariate Polynomial Ring in x over Rational Field + Propagating Ideal of rank 2 with parameter x + over Univariate Polynomial Ring in x over Rational Field sage: I.basis().list() - [I[{{-2, -1, 1, 2}}], I[{{-2, -1, 2}, {1}}], - I[{{-2, -1, 1}, {2}}], I[{{-2}, {-1, 1, 2}}], - I[{{-2, 1, 2}, {-1}}], I[{{-2, -1}, {1, 2}}], - I[{{-2, -1}, {1}, {2}}], I[{{-2}, {-1, 2}, {1}}], - I[{{-2, 2}, {-1}, {1}}], I[{{-2}, {-1, 1}, {2}}], - I[{{-2, 1}, {-1}, {2}}], I[{{-2}, {-1}, {1, 2}}], - I[{{-2}, {-1}, {1}, {2}}]] + [I{{-2, -1, 1, 2}}, I{{-2, -1, 2}, {1}}, + I{{-2, -1, 1}, {2}}, I{{-2}, {-1, 1, 2}}, + I{{-2, 1, 2}, {-1}}, I{{-2, -1}, {1, 2}}, + I{{-2, -1}, {1}, {2}}, I{{-2}, {-1, 2}, {1}}, + I{{-2, 2}, {-1}, {1}}, I{{-2}, {-1, 1}, {2}}, + I{{-2, 1}, {-1}, {2}}, I{{-2}, {-1}, {1, 2}}, + I{{-2}, {-1}, {1}, {2}}] sage: E = I([[1,2],[-1,-2]]) sage: E^2 == x*E True @@ -1131,8 +2283,9 @@ def __init__(self, k, q, base_ring, prefix): sage: TestSuite(I).run() # Not tested -- needs non-unital algebras category """ # This should be the category of non-unital fin-dim algebras with basis - category = FiniteDimensionalAlgebrasWithBasis(base_ring) - SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, ideal_diagrams, category) + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() + SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, + IdealDiagrams(k), category) @cached_method def one_basis(self): @@ -1166,12 +2319,14 @@ def _repr_(self): sage: PropagatingIdeal(2, x, R) Propagating Ideal of rank 2 with parameter x over Univariate Polynomial Ring in x over Rational Field """ - return "Propagating Ideal of rank %s with parameter %s over %s"%(self._k, - self._q, self.base_ring()) + return "Propagating Ideal of rank {} with parameter {} over {}".format( + self._k, self._q, self.base_ring()) class Element(SubPartitionAlgebra.Element): """ - Need to take care of exponents since we are not unital. + An element of a propagating ideal. + + We need to take care of exponents since we are not unital. """ def __pow__(self, n): """ @@ -1187,7 +2342,7 @@ def __pow__(self, n): sage: I = PropagatingIdeal(2, x, R) sage: E = I([[1,2],[-1,-2]]) sage: E^2 - x*I[{{-2, -1}, {1, 2}}] + x*I{{-2, -1}, {1, 2}} sage: E^0 Traceback (most recent call last): ... @@ -1238,7 +2393,7 @@ def is_planar(sp): #Skip the ones that don't involve numbers in both #the bottom and top rows - if len(bn) == 0 or len(bp) == 0: + if not bn or not bp: continue #Make sure that if min(bp) > max(ap) @@ -1268,11 +2423,11 @@ def is_planar(sp): #Make sure we make the numbers negative again #if we are in the bottom row if row is ap: - sr = Set(rng) + sr = set(rng) else: - sr = Set((-1*x for x in rng)) + sr = set((-1*x for x in rng)) - sj = Set(to_consider[j]) + sj = set(to_consider[j]) intersection = sr.intersection(sj) if intersection: if sj != intersection: @@ -1414,27 +2569,28 @@ def to_set_partition(l, k=None): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.to_set_partition([[1,-1],[2,-2]]) == da.identity_set_partition(2) + sage: f = lambda sp: SetPartition(da.to_set_partition(sp)) + sage: f([[1,-1],[2,-2]]) == SetPartition(da.identity_set_partition(2)) True """ if k is None: if l == []: - return SetPartition([]) + return [] else: k = max( (max( map(abs, x) ) for x in l) ) - to_be_added = Set( list(range(1, k+1)) + [-1*x for x in range(1, k+1)] ) + to_be_added = set( list(range(1, k+1)) + [-1*x for x in range(1, k+1)] ) sp = [] for part in l: - spart = Set(part) + spart = set(part) to_be_added -= spart sp.append(spart) for singleton in to_be_added: - sp.append(Set([singleton])) + sp.append(set([singleton])) - return SetPartition(sp) + return sp def to_Brauer_partition(l, k=None): r""" @@ -1444,13 +2600,15 @@ def to_Brauer_partition(l, k=None): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.to_Brauer_partition([[1,2],[-1,-2]]) == SetPartition([[1,2],[-1,-2]]) + sage: f = lambda sp: SetPartition(da.to_Brauer_partition(sp)) + sage: f([[1,2],[-1,-2]]) == SetPartition([[1,2],[-1,-2]]) True - sage: da.to_Brauer_partition([[1,3],[-1,-3]]) == SetPartition([[1,3],[-3,-1],[2,-2]]) + sage: f([[1,3],[-1,-3]]) == SetPartition([[1,3],[-3,-1],[2,-2]]) True - sage: da.to_Brauer_partition([[1,2],[-1,-2]], k=4) == SetPartition([[1,2],[-1,-2],[3,-3],[4,-4]]) + sage: f([[1,-4],[-3,-1],[3,4]]) == SetPartition([[-3,-1],[2,-2],[1,-4],[3,4]]) True - sage: da.to_Brauer_partition([[1,-4],[-3,-1],[3,4]]) == SetPartition([[-3,-1],[2,-2],[1,-4],[3,4]]) + sage: p = SetPartition([[1,2],[-1,-2],[3,-3],[4,-4]]) + sage: SetPartition(da.to_Brauer_partition([[1,2],[-1,-2]], k=4)) == p True """ L = to_set_partition(l, k=k) @@ -1462,9 +2620,9 @@ def to_Brauer_partition(l, k=None): for i in L2: if len(i) >= 3: raise ValueError("blocks must have size at most 2, but {0} has {1}".format(i, len(i))) - if (len(i) == 2): + if len(i) == 2: paired.append(i) - if (len(i) == 1): + if len(i) == 1: not_paired.append(i) if any(i[0] in j or -1*i[0] in j for i in not_paired for j in paired): raise ValueError("unable to convert {0} to a Brauer partition due to the invalid block {1}".format(l, i)) @@ -1481,13 +2639,13 @@ def identity_set_partition(k): EXAMPLES:: sage: import sage.combinat.diagram_algebras as da - sage: da.identity_set_partition(2) + sage: SetPartition(da.identity_set_partition(2)) {{-2, 2}, {-1, 1}} """ if k in ZZ: - return SetPartition( [[i,-i] for i in range(1, k + 1)] ) + return [[i,-i] for i in range(1, k + 1)] # Else k in 1/2 ZZ - return SetPartition( [[i, -i] for i in range(1, k + ZZ(3)/ZZ(2))] ) + return [[i, -i] for i in range(1, k + ZZ(3)/ZZ(2))] def set_partition_composition(sp1, sp2): r""" @@ -1500,7 +2658,8 @@ def set_partition_composition(sp1, sp2): sage: import sage.combinat.diagram_algebras as da sage: sp1 = da.to_set_partition([[1,-2],[2,-1]]) sage: sp2 = da.to_set_partition([[1,-2],[2,-1]]) - sage: da.set_partition_composition(sp1, sp2) == (da.identity_set_partition(2), 0) + sage: p, c = da.set_partition_composition(sp1, sp2) + sage: (SetPartition(p), c) == (SetPartition(da.identity_set_partition(2)), 0) True """ g = pair_to_graph(sp1, sp2) @@ -1516,9 +2675,9 @@ def set_partition_composition(sp1, sp2): if len(cc) > 1: total_removed += 1 else: - res.append( Set((x[0] for x in new_cc)) ) + res.append( set((x[0] for x in new_cc)) ) - return (SetPartition(Set(res)), total_removed) + return (res, total_removed) ########################################################################## # END BORROWED CODE diff --git a/src/sage/combinat/dyck_word.py b/src/sage/combinat/dyck_word.py index 761c483874a..24f25e464e5 100644 --- a/src/sage/combinat/dyck_word.py +++ b/src/sage/combinat/dyck_word.py @@ -3102,7 +3102,7 @@ def to_alternating_sign_matrix(self): return A.from_monotone_triangle(monotone_triangle) -class DyckWords(Parent, UniqueRepresentation): +class DyckWords(UniqueRepresentation, Parent): r""" Dyck words. diff --git a/src/sage/combinat/enumerated_sets.py b/src/sage/combinat/enumerated_sets.py index a39220a900e..2aa80c1895e 100644 --- a/src/sage/combinat/enumerated_sets.py +++ b/src/sage/combinat/enumerated_sets.py @@ -15,7 +15,7 @@ - :class:`~sage.combinat.subset.Subsets`, :class:`~sage.combinat.combination.Combinations` - :class:`~sage.combinat.permutation.Arrangements`, :class:`~sage.combinat.tuple.Tuples` - :class:`~sage.sets.finite_enumerated_set.FiniteEnumeratedSet` -- :class:`~DisjointUnionEnumeratedSets`, :class:`~CartesianProduct` +- :class:`~DisjointUnionEnumeratedSets` Integer lists ------------- @@ -123,7 +123,7 @@ - :ref:`sage.combinat.dlx` - :ref:`sage.combinat.matrices.dlxcpp` - :ref:`sage.combinat.species` -- :class:`~sage.combinat.integer_list.IntegerListsLex` +- :class:`~sage.combinat.integer_lists.IntegerListsLex` - :class:`~sage.combinat.integer_vectors_mod_permgroup.IntegerVectorsModPermutationGroup` Low level enumerated sets diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 78883634690..330bdbc7210 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3,11 +3,22 @@ Finite State Machines, Automata, Transducers This module adds support for finite state machines, automata and -transducers. See classes :class:`Automaton` and :class:`Transducer` -(or the more general class :class:`FiniteStateMachine`) and the -:ref:`examples ` below for details -creating one. You can also use a :mod:`preconstructed and commonly used one -`. +transducers. + +For creating automata and transducers you can use classes + +- :class:`Automaton` and :class:`Transducer` + (or the more general class :class:`FiniteStateMachine`) + +or the generators + +- :class:`automata ` and + :class:`transducers ` + +which contain :doc:`preconstructed and commonly used automata and transducers +`. See also the +:ref:`examples ` below. + Contents ======== @@ -38,6 +49,7 @@ :meth:`~FiniteStateMachine.predecessors` | List of predecessors of a state :meth:`~FiniteStateMachine.induced_sub_finite_state_machine` | Induced sub-machine :meth:`~FiniteStateMachine.accessible_components` | Accessible components + :meth:`~FiniteStateMachine.coaccessible_components` | Coaccessible components :meth:`~FiniteStateMachine.final_components` | Final components (connected components which cannot be left again) @@ -75,7 +87,9 @@ :meth:`~FiniteStateMachine.delete_transition` | Delete a transition :meth:`~FiniteStateMachine.remove_epsilon_transitions` | Remove epsilon transitions (not implemented) :meth:`~FiniteStateMachine.split_transitions` | Split transitions with input words of length ``> 1`` - :meth:`~FiniteStateMachine.determine_alphabets` | Determines input and output alphabets + :meth:`~FiniteStateMachine.determine_alphabets` | Determine input and output alphabets + :meth:`~FiniteStateMachine.determine_input_alphabet` | Determine input alphabet + :meth:`~FiniteStateMachine.determine_output_alphabet` | Determine output alphabet :meth:`~FiniteStateMachine.construct_final_word_out` | Construct final output by implicitly reading trailing letters; cf. :meth:`~FiniteStateMachine.with_final_word_out` @@ -96,11 +110,14 @@ :meth:`~FiniteStateMachine.is_deterministic` | Checks for a deterministic machine :meth:`~FiniteStateMachine.is_complete` | Checks for a complete machine :meth:`~FiniteStateMachine.is_connected` | Checks for a connected machine + :meth:`Automaton.is_equivalent` | Checks for equivalent automata :meth:`~FiniteStateMachine.is_Markov_chain` | Checks for a Markov chain :meth:`~FiniteStateMachine.is_monochromatic` | Checks whether the colors of all states are equal + :meth:`~FiniteStateMachine.number_of_words` | Determine the number of successful paths :meth:`~FiniteStateMachine.asymptotic_moments` | Main terms of expectation and variance of sums of labels + :meth:`~FiniteStateMachine.moments_waiting_time` | Moments of the waiting time for first true output :meth:`~FiniteStateMachine.epsilon_successors` | Epsilon successors of a state - + :meth:`Automaton.shannon_parry_markov_chain` | Compute Markov chain with Parry measure Operations ^^^^^^^^^^ @@ -110,9 +127,10 @@ :widths: 30, 70 :delim: | - :meth:`~FiniteStateMachine.disjoint_union` | Disjoint union (not implemented) - :meth:`~FiniteStateMachine.concatenation` | Concatenation (not implemented) - :meth:`~FiniteStateMachine.Kleene_closure` | Kleene closure (not implemented) + :meth:`~FiniteStateMachine.disjoint_union` | Disjoint union + :meth:`~FiniteStateMachine.concatenation` | Concatenation + :meth:`~FiniteStateMachine.kleene_star` | Kleene star + :meth:`Automaton.complement` | Complement of an automaton :meth:`Automaton.intersection` | Intersection of automata :meth:`Transducer.intersection` | Intersection of transducers :meth:`Transducer.cartesian_product` | Cartesian product of a transducer with another finite state machine @@ -125,11 +143,14 @@ :meth:`~FiniteStateMachine.transposition` | Transposition (all transitions are reversed) :meth:`~FiniteStateMachine.with_final_word_out` | Machine with final output constructed by implicitly reading trailing letters, cf. :meth:`~FiniteStateMachine.construct_final_word_out` for inplace version :meth:`Automaton.determinisation` | Determinisation of an automaton + :meth:`~FiniteStateMachine.completion` | Completion of a finite state machine :meth:`~FiniteStateMachine.process` | Process input :meth:`~FiniteStateMachine.__call__` | Process input with shortened output :meth:`Automaton.process` | Process input of an automaton (output differs from general case) :meth:`Transducer.process` | Process input of a transducer (output differs from general case) :meth:`~FiniteStateMachine.iter_process` | Return process iterator + :meth:`~FiniteStateMachine.language` | Return all possible output words + :meth:`Automaton.language` | Return all possible accepted words Simplification @@ -158,7 +179,7 @@ :widths: 30, 70 :delim: | - :meth:`~FiniteStateMachine.adjacency_matrix` | (Weighted) adjacency :class:`matrix ` + :meth:`~FiniteStateMachine.adjacency_matrix` | (Weighted) adjacency :class:`matrix ` :meth:`~FiniteStateMachine.graph` | Underlying :class:`DiGraph` :meth:`~FiniteStateMachine.plot` | Plot @@ -362,6 +383,51 @@ which gives additionally the state in which we arrived. +We can also let an automaton act on a :doc:`word `:: + + sage: W = Words([-1, 0, 1]); W + Words over {-1, 0, 1} + sage: w = W([1, 0, 1, 0, -1]); w + word: 1,0,1,0,-1 + sage: NAF(w) + True + +Recognizing NAFs via Automata Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Alternatively, we can use automata operations to recognize NAFs; for +simplicity, we only use the input alphabet ``[0, 1]``. On the one +hand, we can construct such an automaton by forbidding the word +``11``:: + + sage: forbidden = automata.ContainsWord([1, 1], input_alphabet=[0, 1]) + sage: NAF_negative = forbidden.complement() + sage: NAF_negative([1, 1, 0, 1]) + False + sage: NAF_negative([1, 0, 1, 0, 1]) + True + +On the other hand, we can write this as a regular expression and +translate that into automata operations:: + + sage: zero = automata.Word([0]) + sage: one = automata.Word([1]) + sage: epsilon = automata.EmptyWord(input_alphabet=[0, 1]) + sage: NAF_positive = (zero + one*zero).kleene_star() * (epsilon + one) + +We check that the two approaches are equivalent:: + + sage: NAF_negative.is_equivalent(NAF_positive) + True + +.. SEEALSO:: + + :meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.ContainsWord`, + :meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.Word`, + :meth:`~Automaton.complement`, + :meth:`~FiniteStateMachine.kleene_star`, + :meth:`~sage.combinat.finite_state_machine_generators.AutomatonGenerators.EmptyWord`, + :meth:`~Automaton.is_equivalent`. .. _finite_state_machine_LaTeX_output: @@ -456,6 +522,37 @@ .. _finite_state_machine_division_by_3_example: + +Transducers and (in)finite Words +-------------------------------- + +A transducer can also act on everything iterable, in particular, on +Sage's :doc:`words `. + +:: + + sage: W = Words([0, 1]); W + Words over {0, 1} + +Let us take the inverter from the previous section and feed some +finite word into it:: + + sage: w = W([1, 1, 0, 1]); w + word: 1101 + sage: inverter(w) + word: 0010 + +We see that the output is again a word (this is a consequence of +calling :meth:`~Transducer.process` with ``automatic_output_type``). + +We can even input something infinite like an infinite word:: + + sage: tm = words.ThueMorseWord(); tm + word: 0110100110010110100101100110100110010110... + sage: inverter(tm) + word: 1001011001101001011010011001011001101001... + + A transducer which performs division by `3` in binary ----------------------------------------------------- @@ -811,6 +908,7 @@ :trac:`16229`, :trac:`16253`, :trac:`16254`, :trac:`16255`, :trac:`16266`, :trac:`16355`, :trac:`16357`, :trac:`16387`, :trac:`16425`, :trac:`16539`, :trac:`16555`, :trac:`16557`, :trac:`16588`, :trac:`16589`, :trac:`16666`, :trac:`16668`, :trac:`16674`, :trac:`16675`, :trac:`16677`. +- Daniel Krenn (2015-09-14): cleanup :trac:`18227` ACKNOWLEDGEMENT: @@ -821,9 +919,9 @@ ======= """ #***************************************************************************** -# Copyright (C) 2012--2014 Clemens Heuberger -# 2012--2014 Daniel Krenn -# 2012--2014 Sara Kropf +# Copyright (C) 2012--2015 Clemens Heuberger +# 2012--2015 Daniel Krenn +# 2012--2015 Sara Kropf # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -831,28 +929,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.sage_object import SageObject -from sage.graphs.digraph import DiGraph -from sage.matrix.constructor import matrix -from sage.rings.integer_ring import ZZ -from sage.rings.real_mpfr import RR -from sage.symbolic.ring import SR -from sage.misc.cachefunc import cached_function -from sage.misc.latex import latex -from sage.misc.misc import verbose -from sage.misc.misc import srange -from sage.functions.trig import cos, sin, atan2 -from sage.symbolic.constants import pi -from sage.symbolic.ring import SR - -from copy import copy -from copy import deepcopy - -import itertools -from itertools import imap, ifilter, izip import collections -from collections import defaultdict, OrderedDict -import heapq +import itertools +import sage def full_group_by(l, key=lambda x: x): @@ -909,7 +988,7 @@ def full_group_by(l, key=lambda x: x): Here, the result ``r`` has been sorted in order to guarantee a consistent order for the doctest suite. """ - elements = defaultdict(list) + elements = collections.defaultdict(list) original_keys = {} for item in l: k = key(item) @@ -1006,6 +1085,7 @@ def startswith(list, prefix): "left": 180, "below": 270} + def FSMLetterSymbol(letter): """ Returns a string associated to the input letter. @@ -1077,7 +1157,7 @@ def is_FSMState(S): return isinstance(S, FSMState) -class FSMState(SageObject): +class FSMState(sage.structure.sage_object.SageObject): """ Class for a state of a finite state machine. @@ -1605,6 +1685,8 @@ def __deepcopy__(self, memo): sage: deepcopy(A) 'A' """ + from copy import deepcopy + try: label = self._deepcopy_relabel_ except AttributeError: @@ -1662,6 +1744,7 @@ def deepcopy(self, memo=None): sage: B.initial_probability is A.initial_probability False """ + from copy import deepcopy return deepcopy(self, memo) @@ -1688,6 +1771,7 @@ def relabeled(self, label, memo=None): 'B' """ + from copy import deepcopy self._deepcopy_relabel_ = label new = deepcopy(self, memo) del self._deepcopy_relabel_ @@ -2043,7 +2127,7 @@ def is_FSMTransition(T): return isinstance(T, FSMTransition) -class FSMTransition(SageObject): +class FSMTransition(sage.structure.sage_object.SageObject): """ Class for a transition of a finite state machine. @@ -2090,6 +2174,7 @@ class FSMTransition(SageObject): word_out = None """Output word of the transition. Read-only.""" + def __init__(self, from_state, to_state, word_in=None, word_out=None, hook=None): @@ -2183,6 +2268,7 @@ def __copy__(self): copy = __copy__ + def __deepcopy__(self, memo): """ Returns a deep copy of the transition. @@ -2202,6 +2288,7 @@ def __deepcopy__(self, memo): sage: deepcopy(t) Transition from 'A' to 'B': 0|- """ + from copy import deepcopy new = FSMTransition(deepcopy(self.from_state, memo), deepcopy(self.to_state, memo), deepcopy(self.word_in, memo), @@ -2231,6 +2318,7 @@ def deepcopy(self, memo=None): sage: deepcopy(t) Transition from 'A' to 'B': 0|- """ + from copy import deepcopy return deepcopy(self, memo) @@ -2526,7 +2614,7 @@ def duplicate_transition_add_input(old_transition, new_transition): return old_transition -class FiniteStateMachine(SageObject): +class FiniteStateMachine(sage.structure.sage_object.SageObject): """ Class for a finite state machine. @@ -2887,6 +2975,38 @@ class FiniteStateMachine(SageObject): ValueError: with_final_word_out cannot be specified when copying another finite state machine. + :trac:`19454` rewrote automatic detection of the alphabets:: + + sage: def transition_function(state, letter): + ....: return (0, 3 + letter) + sage: T1 = Transducer(transition_function, + ....: input_alphabet=[0, 1], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T1.output_alphabet + [3, 4] + sage: T2 = Transducer([(0, 0, 0, 3), (0, 0, 0, 4)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T2.output_alphabet + [3, 4] + sage: T = Transducer([(0, 0, 1, 2)]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], determine_alphabets=False) + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2, 3]) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1], + ....: output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2, 3]) + .. automethod:: __call__ """ @@ -2901,7 +3021,7 @@ class FiniteStateMachine(SageObject): .. SEEALSO:: :class:`FiniteStateMachine`, :meth:`is_Markov_chain`, - :meth:`markov_chain_simplification` + :meth:`markov_chain_simplification`. """ input_alphabet = None @@ -2917,7 +3037,7 @@ class FiniteStateMachine(SageObject): .. SEEALSO:: :class:`FiniteStateMachine`, :meth:`determine_alphabets`, - :attr:`output_alphabet` + :attr:`output_alphabet`. """ output_alphabet = None @@ -2932,8 +3052,9 @@ class FiniteStateMachine(SageObject): .. SEEALSO:: - :class:`FiniteStateMachine`, :meth:`determine_alphabets`, - :attr:`input_alphabet` + :class:`FiniteStateMachine`, + :meth:`determine_alphabets`, + :attr:`input_alphabet`. """ #************************************************************************* @@ -3055,9 +3176,6 @@ def __init__(self, self.add_transition(L) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__iter__'): # data is a something that is iterable, # items are transitions @@ -3070,15 +3188,17 @@ def __init__(self, self.add_transition(transition) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__call__'): self.add_from_transition_function(data) else: raise TypeError('Cannot decide what to do with data.') - if determine_alphabets: + if determine_alphabets is None and data is not None: + if input_alphabet is None: + self.determine_input_alphabet() + if output_alphabet is None: + self.determine_output_alphabet() + elif determine_alphabets: self.determine_alphabets() if with_final_word_out is not None: @@ -3218,8 +3338,10 @@ def deepcopy(self, memo=None): True """ + from copy import deepcopy return deepcopy(self, memo) + def _copy_from_other_(self, other, memo=None, empty=False): """ Copy all data from other to self, to be used in the constructor. @@ -3242,6 +3364,7 @@ def _copy_from_other_(self, other, memo=None, empty=False): sage: A == B True """ + from copy import deepcopy if memo is None: memo = {} self.input_alphabet = deepcopy(other.input_alphabet, memo) @@ -3312,6 +3435,8 @@ def relabeled(self, memo=None, labels=None): ... TypeError: labels must be None, a callable or a dictionary. """ + from copy import deepcopy + self._deepcopy_relabel_ = True self._deepcopy_labels_ = labels new = deepcopy(self, memo) @@ -3359,6 +3484,8 @@ def induced_sub_finite_state_machine(self, states): True """ + from copy import deepcopy + good_states = set() for state in states: if not self.has_state(state): @@ -3412,7 +3539,7 @@ def __hash__(self): def __or__(self, other): """ - Returns the disjoint union of the finite state machines self and other. + Return the disjoint union of this and another finite state machine. INPUT: @@ -3422,18 +3549,30 @@ def __or__(self, other): A new finite state machine. + .. SEEALSO:: + + :meth:`.disjoint_union`, :meth:`.__and__`, + :meth:`Automaton.intersection`, + :meth:`Transducer.intersection`. + TESTS:: sage: FiniteStateMachine() | FiniteStateMachine([('A', 'B')]) + Finite state machine with 2 states + sage: FiniteStateMachine() | 42 Traceback (most recent call last): ... - NotImplementedError + TypeError: Can only add finite state machine """ if is_FiniteStateMachine(other): return self.disjoint_union(other) + else: + raise TypeError("Can only add finite state machine") + __add__ = __or__ + def __iadd__(self, other): """ TESTS:: @@ -3477,18 +3616,18 @@ def __imul__(self, other): def __call__(self, *args, **kwargs): """ - Call either method :meth:`.composition` or :meth:`.process` (with - ``full_output=False``). See the documentation of these functions for - possible parameters. - - EXAMPLES:: + Call either method :meth:`.composition` or :meth:`.process` + (with ``full_output=False``). If the input is not finite + (``is_finite`` of input is ``False``), then + :meth:`.iter_process` (with ``iterator_type='simple'``) is + called. Moreover, the flag ``automatic_output_type`` is set + (unless ``format_output`` is specified). + See the documentation of these functions for possible + parameters. - sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, - ....: initial_states=['A'], final_states=['A']) - sage: binary_inverter([0, 1, 0, 0, 1, 1]) - [1, 0, 1, 1, 0, 0] + EXAMPLES: - :: + The following code performs a :meth:`composition`:: sage: F = Transducer([('A', 'B', 1, 0), ('B', 'B', 1, 1), ....: ('B', 'B', 0, 0)], @@ -3500,6 +3639,28 @@ def __call__(self, *args, **kwargs): sage: H.states() [('A', 1), ('B', 1), ('B', 2)] + An automaton or transducer can also act on an input (an list + or other iterable of letters):: + + sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, + ....: initial_states=['A'], final_states=['A']) + sage: binary_inverter([0, 1, 0, 0, 1, 1]) + [1, 0, 1, 1, 0, 0] + + We can also let them act on :doc:`words `:: + + sage: W = Words([0, 1]); W + Words over {0, 1} + sage: binary_inverter(W([0, 1, 1, 0, 1, 1])) + word: 100100 + + Infinite words work as well:: + + sage: words.FibonacciWord() + word: 0100101001001010010100100101001001010010... + sage: binary_inverter(words.FibonacciWord()) + word: 1011010110110101101011011010110110101101... + When only one successful path is found in a non-deterministic transducer, the result of that path is returned. @@ -3516,6 +3677,7 @@ def __call__(self, *args, **kwargs): :meth:`.composition`, :meth:`~FiniteStateMachine.process`, + :meth:`~FiniteStateMachine.iter_process`, :meth:`Automaton.process`, :meth:`Transducer.process`. @@ -3664,6 +3826,25 @@ def __call__(self, *args, **kwargs): ValueError: Invalid input sequence. sage: T([2, 4], format_output=f, list_of_outputs=True) [None, None] + + :: + + sage: from itertools import islice + sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, + ....: initial_states=['A'], final_states=['A']) + sage: inverter(words.FibonacciWord()) + word: 1011010110110101101011011010110110101101... + sage: inverter(words.FibonacciWord(), automatic_output_type=True) + word: 1011010110110101101011011010110110101101... + sage: tuple(islice(inverter(words.FibonacciWord(), + ....: automatic_output_type=False), 10)) + (1, 0, 1, 1, 0, 1, 0, 1, 1, 0) + sage: type(inverter((1, 0, 1, 1, 0, 1, 0, 1, 1, 0), + ....: automatic_output_type=False)) + + sage: type(inverter((1, 0, 1, 1, 0, 1, 0, 1, 1, 0), + ....: automatic_output_type=True)) + """ if len(args) == 0: raise TypeError("Called with too few arguments.") @@ -3674,6 +3855,14 @@ def __call__(self, *args, **kwargs): kwargs['full_output'] = False if not 'list_of_outputs' in kwargs: kwargs['list_of_outputs'] = False + if not 'automatic_output_type' in kwargs: + kwargs['automatic_output_type'] = not 'format_output' in kwargs + input_tape = args[0] + if hasattr(input_tape, 'is_finite') and \ + input_tape.is_finite() == False: + if not 'iterator_type' in kwargs: + kwargs['iterator_type'] = 'simple' + return self.iter_process(*args, **kwargs) return self.process(*args, **kwargs) raise TypeError("Do not know what to do with that arguments.") @@ -4013,7 +4202,7 @@ def _repr_(self): else: return "Finite state machine with %s states" % len(self._states_) - default_format_letter = latex + default_format_letter = sage.misc.latex.latex format_letter = default_format_letter @@ -4043,10 +4232,11 @@ def format_letter_negative(self, letter): \path[->] (v0) edge[loop above] node {$\overline{1}$} (); \end{tikzpicture} """ + from sage.rings.integer_ring import ZZ if letter in ZZ and letter < 0: return r'\overline{%d}' % -letter else: - return latex(letter) + return sage.misc.latex.latex(letter) def format_transition_label_reversed(self, word): @@ -4086,7 +4276,7 @@ def format_transition_label_reversed(self, word): TEST: Check that :trac:`16357` is fixed:: - + sage: T = Transducer() sage: T.format_transition_label_reversed([]) '\\varepsilon' @@ -4174,12 +4364,13 @@ def default_format_transition_label(self, word): sage: T.default_format_transition_label(iter([])) '\\varepsilon' """ - result = " ".join(imap(self.format_letter, word)) + result = " ".join(itertools.imap(self.format_letter, word)) if result: return result else: return EmptyWordLaTeX + format_transition_label = default_format_transition_label @@ -4531,7 +4722,7 @@ def latex_options(self, state.accepting_where = where elif hasattr(state, 'final_word_out') \ and state.final_word_out: - if where in RR: + if where in sage.rings.real_mpfr.RR: state.accepting_where = where else: raise ValueError('accepting_where for %s must ' @@ -4590,6 +4781,9 @@ def _latex_(self): \path[->] (v4) edge[loop above] node {$\varepsilon\mid \varepsilon$} (); \end{tikzpicture} """ + from sage.functions.trig import sin, cos + from sage.symbolic.constants import pi + def label_rotation(angle, both_directions): """ Given an angle of a transition, compute the TikZ string to @@ -4671,7 +4865,7 @@ def label_rotation(angle, both_directions): elif hasattr(self, "format_state_label"): label = self.format_state_label(vertex) else: - label = latex(vertex.label()) + label = sage.misc.latex.latex(vertex.label()) result += "\\node[state%s] (v%d) at (%f, %f) {$%s$};\n" % ( options, j, vertex.coordinates[0], vertex.coordinates[1], label) @@ -4698,7 +4892,7 @@ def key_function(s): # transitions have to be sorted anyway, the performance # penalty should be bearable; nevertheless, this is only # required for doctests. - adjacent = OrderedDict( + adjacent = collections.OrderedDict( (pair, list(transitions)) for pair, transitions in itertools.groupby( @@ -4718,7 +4912,7 @@ def key_function(s): transition, self.format_transition_label)) label = ", ".join(labels) if source != target: - angle = atan2( + angle = sage.functions.trig.atan2( target.coordinates[1] - source.coordinates[1], target.coordinates[0] - source.coordinates[0]) * 180/pi both_directions = (target, source) in adjacent @@ -4748,7 +4942,8 @@ def key_function(s): return result - def _latex_transition_label_(self, transition, format_function=latex): + def _latex_transition_label_(self, transition, + format_function=sage.misc.latex.latex): r""" Returns the proper transition label. @@ -4771,6 +4966,7 @@ def _latex_transition_label_(self, transition, format_function=latex): """ return ' ' + def set_coordinates(self, coordinates, default=True): """ Set coordinates of the states for the LaTeX representation by @@ -4803,6 +4999,9 @@ def set_coordinates(self, coordinates, default=True): sage: F.state(2).coordinates (2, 1) """ + from sage.functions.trig import sin, cos + from sage.symbolic.constants import pi + states_without_coordinates = [] for state in self.iter_states(): try: @@ -4930,8 +5129,10 @@ def adjacency_matrix(self, input=None, [1 1 0] """ + from sage.rings.integer_ring import ZZ + def default_function(transitions): - x = SR.var('x') + x = sage.symbolic.ring.SR.var('x') return x**sum(transition.word_out) if entry is None: @@ -4956,30 +5157,31 @@ def default_function(transitions): dictionary[(transition.from_state.label(), transition.to_state.label())] \ = entry(transition) - return matrix(len(relabeledFSM.states()), dictionary) + return sage.matrix.constructor.matrix( + len(relabeledFSM.states()), dictionary) - def determine_alphabets(self, reset=True): + def determine_input_alphabet(self, reset=True): """ - Determines the input and output alphabet according to the - transitions in self. + Determine the input alphabet according to the transitions + of this finite state machine. INPUT: - - ``reset`` -- If reset is ``True``, then the existing input - and output alphabets are erased, otherwise new letters are - appended to the existing alphabets. + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing input alphabet is erased, otherwise new letters are + appended to the existing alphabet. OUTPUT: Nothing. - After this operation the input alphabet and the output - alphabet of self are a list of letters. + After this operation the input alphabet of this finite state machine + is a list of letters. .. TODO:: - At the moment, the letters of the alphabets need to be hashable. + At the moment, the letters of the alphabet need to be hashable. EXAMPLES:: @@ -4987,32 +5189,126 @@ def determine_alphabets(self, reset=True): ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) - sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) - sage: T.determine_alphabets() + sage: T.determine_input_alphabet() sage: (T.input_alphabet, T.output_alphabet) - ([0, 1, 2], [0, 1, 4]) - """ + ([0, 1, 2], None) + + .. SEEALSO:: + + :meth:`determine_output_alphabet`, + :meth:`determine_alphabets`. + """ if reset: ain = set() - aout = set() else: ain = set(self.input_alphabet) - aout = set(self.output_alphabet) for t in self.iter_transitions(): for letter in t.word_in: ain.add(letter) + self.input_alphabet = list(ain) + + + def determine_output_alphabet(self, reset=True): + """ + Determine the output alphabet according to the transitions + of this finite state machine. + + INPUT: + + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing output alphabet is erased, otherwise new letters are + appended to the existing alphabet. + + OUTPUT: + + Nothing. + + After this operation the output alphabet of this finite state machine + is a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabet need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_output_alphabet() + sage: (T.input_alphabet, T.output_alphabet) + (None, [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_alphabets`. + """ + if reset: + aout = set() + else: + aout = set(self.output_alphabet) + + for t in self.iter_transitions(): for letter in t.word_out: aout.add(letter) for s in self.iter_final_states(): for letter in s.final_word_out: aout.add(letter) - self.input_alphabet = list(ain) self.output_alphabet = list(aout) + def determine_alphabets(self, reset=True): + """ + Determine the input and output alphabet according to the + transitions in this finite state machine. + + INPUT: + + - ``reset`` -- If reset is ``True``, then the existing input + and output alphabets are erased, otherwise new letters are + appended to the existing alphabets. + + OUTPUT: + + Nothing. + + After this operation the input alphabet and the output + alphabet of this finite state machine are a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabets need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_alphabets() + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1, 2], [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_output_alphabet`. + """ + self.determine_input_alphabet(reset) + self.determine_output_alphabet(reset) + + #************************************************************************* # get states and transitions #************************************************************************* @@ -5037,6 +5333,7 @@ def states(self): ['1', '2'] """ + from copy import copy return copy(self._states_) @@ -5475,7 +5772,7 @@ def has_final_states(self): def is_deterministic(self): """ - Returns whether the finite finite state machine is deterministic. + Return whether the finite finite state machine is deterministic. INPUT: @@ -5489,9 +5786,10 @@ def is_deterministic(self): each transition has input label of length one and for each pair `(q,a)` where `q` is a state and `a` is an element of the input alphabet, there is at most one transition from `q` with - input label `a`. + input label `a`. Furthermore, the finite state may not have + more than one initial state. - TESTS:: + EXAMPLES:: sage: fsm = FiniteStateMachine() sage: fsm.add_transition(('A', 'B', 0, [])) @@ -5506,7 +5804,18 @@ def is_deterministic(self): Transition from 'A' to 'B': 0,1|- sage: fsm.is_deterministic() False + + Check that :trac:`18556` is fixed:: + + sage: Automaton().is_deterministic() + True + sage: Automaton(initial_states=[0]).is_deterministic() + True + sage: Automaton(initial_states=[0, 1]).is_deterministic() + False """ + if len(self.initial_states())>1: + return False for state in self.iter_states(): for transition in state.transitions: if len(transition.word_in) != 1: @@ -5607,7 +5916,9 @@ def is_connected(self): _process_default_options_ = {'full_output': True, 'list_of_outputs': None, 'only_accepted': False, - 'always_include_output': False} + 'always_include_output': False, + 'automatic_output_type': False} + def process(self, *args, **kwargs): """ @@ -5668,6 +5979,23 @@ def process(self, *args, **kwargs): process iterator is activated. See also the notes below for multi-tape machines. + - ``process_all_prefixes_of_input`` -- (default: ``False``) a + boolean. If ``True``, then each prefix of the input word is + processed (instead of processing the whole input word at + once). Consequently, there is an output generated for each + of these prefixes. + + - ``process_iterator_class`` -- (default: ``None``) a class + inherited from :class:`FSMProcessIterator`. If ``None``, + then :class:`FSMProcessIterator` is taken. An instance of this + class is created and is used during the processing. + + - ``automatic_output_type`` -- (default: ``False``) a boolean. + If set and the input has a parent, then the + output will have the same parent. If the input does not have + a parent, then the output will be of the same type as the + input. + OUTPUT: A triple (or a list of triples, @@ -5741,7 +6069,7 @@ def process(self, *args, **kwargs): both nonzero (see also the example on :ref:`non-adjacent forms ` in the documentation of the module - :mod:`~sage.combinat.finite_state_machine`):: + :doc:`finite_state_machine`):: sage: NAF = FiniteStateMachine( ....: {'_': [('_', 0), (1, 1)], 1: [('_', 0)]}, @@ -5894,6 +6222,8 @@ def process(self, *args, **kwargs): sage: T.process([3]) (False, None, None) """ + from copy import copy + # set default values options = copy(self._process_default_options_) options.update(kwargs) @@ -5969,16 +6299,94 @@ def _process_convert_output_(self, output_data, **kwargs): return (accept_input, current_state, output) - def iter_process(self, input_tape=None, initial_state=None, **kwargs): - """ - This function returns an instance of - :class:`FSMProcessIterator`. See :meth:`.process` (which runs - this iterator until the end) for more information. + def iter_process(self, input_tape=None, initial_state=None, + process_iterator_class=None, + iterator_type=None, + automatic_output_type=False, **kwargs): + r""" + This function returns an iterator for processing the input. + See :meth:`.process` (which runs this iterator until the end) + for more information. - EXAMPLES:: + INPUT: + + - ``iterator_type`` -- If ``None`` (default), then + an instance of :class:`FSMProcessIterator` is returned. If + this is ``'simple'`` only an iterator over one output is + returned (an exception is raised if this is not the case, i.e., + if the process has branched). + + See :meth:`process` for a description of the other parameters. + + OUTPUT: + + An iterator. + + EXAMPLES: + + We can use :meth:`iter_process` to deal with infinite words:: sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) + sage: words.FibonacciWord() + word: 0100101001001010010100100101001001010010... + sage: it = inverter.iter_process( + ....: words.FibonacciWord(), iterator_type='simple') + sage: Words([0,1])(it) + word: 1011010110110101101011011010110110101101... + + This can also be done by:: + + sage: inverter.iter_process(words.FibonacciWord(), + ....: iterator_type='simple', + ....: automatic_output_type=True) + word: 1011010110110101101011011010110110101101... + + or even simpler by:: + + sage: inverter(words.FibonacciWord()) + word: 1011010110110101101011011010110110101101... + + To see what is going on, we use :meth:`iter_process` without + arguments:: + + sage: from itertools import islice + sage: it = inverter.iter_process(words.FibonacciWord()) + sage: for current in islice(it, 4): + ....: print current + process (1 branch) + + at state 'A' + +-- tape at 1, [[1]] + process (1 branch) + + at state 'A' + +-- tape at 2, [[1, 0]] + process (1 branch) + + at state 'A' + +-- tape at 3, [[1, 0, 1]] + process (1 branch) + + at state 'A' + +-- tape at 4, [[1, 0, 1, 1]] + + The following show the difference between using the ``'simple'``-option + and not using it. With this option, we have + :: + + sage: it = inverter.iter_process(input_tape=[0, 1, 1], + ....: iterator_type='simple') + sage: for i, o in enumerate(it): + ....: print 'step %s: output %s' % (i, o) + step 0: output 1 + step 1: output 0 + step 2: output 0 + + So :meth:`iter_process` is a generator expression which gives + a new output letter in each step (and not more). In many cases + this is sufficient. + + Doing the same without the ``'simple'``-option does not give + the output directly; it has to be extracted first. On the + other hand, additional information is presented:: + sage: it = inverter.iter_process(input_tape=[0, 1, 1]) sage: for current in it: ....: print current @@ -5993,7 +6401,29 @@ def iter_process(self, input_tape=None, initial_state=None, **kwargs): +-- tape at 3, [[1, 0, 0]] process (0 branches) sage: it.result() - [(True, 'A', [1, 0, 0])] + [Branch(accept=True, state='A', output=[1, 0, 0])] + + One can see the growing of the output (the list of lists at + the end of each entry). + + Even if the transducer has transitions with empty or multiletter + output, the simple iterator returns one new output letter in + each step:: + + sage: T = Transducer([(0, 0, 0, []), + ....: (0, 0, 1, [1]), + ....: (0, 0, 2, [2, 2])], + ....: initial_states=[0]) + sage: it = T.iter_process(input_tape=[0, 1, 2, 0, 1, 2], + ....: iterator_type='simple') + sage: for i, o in enumerate(it): + ....: print 'step %s: output %s' % (i, o) + step 0: output 1 + step 1: output 2 + step 2: output 2 + step 3: output 1 + step 4: output 2 + step 5: output 2 .. SEEALSO:: @@ -6003,43 +6433,153 @@ def iter_process(self, input_tape=None, initial_state=None, **kwargs): :meth:`~FiniteStateMachine.__call__`, :class:`FSMProcessIterator`. """ - return FSMProcessIterator(self, - input_tape=input_tape, - initial_state=initial_state, - **kwargs) - - - #************************************************************************* - # change finite state machine (add/remove state/transitions) - #************************************************************************* + if automatic_output_type and kwargs.has_key('format_output'): + raise ValueError("Parameter 'automatic_output_type' set, but " + "'format_output' specified as well.") + if automatic_output_type: + try: + kwargs['format_output'] = input_tape.parent() + except AttributeError: + kwargs['format_output'] = type(input_tape) + + if process_iterator_class is None: + process_iterator_class = FSMProcessIterator + it = process_iterator_class(self, + input_tape=input_tape, + initial_state=initial_state, + **kwargs) + if iterator_type is None: + return it + elif iterator_type == 'simple': + simple_it = self._iter_process_simple_(it) + try: + return kwargs['format_output'](simple_it) + except KeyError: + return simple_it + else: + raise ValueError('Iterator type %s unknown.' % (iterator_type,)) - def add_state(self, state): - """ - Adds a state to the finite state machine and returns the new - state. If the state already exists, that existing state is - returned. + def _iter_process_simple_(self, iterator): + r""" + Converts a :class:`process iterator ` to a simpler + iterator, which only outputs the written letters. INPUT: - - ``state`` is either an instance of - :class:`FSMState` or, - otherwise, a label of a state. + - ``iterator`` -- in instance of :class:`FSMProcessIterator`. OUTPUT: - The new or existing state. + A generator. + + An exception is raised if the process branches. EXAMPLES:: - sage: from sage.combinat.finite_state_machine import FSMState - sage: F = FiniteStateMachine() - sage: A = FSMState('A', is_initial=True) - sage: F.add_state(A) - 'A' - """ - try: - return self.state(state) + sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, + ....: initial_states=['A'], final_states=['A']) + sage: it = inverter.iter_process(words.FibonacciWord()[:10]) + sage: it_simple = inverter._iter_process_simple_(it) + sage: list(it_simple) + [1, 0, 1, 1, 0, 1, 0, 1, 1, 0] + + .. SEEALSO:: + + :meth:`iter_process`, + :meth:`FiniteStateMachine.process`, + :meth:`Automaton.process`, + :meth:`Transducer.process`, + :meth:`~FiniteStateMachine.__call__`, + :class:`FSMProcessIterator`. + + TESTS:: + + sage: T = Transducer([(0, 0, [0, 0], 0), (0, 1, 0, 0)], + ....: initial_states=[0], final_states=[0]) + sage: list(T.iter_process([0, 0], iterator_type='simple')) + Traceback (most recent call last): + ... + RuntimeError: Process has branched (2 branches exist). + The 'simple' iterator cannot be used here. + sage: T = Transducer([(0, 0, 0, 0), (0, 1, 0, 0)], + ....: initial_states=[0], final_states=[0]) + sage: list(T.iter_process([0], iterator_type='simple')) + Traceback (most recent call last): + ... + RuntimeError: Process has branched (visiting 2 states in branch). + The 'simple' iterator cannot be used here. + sage: T = Transducer([(0, 1, 0, 1), (0, 1, 0, 2)], + ....: initial_states=[0], final_states=[0]) + sage: list(T.iter_process([0], iterator_type='simple')) + Traceback (most recent call last): + ... + RuntimeError: Process has branched. (2 different outputs in branch). + The 'simple' iterator cannot be used here. + """ + for current in iterator: + if not current: + return + + if len(current) > 1: + raise RuntimeError("Process has branched " + "(%s branches exist). The " + "'simple' iterator cannot be used " + "here." % + (len(current),)) + pos, states = next(current.iteritems()) + if len(states) > 1: + raise RuntimeError("Process has branched " + "(visiting %s states in branch). The " + "'simple' iterator cannot be used " + "here." % + (len(states),)) + state, branch = next(states.iteritems()) + if len(branch.outputs) > 1: + raise RuntimeError("Process has branched. " + "(%s different outputs in branch). The " + "'simple' iterator cannot be used " + "here." % + (len(branch.outputs),)) + + for o in branch.outputs[0]: + yield o + branch.outputs[0] = [] # Reset output so that in the next round + # (of "for current in iterator") only new + # output is returned (by the yield). + + + #************************************************************************* + # change finite state machine (add/remove state/transitions) + #************************************************************************* + + + def add_state(self, state): + """ + Adds a state to the finite state machine and returns the new + state. If the state already exists, that existing state is + returned. + + INPUT: + + - ``state`` is either an instance of + :class:`FSMState` or, + otherwise, a label of a state. + + OUTPUT: + + The new or existing state. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: F = FiniteStateMachine() + sage: A = FSMState('A', is_initial=True) + sage: F.add_state(A) + 'A' + """ + try: + return self.state(state) except LookupError: pass # at this point we know that we have a new state @@ -6577,7 +7117,7 @@ def epsilon_successors(self, state): def accessible_components(self): """ - Returns a new finite state machine with the accessible states + Return a new finite state machine with the accessible states of self and all transitions between those states. INPUT: @@ -6607,6 +7147,9 @@ def accessible_components(self): sage: F.accessible_components() Automaton with 1 state + .. SEEALSO:: + :meth:`coaccessible_components` + TESTS: Check whether input of length > 1 works:: @@ -6616,6 +7159,7 @@ def accessible_components(self): sage: F.accessible_components() Automaton with 3 states """ + from copy import deepcopy if len(self.initial_states()) == 0: return deepcopy(self) @@ -6638,6 +7182,47 @@ def accessible(from_state, read): return result + def coaccessible_components(self): + r""" + Return the sub-machine induced by the coaccessible states of this + finite state machine. + + OUTPUT: + + A finite state machine of the same type as this finite state + machine. + + EXAMPLES:: + + sage: A = automata.ContainsWord([1, 1], + ....: input_alphabet=[0, 1]).complement().minimization().relabeled() + sage: A.transitions() + [Transition from 0 to 0: 0|-, + Transition from 0 to 0: 1|-, + Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-, + Transition from 2 to 0: 1|-] + sage: A.initial_states() + [1] + sage: A.final_states() + [1, 2] + sage: C = A.coaccessible_components() + sage: C.transitions() + [Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-] + + .. SEEALSO:: + :meth:`accessible_components`, + :meth:`induced_sub_finite_state_machine` + """ + DG = self.digraph().reverse() + coaccessible_states = DG.breadth_first_search( + [_.label() for _ in self.iter_final_states()]) + return self.induced_sub_finite_state_machine( + [self.state(_) for _ in coaccessible_states]) + # ************************************************************************* # creating new finite state machines # ************************************************************************* @@ -6645,40 +7230,459 @@ def accessible(from_state, read): def disjoint_union(self, other): """ - TESTS:: + Return the disjoint union of this and another finite state + machine. + + INPUT: + + - ``other`` -- a :class:`FiniteStateMachine`. + + OUTPUT: + + A finite state machine of the same type as this finite state + machine. - sage: F = FiniteStateMachine([('A', 'A')]) - sage: FiniteStateMachine().disjoint_union(F) + In general, the disjoint union of two finite state machines is + non-deterministic. In the case of a automata, the language + accepted by the disjoint union is the union of the languages + accepted by the constituent automata. In the case of + transducer, for each successful path in one of the constituent + transducers, there will be one successful path with the same input + and output labels in the disjoint union. + + The labels of the states of the disjoint union are pairs ``(i, + s)``: for each state ``s`` of this finite state machine, there + is a state ``(0, s)`` in the disjoint union; for each state + ``s`` of the other finite state machine, there is a state ``(1, + s)`` in the disjoint union. + + The input alphabet is the union of the input alphabets (if + possible) and ``None`` otherwise. In the latter case, try + calling :meth:`.determine_alphabets`. + + The disjoint union can also be written as ``A + B`` or ``A | B``. + + EXAMPLES:: + + sage: A = Automaton([(0, 1, 0), (1, 0, 1)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: A([0, 1, 0, 1]) + True + sage: B = Automaton([(0, 1, 0), (1, 2, 0), (2, 0, 1)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: B([0, 0, 1]) + True + sage: C = A.disjoint_union(B) + sage: C + Automaton with 5 states + sage: C.transitions() + [Transition from (0, 0) to (0, 1): 0|-, + Transition from (0, 1) to (0, 0): 1|-, + Transition from (1, 0) to (1, 1): 0|-, + Transition from (1, 1) to (1, 2): 0|-, + Transition from (1, 2) to (1, 0): 1|-] + sage: C([0, 0, 1]) + True + sage: C([0, 1, 0, 1]) + True + sage: C([1]) + False + sage: C.initial_states() + [(0, 0), (1, 0)] + + Instead of ``.disjoint_union``, alternative notations are + available:: + + sage: C1 = A + B + sage: C1 == C + True + sage: C2 = A | B + sage: C2 == C + True + + In general, the disjoint union is not deterministic.:: + + sage: C.is_deterministic() + False + sage: D = C.determinisation().minimization() + sage: D.is_equivalent(Automaton([(0, 0, 0), (0, 0, 1), + ....: (1, 7, 0), (1, 0, 1), (2, 6, 0), (2, 0, 1), + ....: (3, 5, 0), (3, 0, 1), (4, 0, 0), (4, 2, 1), + ....: (5, 0, 0), (5, 3, 1), (6, 4, 0), (6, 0, 1), + ....: (7, 4, 0), (7, 3, 1)], + ....: initial_states=[1], + ....: final_states=[1, 2, 3])) + True + + Disjoint union of transducers:: + + sage: T1 = Transducer([(0, 0, 0, 1)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T2 = Transducer([(0, 0, 0, 2)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T1([0]) + [1] + sage: T2([0]) + [2] + sage: T = T1.disjoint_union(T2) + sage: T([0]) Traceback (most recent call last): ... - NotImplementedError + ValueError: Found more than one accepting path. + sage: T.process([0]) + [(True, (1, 0), [2]), (True, (0, 0), [1])] + + Handling of the input alphabet (see :trac:`18989`):: + + sage: A = Automaton([(0, 0, 0)]) + sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) + sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) + sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) + sage: A.input_alphabet + [0] + sage: B.input_alphabet + [1, 2] + sage: C.input_alphabet is None + True + sage: D.input_alphabet + [[0, 0]] + sage: (A + B).input_alphabet + [0, 1, 2] + sage: (A + C).input_alphabet is None + True + sage: (A + D).input_alphabet is None + True + + .. SEEALSO:: + + :meth:`Automaton.intersection`, + :meth:`Transducer.intersection`, + :meth:`.determine_alphabets`. """ - raise NotImplementedError + result = self.empty_copy() + for s in self.iter_states(): + result.add_state(s.relabeled((0, s))) + for s in other.iter_states(): + result.add_state(s.relabeled((1, s))) + for t in self.iter_transitions(): + result.add_transition((0, t.from_state), + (0, t.to_state), + t.word_in, + t.word_out) + for t in other.iter_transitions(): + result.add_transition((1, t.from_state), + (1, t.to_state), + t.word_in, + t.word_out) + try: + result.input_alphabet = list(set(self.input_alphabet) + | set(other.input_alphabet)) + except TypeError: + # e.g. None or unhashable letters + result.input_alphabet = None + + return result def concatenation(self, other): """ + Concatenate this finite state machine with another finite + state machine. + + INPUT: + + - ``other`` -- a :class:`FiniteStateMachine`. + + OUTPUT: + + A :class:`FiniteStateMachine` of the same type as this finite + state machine. + + Assume that both finite state machines are automata. If + `\mathcal{L}_1` is the language accepted by this automaton and + `\mathcal{L}_2` is the language accepted by the other automaton, + then the language accepted by the concatenated automaton is + `\{ w_1w_2 \mid w_1\in\mathcal{L}_1, w_2\in\mathcal{L}_2\}` where + `w_1w_2` denotes the concatenation of the words `w_1` and `w_2`. + + Assume that both finite state machines are transducers and that + this transducer maps words `w_1\in\mathcal{L}_1` to words + `f_1(w_1)` and that the other transducer maps words + `w_2\in\mathcal{L}_2` to words `f_2(w_2)`. Then the concatenated + transducer maps words `w_1w_2` with `w_1\in\mathcal{L}_1` and + `w_2\in\mathcal{L}_2` to `f_1(w_1)f_2(w_2)`. Here, `w_1w_2` and + `f_1(w_1)f_2(w_2)` again denote concatenation of words. + + The input alphabet is the union of the input alphabets (if + possible) and ``None`` otherwise. In the latter case, try + calling :meth:`.determine_alphabets`. + + Instead of ``A.concatenation(B)``, the notation ``A * B`` can be + used. + + EXAMPLES: + + Concatenation of two automata:: + + sage: A = automata.Word([0]) + sage: B = automata.Word([1]) + sage: C = A.concatenation(B) + sage: C.transitions() + [Transition from (0, 0) to (0, 1): 0|-, + Transition from (0, 1) to (1, 0): -|-, + Transition from (1, 0) to (1, 1): 1|-] + sage: [w + ....: for w in ([0, 0], [0, 1], [1, 0], [1, 1]) + ....: if C(w)] + [[0, 1]] + sage: from sage.combinat.finite_state_machine import ( + ....: is_Automaton, is_Transducer) + sage: is_Automaton(C) + True + + Concatenation of two transducers:: + + sage: A = Transducer([(0, 1, 0, 1), (0, 1, 1, 2)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: B = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: C = A.concatenation(B) + sage: C.transitions() + [Transition from (0, 0) to (0, 1): 0|1, + Transition from (0, 0) to (0, 1): 1|2, + Transition from (0, 1) to (1, 0): -|-, + Transition from (1, 0) to (1, 1): 0|1, + Transition from (1, 0) to (1, 1): 1|0] + sage: [(w, C(w)) for w in ([0, 0], [0, 1], [1, 0], [1, 1])] + [([0, 0], [1, 1]), + ([0, 1], [1, 0]), + ([1, 0], [2, 1]), + ([1, 1], [2, 0])] + sage: is_Transducer(C) + True + + + Alternative notation as multiplication:: + + sage: C == A * B + True + + Final output words are taken into account:: + + sage: A = Transducer([(0, 1, 0, 1)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: A.state(1).final_word_out = 2 + sage: B = Transducer([(0, 1, 0, 3)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: B.state(1).final_word_out = 4 + sage: C = A * B + sage: C([0, 0]) + [1, 2, 3, 4] + + Handling of the input alphabet:: + + sage: A = Automaton([(0, 0, 0)]) + sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) + sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) + sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) + sage: A.input_alphabet + [0] + sage: B.input_alphabet + [1, 2] + sage: C.input_alphabet is None + True + sage: D.input_alphabet + [[0, 0]] + sage: (A * B).input_alphabet + [0, 1, 2] + sage: (A * C).input_alphabet is None + True + sage: (A * D).input_alphabet is None + True + + .. SEEALSO:: + + :meth:`~.disjoint_union`, + :meth:`.determine_alphabets`. + TESTS:: - sage: F = FiniteStateMachine([('A', 'A')]) - sage: FiniteStateMachine().concatenation(F) + sage: A = Automaton() + sage: F = FiniteStateMachine() + sage: A * F Traceback (most recent call last): ... - NotImplementedError + TypeError: Cannot concatenate finite state machines of + different types. + sage: F * A + Traceback (most recent call last): + ... + TypeError: Cannot concatenate finite state machines of + different types. + sage: F * 5 + Traceback (most recent call last): + ... + TypeError: A finite state machine can only be concatenated + with a another finite state machine. """ - raise NotImplementedError + if not is_FiniteStateMachine(other): + raise TypeError('A finite state machine can only be concatenated ' + 'with a another finite state machine.') + if is_Automaton(other) != is_Automaton(self): + raise TypeError('Cannot concatenate finite state machines of ' + 'different types.') + result = self.empty_copy() + first_states = {} + second_states = {} + for s in self.iter_states(): + new_state = s.relabeled((0, s.label())) + new_state.final_word_out = None + new_state.is_final = False + first_states[s] = new_state + result.add_state(new_state) + + for s in other.iter_states(): + new_state = s.relabeled((1, s.label())) + new_state.is_initial = False + second_states[s] = new_state + result.add_state(new_state) - def Kleene_closure(self): - """ - TESTS:: + for t in self.iter_transitions(): + result.add_transition(first_states[t.from_state], + first_states[t.to_state], + t.word_in, + t.word_out) + + for t in other.iter_transitions(): + result.add_transition(second_states[t.from_state], + second_states[t.to_state], + t.word_in, + t.word_out) + + for s in self.iter_final_states(): + first_state = first_states[s] + for t in other.iter_initial_states(): + second_state = second_states[t] + result.add_transition(first_state, + second_state, + [], + s.final_word_out) + + try: + result.input_alphabet = list(set(self.input_alphabet) + | set(other.input_alphabet)) + except TypeError: + # e.g. None or unhashable letters + result.input_alphabet = None + + return result + + + __mul__ = concatenation + + + def kleene_star(self): + r""" + Compute the Kleene closure of this finite state machine. + + OUTPUT: + + A :class:`FiniteStateMachine` of the same type as this finite + state machine. + + Assume that this finite state machine is an automaton + recognizing the language `\mathcal{L}`. Then the Kleene star + recognizes the language `\mathcal{L}^*=\{ w_1\ldots w_n \mid + n\ge 0, w_j\in\mathcal{L} \text{ for all } j\}`. - sage: FiniteStateMachine().Kleene_closure() + Assume that this finite state machine is a transducer realizing + a function `f` on some alphabet `\mathcal{L}`. Then the Kleene + star realizes a function `g` on `\mathcal{L}^*` with + `g(w_1\ldots w_n)=f(w_1)\ldots f(w_n)`. + + EXAMPLES: + + Kleene star of an automaton:: + + sage: A = automata.Word([0, 1]) + sage: B = A.kleene_star() + sage: B.transitions() + [Transition from 0 to 1: 0|-, + Transition from 2 to 0: -|-, + Transition from 1 to 2: 1|-] + sage: from sage.combinat.finite_state_machine import ( + ....: is_Automaton, is_Transducer) + sage: is_Automaton(B) + True + sage: [w for w in ([], [0, 1], [0, 1, 0], [0, 1, 0, 1], [0, 1, 1, 1]) + ....: if B(w)] + [[], + [0, 1], + [0, 1, 0, 1]] + + Kleene star of a transducer:: + + sage: T = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: S = T.kleene_star() + sage: S.transitions() + [Transition from 0 to 1: 0|1, + Transition from 0 to 1: 1|0, + Transition from 1 to 0: -|-] + sage: is_Transducer(S) + True + sage: for w in ([], [0], [1], [0, 0], [0, 1]): + ....: print w, S.process(w) + [] (True, 0, []) + [0] [(True, 0, [1]), (True, 1, [1])] + [1] [(True, 0, [0]), (True, 1, [0])] + [0, 0] [(True, 0, [1, 1]), (True, 1, [1, 1])] + [0, 1] [(True, 0, [1, 0]), (True, 1, [1, 0])] + + Final output words are taken into account:: + + sage: T = Transducer([(0, 1, 0, 1)], + ....: initial_states=[0], + ....: final_states=[1]) + sage: T.state(1).final_word_out = 2 + sage: S = T.kleene_star() + sage: S.process([0, 0]) + [(True, 0, [1, 2, 1, 2]), (True, 1, [1, 2, 1, 2])] + + Final output words may lead to undesirable situations if initial + states and final states coincide:: + + sage: T = Transducer(initial_states=[0], final_states=[0]) + sage: T.state(0).final_word_out = 1 + sage: T([]) + [1] + sage: S = T.kleene_star() + sage: S([]) Traceback (most recent call last): ... - NotImplementedError + RuntimeError: State 0 is in an epsilon cycle (no input), but + output is written. """ - raise NotImplementedError + from copy import deepcopy + result = deepcopy(self) + for initial in result.iter_initial_states(): + for final in result.iter_final_states(): + result.add_transition(final, initial, [], final.final_word_out) + + for initial in result.iter_initial_states(): + initial.is_final = True + + return result def intersection(self, other): @@ -6744,7 +7748,9 @@ def product_FiniteStateMachine(self, other, function, ``final_function`` on the constituent states. The color of a new state is the tuple of colors of the - constituent states of ``self`` and ``other``. + constituent states of ``self`` and ``other``. However, + if all constituent states have color ``None``, then + the state has color ``None``, too. EXAMPLES:: @@ -6760,6 +7766,8 @@ def product_FiniteStateMachine(self, other, function, [Transition from ('A', 1) to ('B', 1): 2|-, Transition from ('A', 1) to ('A', 1): 1|-, Transition from ('B', 1) to ('A', 1): 3|-] + sage: [s.color for s in H.iter_states()] + [None, None] sage: H1 = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False) sage: H1.states()[0].label()[0] is F.states()[0] True @@ -6855,8 +7863,8 @@ def product_FiniteStateMachine(self, other, function, sage: A = Automaton([[0, 0, 0]], initial_states=[0]) sage: B = A.product_FiniteStateMachine(A, ....: lambda t1, t2: (0, None)) - sage: B.states()[0].color - (None, None) + sage: B.states()[0].color is None + True sage: B.determinisation() Automaton with 1 state @@ -6946,7 +7954,10 @@ def default_final_function(*args): if all(s.is_final for s in state.label()): state.is_final = True state.final_word_out = final_function(*state.label()) - state.color = tuple(s.color for s in state.label()) + if all(s.color is None for s in state.label()): + state.color = None + else: + state.color = tuple(s.color for s in state.label()) if only_accessible_components: if result.input_alphabet is None: @@ -7347,15 +8358,19 @@ def _composition_explorative_(self, other): Transition from ('B', 1) to ('B', 1): 0|0, Transition from ('B', 1) to ('B', 2): 1|0] - Check that colors are correctly dealt with. In particular, the - new colors have to be hashable such that + Check that colors are correctly dealt with, cf. :trac:`19199`. + In particular, the new colors have to be hashable such that :meth:`Automaton.determinisation` does not fail:: sage: T = Transducer([[0, 0, 0, 0]], initial_states=[0]) sage: A = T.input_projection() sage: B = A.composition(T, algorithm='explorative') + sage: B.states()[0].color is None + True + sage: A.state(0).color = 0 + sage: B = A.composition(T, algorithm='explorative') sage: B.states()[0].color - (None, None) + (None, 0) sage: B.determinisation() Automaton with 1 state """ @@ -7411,7 +8426,10 @@ def composition_transition(states, input): state.is_final = True state.final_word_out = final_output_second[0][2] - state.color = tuple(s.color for s in state.label()) + if all(s.color is None for s in state.label()): + state.color = None + else: + state.color = tuple(s.color for s in state.label()) F.output_alphabet = second.output_alphabet return F @@ -7510,6 +8528,8 @@ def projection(self, what='input'): Transition from 'A' to 'A': 1|-, Transition from 'B' to 'B': 0|-] """ + from copy import copy, deepcopy + new = Automaton() # TODO: use empty_copy() in order to # preserve on_duplicate_transition and future extensions. @@ -7555,14 +8575,15 @@ def projection(self, what='input'): return new - def transposition(self): + def transposition(self, reverse_output_labels=True): """ Returns a new finite state machine, where all transitions of the input finite state machine are reversed. INPUT: - Nothing. + - ``reverse_output_labels`` -- a boolean (default: ``True``): whether to reverse + output labels. OUTPUT: @@ -7582,6 +8603,28 @@ def transposition(self): sage: aut.transposition().initial_states() ['1', '2'] + :: + + sage: A = Automaton([(0, 1, [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: A([1, 0]) + True + sage: A.transposition()([0, 1]) + True + + :: + + sage: T = Transducer([(0, 1, [1, 0], [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: T([1, 0]) + [1, 0] + sage: T.transposition()([0, 1]) + [0, 1] + sage: T.transposition(reverse_output_labels=False)([0, 1]) + [1, 0] + TESTS: @@ -7599,6 +8642,13 @@ def transposition(self): NotImplementedError: Transposition for transducers with final output words is not implemented. """ + from copy import deepcopy + + if reverse_output_labels: + rewrite_output = lambda word: list(reversed(word)) + else: + rewrite_output = lambda word: word + transposition = self.empty_copy() for state in self.iter_states(): @@ -7607,7 +8657,8 @@ def transposition(self): for transition in self.iter_transitions(): transposition.add_transition( transition.to_state.label(), transition.from_state.label(), - transition.word_in, transition.word_out) + list(reversed(transition.word_in)), + rewrite_output(transition.word_out)) for initial in self.iter_initial_states(): state = transposition.state(initial.label()) @@ -7720,6 +8771,160 @@ def final_components(self): if condensation.out_degree(component) == 0] + def completion(self, sink=None): + """ + Return a completion of this finite state machine. + + INPUT: + + - ``sink`` -- either an instance of :class:`FSMState` or a label + for the sink (default: ``None``). If ``None``, the least + available non-zero integer is used. + + OUTPUT: + + A :class:`FiniteStateMachine` of the same type as this finite + state machine. + + The resulting finite state machine is a complete version of this + finite state machine. A finite state machine is considered to + be complete if each transition has an input label of length one + and for each pair `(q, a)` where `q` is a state and `a` is an + element of the input alphabet, there is exactly one transition + from `q` with input label `a`. + + If this finite state machine is already complete, a deep copy is + returned. Otherwise, a new non-final state (usually called a + sink) is created and transitions to this sink are introduced as + appropriate. + + EXAMPLES:: + + sage: F = FiniteStateMachine([(0, 0, 0, 0), + ....: (0, 1, 1, 1), + ....: (1, 1, 0, 0)]) + sage: F.is_complete() + False + sage: G1 = F.completion() + sage: G1.is_complete() + True + sage: G1.transitions() + [Transition from 0 to 0: 0|0, + Transition from 0 to 1: 1|1, + Transition from 1 to 1: 0|0, + Transition from 1 to 2: 1|-, + Transition from 2 to 2: 0|-, + Transition from 2 to 2: 1|-] + sage: G2 = F.completion('Sink') + sage: G2.is_complete() + True + sage: G2.transitions() + [Transition from 0 to 0: 0|0, + Transition from 0 to 1: 1|1, + Transition from 1 to 1: 0|0, + Transition from 1 to 'Sink': 1|-, + Transition from 'Sink' to 'Sink': 0|-, + Transition from 'Sink' to 'Sink': 1|-] + sage: F.completion(1) + Traceback (most recent call last): + ... + ValueError: The finite state machine already contains a state + '1'. + + An input alphabet must be given:: + + sage: F = FiniteStateMachine([(0, 0, 0, 0), + ....: (0, 1, 1, 1), + ....: (1, 1, 0, 0)], + ....: determine_alphabets=False) + sage: F.is_complete() + Traceback (most recent call last): + ... + ValueError: No input alphabet is given. Try calling + determine_alphabets(). + + Non-deterministic machines are not allowed. :: + + sage: F = FiniteStateMachine([(0, 0, 0, 0), (0, 1, 0, 0)]) + sage: F.is_complete() + False + sage: F.completion() + Traceback (most recent call last): + ... + ValueError: The finite state machine must be deterministic. + sage: F = FiniteStateMachine([(0, 0, [0, 0], 0)]) + sage: F.is_complete() + False + sage: F.completion() + Traceback (most recent call last): + ... + ValueError: The finite state machine must be deterministic. + + .. SEEALSO:: + + :meth:`is_complete`, + :meth:`split_transitions`, + :meth:`determine_alphabets`, + :meth:`is_deterministic`. + + TESTS: + + Test the use of an :class:`FSMState` as sink:: + + sage: F = FiniteStateMachine([(0, 0, 0, 0), + ....: (0, 1, 1, 1), + ....: (1, 1, 0, 0)]) + sage: from sage.combinat.finite_state_machine import FSMState + sage: F.completion(FSMState(1)) + Traceback (most recent call last): + ... + ValueError: The finite state machine already contains a state + '1'. + sage: s = FSMState(2) + sage: G = F.completion(s) + sage: G.state(2) is s + True + """ + from copy import deepcopy + result = deepcopy(self) + if result.is_complete(): + return result + if not result.is_deterministic(): + raise ValueError( + "The finite state machine must be deterministic.") + + if sink is not None: + try: + s = result.state(sink) + raise ValueError("The finite state machine already " + "contains a state '%s'." % s.label()) + except LookupError: + pass + else: + from sage.rings.integer_ring import ZZ + sink = 1 + max(itertools.chain( + [-1], + (s.label() for s in result.iter_states() + if s.label() in ZZ))) + + sink_state = result.add_state(sink) + + for state in result.iter_states(): + for transition in state.transitions: + if len(transition.word_in) != 1: + raise ValueError( + "Transitions with input labels of length greater " + "than one are not allowed. Try calling " + "split_transitions().") + + existing = set(transition.word_in[0] + for transition in state.transitions) + for missing in set(result.input_alphabet) - existing: + result.add_transition(state, sink_state, missing) + + return result + + # ************************************************************************* # simplifications # ************************************************************************* @@ -7882,7 +9087,7 @@ def find_common_output(state): + [common_output[0]] found_inbound_transition = True if not found_inbound_transition: - verbose( + sage.misc.misc.verbose( "All transitions leaving state %s have an " "output label with prefix %s. However, " "there is no inbound transition and it is " @@ -8159,7 +9364,7 @@ def merged_transitions(self): sage: T2 is T1 True """ - + from copy import deepcopy def key(transition): return (transition.to_state, transition.word_out) @@ -8484,6 +9689,8 @@ def with_final_word_out(self, letters, allow_non_final=True): ... ValueError: letters is not allowed to be an empty list. """ + from copy import deepcopy + new = deepcopy(self) new.construct_final_word_out(letters, allow_non_final) return new @@ -8618,7 +9825,9 @@ def graph(self, edge_labels='words_in_out'): sage: T.graph() Looped multi-digraph on 1 vertex - .. SEEALSO:: :class:`DiGraph` + .. SEEALSO:: + + :class:`DiGraph` """ if edge_labels == 'words_in_out': label_fct = lambda t:t._in_out_label_() @@ -8637,7 +9846,7 @@ def graph(self, edge_labels='words_in_out'): graph_data.append((t.from_state.label(), t.to_state.label(), label_fct(t))) - G = DiGraph(graph_data, multiedges=True, loops=True) + G = sage.graphs.digraph.DiGraph(graph_data, multiedges=True, loops=True) G.add_vertices(isolated_vertices) return G @@ -8723,7 +9932,107 @@ def predecessors(self, state, valid_input=None): done.append(s) return(done) - def asymptotic_moments(self, variable=SR.symbol('n')): + + def number_of_words(self, variable=sage.symbolic.ring.SR.var('n')): + r""" + Return the number of successful input words of given length. + + INPUT: + + - ``variable`` -- a symbol denoting the length of the words, + by default `n`. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: NAFpm = Automaton([(0, 0, 0), (0, 1, 1), (0, 1, -1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFpm.number_of_words(); N + 4/3*2^n - 1/3*(-1)^n + sage: all(len(list(NAFpm.language(_))) + ....: - len(list(NAFpm.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + sage: NAFp = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFp.number_of_words(); N + 1.170820393249937?*1.618033988749895?^n + - 0.1708203932499369?*(-0.618033988749895?)^n + sage: all(len(list(NAFp.language(_))) + ....: - len(list(NAFp.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + + The adjacency matrix of the following example is a Jordan matrix of size 3 to + the eigenvalue 4:: + + sage: J3 = Automaton([(0, 1, -1), (1, 2, -1)], + ....: initial_states=[0], + ....: final_states=[0, 1, 2]) + sage: for i in range(3): + ....: for j in range(4): + ....: new_transition = J3.add_transition(i, i, j) + sage: J3.adjacency_matrix(entry=lambda t: 1) + [4 1 0] + [0 4 1] + [0 0 4] + sage: N = J3.number_of_words(); N + 1/2*4^(n - 2)*(n - 1)*n + 4^(n - 1)*n + 4^n + sage: all(len(list(J3.language(_))) + ....: - len(list(J3.language(_-1))) == N.subs(n=_) + ....: for _ in range(1, 6)) + True + + TESTS:: + + sage: A = Automaton([(0, 0, 0), (0, 1, 0)], + ....: initial_states=[0]) + sage: A.number_of_words() + Traceback (most recent call last): + ... + NotImplementedError: Finite State Machine must be deterministic. + """ + from sage.matrix.constructor import matrix + from sage.modules.free_module_element import vector + from sage.rings.arith import falling_factorial + from sage.rings.integer_ring import ZZ + from sage.rings.qqbar import QQbar + from sage.symbolic.ring import SR + + def jordan_block_power(block, exponent): + eigenvalue = SR(block[0, 0]) + return matrix(block.nrows(), + block.nrows(), + lambda i, j: eigenvalue**(exponent-(j-i))* + falling_factorial(exponent, j-i)/ZZ(j-i).factorial() + if j>= i else 0) + + def matrix_power(A, exponent): + J, T = A.jordan_form(QQbar, transformation=True) + assert T*J*T.inverse() == A + Jpower = matrix.block_diagonal( + [jordan_block_power(J.subdivision(j, j), exponent) + for j in range(len(J.subdivisions()[0])+1) ]) + P = Jpower.parent() + result = P(T)*Jpower*P(T).inverse() + assert all(result.subs(n=_) == A**_ for _ in range(5)) + return result + + if not self.is_deterministic(): + raise NotImplementedError("Finite State Machine must be deterministic.") + + left = vector(ZZ(s.is_initial) for s in self.iter_states()) + right = vector(ZZ(s.is_final) for s in self.iter_states()) + A = self.adjacency_matrix(entry=lambda t: 1) + return left*matrix_power(A, variable)*right + + + def asymptotic_moments(self, variable=sage.symbolic.ring.SR.var('n')): r""" Returns the main terms of expectation and variance of the sum of output labels and its covariance with the sum of input @@ -9106,6 +10415,7 @@ def asymptotic_moments(self, variable=SR.symbol('n')): from sage.calculus.functional import derivative from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ + from sage.symbolic.ring import SR if self.input_alphabet is None: raise ValueError("No input alphabet is given. " @@ -9146,8 +10456,9 @@ def get_matrix(fsm, x, y): try: M = get_matrix(self, x, y) except TypeError: - verbose("Non-integer output weights lead to " - "significant performance degradation.", level=0) + sage.misc.misc.verbose( + "Non-integer output weights lead to " + "significant performance degradation.", level=0) # fall back to symbolic ring R = SR x = R.symbol() @@ -9164,25 +10475,427 @@ def substitute_one(g): # Therefore, we need this helper function. return g(1, 1, 1) - f = (M.parent().identity_matrix() - z/K*M).det() - f_x = substitute_one(derivative(f, x)) - f_y = substitute_one(derivative(f, y)) - f_z = substitute_one(derivative(f, z)) - f_xy = substitute_one(derivative(f, x, y)) - f_xz = substitute_one(derivative(f, x, z)) - f_yz = substitute_one(derivative(f, y, z)) - f_yy = substitute_one(derivative(f, y, y)) - f_zz = substitute_one(derivative(f, z, z)) + f = (M.parent().identity_matrix() - z/K*M).det() + f_x = substitute_one(derivative(f, x)) + f_y = substitute_one(derivative(f, y)) + f_z = substitute_one(derivative(f, z)) + f_xy = substitute_one(derivative(f, x, y)) + f_xz = substitute_one(derivative(f, x, z)) + f_yz = substitute_one(derivative(f, y, z)) + f_yy = substitute_one(derivative(f, y, y)) + f_zz = substitute_one(derivative(f, z, z)) + + e_2 = f_y / f_z + v_2 = (f_y**2 * (f_zz+f_z) + f_z**2 * (f_yy+f_y) + - 2*f_y*f_z*f_yz) / f_z**3 + c = (f_x * f_y * (f_zz+f_z) + f_z**2 * f_xy - f_y*f_z*f_xz + - f_x*f_z*f_yz) / f_z**3 + + return {'expectation': e_2*variable + SR(1).Order(), + 'variance': v_2*variable + SR(1).Order(), + 'covariance': c*variable + SR(1).Order()} + + + def moments_waiting_time(self, test=bool, is_zero=None, + expectation_only=False): + ur""" + If this finite state machine acts as a Markov chain, return + the expectation and variance of the number of steps until + first writing ``True``. + + INPUT: + + - ``test`` -- (default: ``bool``) a callable deciding whether + an output label is to be considered ``True``. By default, the + standard conversion to boolean is used. + + - ``is_zero`` -- (default: ``None``) a callable deciding + whether an expression for a probability is zero. By default, + checking for zero is simply done by + :meth:`~sage.structure.element.Element.is_zero`. This + parameter can be used to provide a more sophisticated check + for zero, e.g. in the case of symbolic probabilities, see + the examples below. This parameter is passed on to + :meth:`is_Markov_chain`. This parameter only affects the + input of the Markov chain. + + - ``expectation_only`` -- (default: ``False``) if set, the + variance is not computed (in order to save time). By default, + the variance is computed. + + OUTPUT: + + A dictionary (if ``expectation_only=False``) consisting of + + - ``expectation``, + - ``variance``. + + Otherwise, just the expectation is returned (no dictionary for + ``expectation_only=True``). + + Expectation and variance of the number of steps until first + writing ``True`` (as determined by the parameter ``test``). + + ALGORITHM: + + Relies on a (classical and easy) probabilistic argument, + cf. [FGT1992]_, Eqns. (6) and (7). + + For the variance, see [FHP2015]_, Section 2. + + EXAMPLES: + + #. The simplest example is to wait for the first `1` in a + `0`-`1`-string where both digits appear with probability + `1/2`. In fact, the waiting time equals `k` if and only if + the string starts with `0^{k-1}1`. This event occurs with + probability `2^{-k}`. Therefore, the expected waiting time + and the variance are `\sum_{k\ge 1} k2^{-k}=2` and + `\sum_{k\ge 1} (k-2)^2 2^{-k}=2`:: + + sage: var('k') + k + sage: sum(k * 2^(-k), k, 1, infinity) + 2 + sage: sum((k-2)^2 * 2^(-k), k, 1, infinity) + 2 + + We now compute the same expectation and variance by using a + Markov chain:: + + sage: from sage.combinat.finite_state_machine import ( + ....: duplicate_transition_add_input) + sage: T = Transducer( + ....: [(0, 0, 1/2, 0), (0, 0, 1/2, 1)], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input, + ....: initial_states=[0], + ....: final_states=[0]) + sage: T.moments_waiting_time() + {'expectation': 2, 'variance': 2} + sage: T.moments_waiting_time(expectation_only=True) + 2 + + In the following, we replace the output ``0`` by ``-1`` and + demonstrate the use of the parameter ``test``:: + + sage: T.delete_transition((0, 0, 1/2, 0)) + sage: T.add_transition((0, 0, 1/2, -1)) + Transition from 0 to 0: 1/2|-1 + sage: T.moments_waiting_time(test=lambda x: x<0) + {'expectation': 2, 'variance': 2} + + #. Make sure that the transducer is actually a Markov + chain. Although this is checked by the code, unexpected + behaviour may still occur if the transducer looks like a + Markov chain. In the following example, we 'forget' to + assign probabilities, but due to a coincidence, all + 'probabilities' add up to one. Nevertheless, `0` is never + written, so the expectation is `1`. + + :: + + sage: T = Transducer([(0, 0, 0, 0), (0, 0, 1, 1)], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input, + ....: initial_states=[0], + ....: final_states=[0]) + sage: T.moments_waiting_time() + {'expectation': 1, 'variance': 0} + + #. If ``True`` is never written, the moments are + ``+Infinity``:: + + sage: T = Transducer([(0, 0, 1, 0)], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input, + ....: initial_states=[0], + ....: final_states=[0]) + sage: T.moments_waiting_time() + {'expectation': +Infinity, 'variance': +Infinity} + + #. Let `h` and `r` be positive integers. We consider random + strings of letters `1`, `\ldots`, `r` where the letter `j` + occurs with probability `p_j`. Let `B` be the random + variable giving the first position of a block of `h` + consecutive identical letters. Then + + .. MATH:: + + \begin{aligned} + \mathbb{E}(B)&=\frac1{\displaystyle\sum_{i=1}^r + \frac1{p_i^{-1}+\cdots+p_i^{-h}}},\\ + \mathbb{V}(B)&=\frac{\displaystyle\sum_{i=1}^r\biggl( + \frac{p_i +p_i^h}{1-p_i^h} + - 2h\frac{ p_i^h(1-p_i)}{(1-p_i^h)^2}\biggr)} + {\displaystyle\biggl(\sum_{i=1}^r + \frac1{p_i^{-1}+\cdots+p_i^{-h}}\biggr)^2} + \end{aligned} + + cf. [S1986]_, p. 62, or [FHP2015]_, Theorem 1. We now + verify this with a transducer approach. + + :: + + sage: def test(h, r): + ....: R = PolynomialRing( + ....: QQ, + ....: names=['p_%d' % j for j in range(r)]) + ....: p = R.gens() + ....: def is_zero(polynomial): + ....: return polynomial in (sum(p) - 1) * R + ....: theory_expectation = 1/(sum(1/sum(p[j]^(-i) + ....: for i in range(1, h+1)) + ....: for j in range(r))) + ....: theory_variance = sum( + ....: (p[i] + p[i]^h)/(1 - p[i]^h) + ....: - 2*h*p[i]^h * (1 - p[i])/(1 - p[i]^h)^2 + ....: for i in range(r) + ....: ) * theory_expectation^2 + ....: alphabet = range(r) + ....: counters = [ + ....: transducers.CountSubblockOccurrences([j]*h, + ....: alphabet) + ....: for j in alphabet] + ....: all_counter = counters[0].cartesian_product( + ....: counters[1:]) + ....: adder = transducers.add(input_alphabet=[0, 1], + ....: number_of_operands=r) + ....: probabilities = Transducer( + ....: [(0, 0, p[j], j) for j in alphabet], + ....: initial_states=[0], + ....: final_states=[0], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input) + ....: chain = adder(all_counter(probabilities)) + ....: result = chain.moments_waiting_time( + ....: is_zero=is_zero) + ....: return is_zero((result['expectation'] - + ....: theory_expectation).numerator()) \ + ....: and \ + ....: is_zero((result['variance'] - + ....: theory_variance).numerator()) + sage: test(2, 2) + True + sage: test(2, 3) + True + sage: test(3, 3) + True + + #. Consider the alphabet `\{0, \ldots, r-1\}`, some `1\le j\le + r` and some `h\ge 1`. For some probabilities `p_0`, + `\ldots`, `p_{r-1}`, we consider infinite words where the + letters occur independently with the given probabilities. + The random variable `B_j` is the first position `n` such + that there exist `j` of the `r` letters having an `h`-run. + The expectation of `B_j` is given in [FHP2015]_, Theorem 2. + Here, we verify this result by using transducers:: + + sage: def test(h, r, j): + ....: R = PolynomialRing( + ....: QQ, + ....: names=['p_%d' % i for i in range(r)]) + ....: p = R.gens() + ....: def is_zero(polynomial): + ....: return polynomial in (sum(p) - 1) * R + ....: alphabet = range(r) + ....: counters = [ + ....: transducers.Wait([0, 1])( + ....: transducers.CountSubblockOccurrences( + ....: [i]*h, + ....: alphabet)) + ....: for i in alphabet] + ....: all_counter = counters[0].cartesian_product( + ....: counters[1:]) + ....: adder = transducers.add(input_alphabet=[0, 1], + ....: number_of_operands=r) + ....: threshold = transducers.map( + ....: f=lambda x: x >= j, + ....: input_alphabet=srange(r+1)) + ....: probabilities = Transducer( + ....: [(0, 0, p[i], i) for i in alphabet], + ....: initial_states=[0], + ....: final_states=[0], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input) + ....: chain = threshold(adder(all_counter( + ....: probabilities))) + ....: result = chain.moments_waiting_time( + ....: is_zero=is_zero, + ....: expectation_only=True) + ....: + ....: R_v = PolynomialRing( + ....: QQ, + ....: names=['p_%d' % i for i in range(r)]) + ....: v = R_v.gens() + ....: S = 1/(1 - sum(v[i]/(1+v[i]) + ....: for i in range(r))) + ....: alpha = [(p[i] - p[i]^h)/(1 - p[i]) + ....: for i in range(r)] + ....: gamma = [p[i]/(1 - p[i]) for i in range(r)] + ....: alphabet_set = set(alphabet) + ....: expectation = 0 + ....: for q in range(j): + ....: for M in Subsets(alphabet_set, q): + ....: summand = S + ....: for i in M: + ....: summand = summand.subs( + ....: {v[i]: gamma[i]}) -\ + ....: summand.subs({v[i]: alpha[i]}) + ....: for i in alphabet_set - set(M): + ....: summand = summand.subs( + ....: {v[i]: alpha[i]}) + ....: expectation += summand + ....: return is_zero((result - expectation).\ + ....: numerator()) + sage: test(2, 3, 2) + True + + REFERENCES: + + .. [FGT1992] Philippe Flajolet, Danièle Gardy, Loÿs Thimonier, + *Birthday paradox, coupon collectors, caching algorithms and + self-organizing search*, Discrete Appl. Math. 39 (1992), + 207--229, :doi:`10.1016/0166-218X(92)90177-C`. + + .. [FHP2015] Uta Freiberg, Clemens Heuberger, Helmut Prodinger, + *Application of Smirnov Words to Waiting Time Distributions + of Runs*, :arxiv:`1503.08096`. + + .. [S1986] Gábor J. Székely, *Paradoxes in Probability Theory + and Mathematical Statistics*, D. Reidel Publishing Company. + + TESTS: + + Only Markov chains are acceptable:: + + sage: T = transducers.Identity([0, 1, 2]) + sage: T.moments_waiting_time() + Traceback (most recent call last): + ... + ValueError: Only Markov chains can compute + moments_waiting_time. + + There must be a unique initial state:: + + sage: T = Transducer([(0, 1, 1, 1), (1, 0, 1, 0)], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input) + sage: T.moments_waiting_time() + Traceback (most recent call last): + ... + ValueError: Unique initial state is required. + + Using `0` as initial state in this example, a `1` is written in + the first step with probability `1`, so the waiting time is + always `1`:: + + sage: T.state(0).is_initial = True + sage: T.moments_waiting_time() + {'expectation': 1, 'variance': 0} + + Using both `0` and `1` as initial states again yields an error + message:: + + sage: T.state(1).is_initial = True + sage: T.moments_waiting_time() + Traceback (most recent call last): + ... + ValueError: Unique initial state is required. + + Detection of infinite waiting time for symbolic probabilities:: + + sage: R. = PolynomialRing(QQ) + sage: T = Transducer([(0, 0, p, 0), (0, 0, q, 0)], + ....: initial_states=[0], + ....: on_duplicate_transition=\ + ....: duplicate_transition_add_input) + sage: T.moments_waiting_time( + ....: is_zero=lambda e: e in (p + q - 1)*R) + {'expectation': +Infinity, 'variance': +Infinity} + """ + from sage.modules.free_module_element import vector + from sage.matrix.constructor import identity_matrix + from sage.rings.polynomial.polynomial_ring_constructor import\ + PolynomialRing + + def default_is_zero(expression): + return expression.is_zero() + + is_zero_function = default_is_zero + if is_zero is not None: + is_zero_function = is_zero + + if not self.is_Markov_chain(is_zero): + raise ValueError("Only Markov chains can compute " + "moments_waiting_time.") - e_2 = f_y / f_z - v_2 = (f_y**2 * (f_zz+f_z) + f_z**2 * (f_yy+f_y) - - 2*f_y*f_z*f_yz) / f_z**3 - c = (f_x * f_y * (f_zz+f_z) + f_z**2 * f_xy - f_y*f_z*f_xz - - f_x*f_z*f_yz) / f_z**3 + if len(self.initial_states()) != 1: + raise ValueError("Unique initial state is required.") - return {'expectation': e_2*variable + SR(1).Order(), - 'variance': v_2*variable + SR(1).Order(), - 'covariance': c*variable + SR(1).Order()} + def entry(transition): + word_out = transition.word_out + if len(word_out) == 0 or ( + len(word_out) == 1 and not test(word_out[0])): + return transition.word_in[0] + else: + return 0 + + relabeled = self.relabeled() + n = len(relabeled.states()) + assert [s.label() for s in relabeled.states()] == range(n) + from sage.rings.integer_ring import ZZ + entry_vector = vector(ZZ(s.is_initial) + for s in relabeled.states()) + exit_vector = vector([1] * n) + transition_matrix = relabeled.adjacency_matrix(entry=entry) + # transition_matrix is the probability transition matrix + # of the part of the transducer before the occurrence of true + # output. + # We cannot use the input parameter of adjacency_matrix + # because we want to check for "true" input in the sense + # of python's boolean conversion. So we cannot give + # input=[False] as this might lead to strange phenomena. + if all(map(is_zero_function, + transition_matrix * exit_vector - exit_vector)): + import sage.rings.infinity + expectation = sage.rings.infinity.PlusInfinity() + variance = sage.rings.infinity.PlusInfinity() + else: + if expectation_only: + system_matrix = identity_matrix(n) - transition_matrix + expectation = entry_vector * \ + system_matrix.solve_right(exit_vector) + else: + base_ring = transition_matrix.parent().base_ring() + from sage.rings.polynomial.multi_polynomial_ring \ + import is_MPolynomialRing + if is_MPolynomialRing(base_ring): + # if base_ring is already a multivariate polynomial + # ring, extend it instead of creating a univariate + # polynomial ring over a polynomial ring. This + # should improve performance. + R = PolynomialRing( + base_ring.base_ring(), + base_ring.variable_names() + + ('Z_waiting_time',)) + else: + R = PolynomialRing(base_ring, 'Z_waiting_time') + Z = R.gens()[-1] + system_matrix = identity_matrix(n) - Z * \ + transition_matrix + G = entry_vector * system_matrix.solve_right( + exit_vector) + expectation = G.subs({Z: 1}) + variance = 2 * G.derivative(Z).subs({Z: 1}) \ + + expectation \ + - expectation**2 + + if expectation_only: + return expectation + else: + return {'expectation': expectation, + 'variance': variance} def is_monochromatic(self): @@ -9211,6 +10924,100 @@ def is_monochromatic(self): return equal(s.color for s in self.iter_states()) + def language(self, max_length=None, **kwargs): + r""" + Return all words that can be written by this transducer. + + INPUT: + + - ``max_length`` -- an integer or ``None`` (default). Only + output words which come from inputs of length at most + ``max_length`` will be considered. If ``None``, then this + iterates over all possible words without length restrictions. + + - ``kwargs`` -- will be passed on to to the :class:`process + iterator `. See :meth:`process` for a + description. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: NAF = Transducer([('I', 0, 0, None), ('I', 1, 1, None), + ....: (0, 0, 0, 0), (0, 1, 1, 0), + ....: (1, 0, 0, 1), (1, 2, 1, -1), + ....: (2, 1, 0, 0), (2, 2, 1, 0)], + ....: initial_states=['I'], final_states=[0], + ....: input_alphabet=[0, 1]) + sage: sorted(NAF.language(4), + ....: key=lambda o: (ZZ(o, base=2), len(o))) + [[], [0], [0, 0], [0, 0, 0], + [1], [1, 0], [1, 0, 0], + [0, 1], [0, 1, 0], + [-1, 0, 1], + [0, 0, 1], + [1, 0, 1]] + + :: + + sage: iterator = NAF.language() + sage: next(iterator) + [] + sage: next(iterator) + [0] + sage: next(iterator) + [1] + sage: next(iterator) + [0, 0] + sage: next(iterator) + [0, 1] + + .. SEEALSO:: + + :meth:`Automaton.language`, + :meth:`process`. + + TESTS:: + + sage: T = Transducer([(0, 1, 0, 'a'), (1, 2, 1, 'b')], + ....: initial_states=[0], final_states=[0, 1, 2]) + sage: T.determine_alphabets() + sage: list(T.language(2)) + [[], ['a'], ['a', 'b']] + sage: list(T.language(3)) + [[], ['a'], ['a', 'b']] + sage: from sage.combinat.finite_state_machine import _FSMProcessIteratorAll_ + sage: it = T.iter_process( + ....: process_iterator_class=_FSMProcessIteratorAll_, + ....: max_length=3, + ....: process_all_prefixes_of_input=True) + sage: for current in it: + ....: print current + ....: print "finished:", [branch.output for branch in it._finished_] + process (1 branch) + + at state 1 + +-- tape at 1, [['a']] + finished: [[]] + process (1 branch) + + at state 2 + +-- tape at 2, [['a', 'b']] + finished: [[], ['a']] + process (0 branches) + finished: [[], ['a'], ['a', 'b']] + """ + kwargs['process_iterator_class'] = _FSMProcessIteratorAll_ + kwargs['max_length'] = max_length + kwargs['process_all_prefixes_of_input'] = True + it = self.iter_process(**kwargs) + for _ in it: + for branch in it._finished_: + if branch.accept: + yield branch.output + it._finished_ = [] + + #***************************************************************************** @@ -9328,7 +11135,9 @@ def _repr_(self): else: return "Automaton with %s states" % len(self._states_) - def _latex_transition_label_(self, transition, format_function=latex): + + def _latex_transition_label_(self, transition, + format_function=sage.misc.latex.latex): r""" Returns the proper transition label. @@ -9459,8 +11268,10 @@ def function(transition1, transition2): function, only_accessible_components=only_accessible_components) + cartesian_product = intersection + def determinisation(self): """ Returns a deterministic automaton which accepts the same input @@ -9478,7 +11289,8 @@ def determinisation(self): of states of ``self``. The color of a new state is the frozenset of colors of the constituent states of ``self``. Therefore, the colors of the constituent states have to be - hashable. + hashable. However, if all constituent states have color + ``None``, then the resulting color is ``None``, too. The input alphabet must be specified. @@ -9514,13 +11326,7 @@ def determinisation(self): sage: A = Automaton([(0, 1, 1), (0, 2, [1, 1]), (0, 3, [1, 1, 1]), ....: (1, 0, -1), (2, 0, -2), (3, 0, -3)], ....: initial_states=[0], final_states=[0, 1, 2, 3]) - sage: B = A.determinisation().relabeled() - sage: all(t.to_state.label() == 2 for t in - ....: B.state(2).transitions) - True - sage: B.state(2).is_final - False - sage: B.delete_state(2) # this is a sink + sage: B = A.determinisation().relabeled().coaccessible_components() sage: sorted(B.transitions()) [Transition from 0 to 1: 1|-, Transition from 1 to 0: -1|-, @@ -9541,6 +11347,15 @@ def determinisation(self): sage: A.determinisation() Automaton with 1 state + If the colors of all constituent states are ``None``, + the resulting color is ``None``, too (:trac:`19199`):: + + sage: A = Automaton([(0, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: [s.color for s in A.determinisation().iter_states()] + [None] + TESTS: This is from :trac:`15078`, comment 13. @@ -9574,6 +11389,26 @@ def determinisation(self): [frozenset(['A', 'C'])] sage: Ddet.process(list('aaab')) (True, frozenset(['A', 'C'])) + + Test that :trac:`18992` is fixed:: + + sage: A = Automaton([(0, 1, []), (1, 1, 0)], + ....: initial_states=[0], final_states=[1]) + sage: B = A.determinisation() + sage: B.initial_states() + [frozenset([0, 1])] + sage: B.final_states() + [frozenset([0, 1]), frozenset([1])] + sage: B.transitions() + [Transition from frozenset([0, 1]) to frozenset([1]): 0|-, + Transition from frozenset([1]) to frozenset([1]): 0|-] + sage: C = B.minimization().relabeled() + sage: C.initial_states() + [0] + sage: C.final_states() + [0] + sage: C.transitions() + [Transition from 0 to 0: 0|-] """ if any(len(t.word_in) > 1 for t in self.iter_transitions()): return self.split_transitions().determinisation() @@ -9608,13 +11443,19 @@ def set_transition(states, letter): return (frozenset(result), []) result = self.empty_copy() - new_initial_states = [frozenset(self.iter_initial_states())] + new_initial_states = [frozenset(set().union( + *(epsilon_successors[s] + for s in self.iter_initial_states() + )))] result.add_from_transition_function(set_transition, initial_states=new_initial_states) for state in result.iter_states(): state.is_final = any(s.is_final for s in state.label()) - state.color = frozenset(s.color for s in state.label()) + if all(s.color is None for s in state.label()): + state.color = None + else: + state.color = frozenset(s.color for s in state.label()) return result @@ -9736,6 +11577,102 @@ def _minimization_Moore_(self): "implemented for deterministic finite state machines") + def complement(self): + r""" + Return the complement of this automaton. + + OUTPUT: + + An :class:`Automaton`. + + If this automaton recognizes language `\mathcal{L}` over an + input alphabet `\mathcal{A}`, then the complement recognizes + `\mathcal{A}\setminus\mathcal{L}`. + + EXAMPLES:: + + sage: A = automata.Word([0, 1]) + sage: [w for w in + ....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] + ....: if A(w)] + [[0, 1]] + sage: Ac = A.complement() + sage: Ac.transitions() + [Transition from 0 to 1: 0|-, + Transition from 0 to 3: 1|-, + Transition from 2 to 3: 0|-, + Transition from 2 to 3: 1|-, + Transition from 1 to 2: 1|-, + Transition from 1 to 3: 0|-, + Transition from 3 to 3: 0|-, + Transition from 3 to 3: 1|-] + sage: [w for w in + ....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] + ....: if Ac(w)] + [[], [0], [1], [0, 0], [1, 0], [1, 1]] + + The automaton must be deterministic:: + + sage: A = automata.Word([0]) * automata.Word([1]) + sage: A.complement() + Traceback (most recent call last): + ... + ValueError: The finite state machine must be deterministic. + sage: Ac = A.determinisation().complement() + sage: [w for w in + ....: [], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1] + ....: if Ac(w)] + [[], [0], [1], [0, 0], [1, 0], [1, 1]] + """ + result = self.completion() + for state in result.iter_states(): + state.is_final = not state.is_final + + return result + + def is_equivalent(self, other): + """ + Test whether two automata are equivalent, i.e., accept the same + language. + + INPUT: + + - ``other`` -- an :class:`Automaton`. + + EXAMPLES:: + + sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 1)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: B = Automaton([('a', 'a', 0), ('a', 'b', 1), ('b', 'a', 1)], + ....: initial_states=['a'], + ....: final_states=['a']) + sage: A.is_equivalent(B) + True + sage: B.add_transition('b', 'a', 0) + Transition from 'b' to 'a': 0|- + sage: A.is_equivalent(B) + False + """ + A = self.minimization().relabeled() + [initial] = A.initial_states() + address = {initial: ()} + for v in A.digraph().breadth_first_search(initial.label()): + state = A.state(v) + state_address = address[state] + for t in A.iter_transitions(state): + if t.to_state not in address: + address[t.to_state] = state_address + tuple(t.word_in) + + B = other.minimization().relabeled() + labels = {B.process(path)[1].label(): state.label() + for (state, path) in address.iteritems()} + try: + return A == B.relabeled(labels=labels) + except KeyError: + return False + + def process(self, *args, **kwargs): """ Return whether the automaton accepts the input and the state @@ -9799,6 +11736,17 @@ def process(self, *args, **kwargs): process iterator is activated. See also the notes below for multi-tape machines. + - ``process_all_prefixes_of_input`` -- (default: ``False``) a + boolean. If ``True``, then each prefix of the input word is + processed (instead of processing the whole input word at + once). Consequently, there is an output generated for each + of these prefixes. + + - ``process_iterator_class`` -- (default: ``None``) a class + inherited from :class:`FSMProcessIterator`. If ``None``, + then :class:`FSMProcessIterator` is taken. An instance of this + class is created and is used during the processing. + OUTPUT: The full output is a pair (or a list of pairs, @@ -9845,7 +11793,7 @@ def process(self, *args, **kwargs): Internally this function creates and works with an instance of :class:`FSMProcessIterator`. This iterator can also be obtained - with :meth:`iter_process`. + with :meth:`~FiniteStateMachine.iter_process`. If working with multi-tape finite state machines, all input words of transitions are words of `k`-tuples of letters. @@ -9863,7 +11811,7 @@ def process(self, *args, **kwargs): accepts non-adjacent forms (see also the example on :ref:`non-adjacent forms ` in the documentation of the module - :mod:`~sage.combinat.finite_state_machine`) + :doc:`finite_state_machine`) and then test it by feeding it with several binary digit expansions. @@ -9951,6 +11899,8 @@ def process(self, *args, **kwargs): :meth:`~FiniteStateMachine.__call__`, :class:`FSMProcessIterator`. """ + from copy import copy + # set default values options = copy(self._process_default_options_) options.update(kwargs) @@ -10016,6 +11966,141 @@ def _process_convert_output_(self, output_data, **kwargs): return accept_input + def shannon_parry_markov_chain(self): + """ + Compute a time homogeneous Markov chain such that all words of a + given length recognized by the original automaton occur as the + output with the same weight; the transition probabilities + correspond to the Parry measure. + + OUTPUT: + + A Markov chain. Its input labels are the transition probabilities, the + output labels the labels of the original automaton. In order to obtain + equal weight for all words of the same length, an "exit weight" is + needed. It is stored in the attribute ``color`` of the states of the + Markov chain. The weights of the words of the same length sum up to one + up to an exponentially small error. + + The stationary distribution of this Markov chain is + saved as the initial probabilities of the states. + + The transition probabilities correspond to the Parry measure + (see [S1948]_ and [P1964]_). + + The automaton is assumed to be deterministic, irreducible and + aperiodic. All states must be final. + + EXAMPLES:: + + sage: NAF = Automaton([(0, 0, 0), (0, 1, 1), (0, 1, -1), + ....: (1, 0, 0)], initial_states=[0], + ....: final_states=[0, 1]) + sage: P_NAF = NAF.shannon_parry_markov_chain() + sage: P_NAF.transitions() + [Transition from 0 to 0: 1/2|0, + Transition from 0 to 1: 1/4|1, + Transition from 0 to 1: 1/4|-1, + Transition from 1 to 0: 1|0] + sage: for s in P_NAF.iter_states(): + ....: print s.color + 3/4 + 3/2 + + The stationary distribution is also computed and saved as the + initial probabilities of the returned Markov chain:: + + sage: for s in P_NAF.states(): + ....: print s, s.initial_probability + 0 2/3 + 1 1/3 + + The automaton is assumed to be deterministic, irreducible and aperiodic:: + + sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 1, 0)], + ....: initial_states=[0]) + sage: A.shannon_parry_markov_chain() + Traceback (most recent call last): + ... + NotImplementedError: Automaton must be strongly connected. + sage: A = Automaton([(0, 0, 0), (0, 1, 0)], + ....: initial_states=[0]) + sage: A.shannon_parry_markov_chain() + Traceback (most recent call last): + ... + NotImplementedError: Automaton must be deterministic. + sage: A = Automaton([(0, 1, 0), (1, 0, 0)], + ....: initial_states=[0]) + sage: A.shannon_parry_markov_chain() + Traceback (most recent call last): + ... + NotImplementedError: Automaton must be aperiodic. + + All states must be final:: + + sage: A = Automaton([(0, 1, 0), (0, 0, 1), (1, 0, 0)], + ....: initial_states=[0]) + sage: A.shannon_parry_markov_chain() + Traceback (most recent call last): + ... + NotImplementedError: All states must be final. + + ALGORITHM: + + See [HKP2015a]_, Lemma 4.1. + + REFERENCES: + + .. [HKP2015a] Clemens Heuberger, Sara Kropf, and Helmut + Prodinger, *Analysis of Carries in Signed Digit Expansions*, + :arxiv:`1503.08816`. + .. [P1964] William Parry, *Intrinsic Markov chains*, Transactions + of the American Mathematical Society 112, 1964, pp. 55-66. + :doi:`10.1090/S0002-9947-1964-0161372-1`. + .. [S1948] Claude E. Shannon, *A mathematical theory of communication*, + The Bell System Technical Journal 27, 1948, 379-423, + :doi:`10.1002/j.1538-7305.1948.tb01338.x`. + """ + from sage.modules.free_module_element import vector + if not self.is_deterministic(): + raise NotImplementedError("Automaton must be deterministic.") + if not self.digraph().is_aperiodic(): + raise NotImplementedError("Automaton must be aperiodic.") + if not self.digraph().is_strongly_connected(): + raise NotImplementedError("Automaton must be strongly connected.") + if not all(s.is_final for s in self.iter_states()): + raise NotImplementedError("All states must be final.") + from sage.rings.integer_ring import ZZ + M = self.adjacency_matrix().change_ring(ZZ) + states = {state: i for i, state in enumerate(self.iter_states())} + w_all = sorted(M.eigenvectors_right(), + key=lambda x: abs(x[0]), + reverse=True) + w = w_all[0][1][0] + mu = w_all[0][0] + u_all = sorted(M.eigenvectors_left(), + key=lambda x: abs(x[0]), + reverse=True) + u = u_all[0][1][0] + u = 1/(u*w) * u + final = vector(int(s.is_final) for s in self.iter_states()) + ff = u*final + + assert u*w == 1 + P = Transducer(initial_states=[s.label() for s in self.iter_initial_states()], + final_states=[s.label() for s in self.iter_final_states()], + on_duplicate_transition=duplicate_transition_add_input) + for t in self.iter_transitions(): + P.add_transition(t.from_state.label(), + t.to_state.label(), + w[states[t.to_state]]/w[states[t.from_state]]/mu, + t.word_in) + for s in self.iter_states(): + P.state(s.label()).color = 1/(w[states[s]] * ff) + P.state(s.label()).initial_probability = w[states[s]] * u[states[s]] + return P + + def with_output(self, word_out_function=None): r""" Construct a transducer out of this automaton. @@ -10102,6 +12187,8 @@ def with_output(self, word_out_function=None): sage: B.with_output(lambda t: [c.upper() for c in t.word_in]).input_projection() == B True """ + from copy import copy + if word_out_function is None: word_out_function = lambda transition: copy(transition.word_in) new = Transducer() @@ -10111,6 +12198,58 @@ def with_output(self, word_out_function=None): t.word_out = word_out_function(t) return new + + def language(self, max_length=None, **kwargs): + r""" + Return all words accepted by this automaton. + + INPUT: + + - ``max_length`` -- an integer or ``None`` (default). Only + inputs of length at most ``max_length`` will be + considered. If ``None``, then this iterates over all + possible words without length restrictions. + + - ``kwargs`` -- will be passed on to to the :class:`process + iterator `. See :meth:`process` for a + description. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: NAF = Automaton( + ....: {'A': [('A', 0), ('B', 1), ('B', -1)], + ....: 'B': [('A', 0)]}, + ....: initial_states=['A'], final_states=['A', 'B']) + sage: list(NAF.language(3)) + [[], + [0], [-1], [1], + [-1, 0], [0, 0], [1, 0], [0, -1], [0, 1], + [-1, 0, 0], [0, -1, 0], [0, 0, 0], [0, 1, 0], [1, 0, 0], + [-1, 0, -1], [-1, 0, 1], [0, 0, -1], + [0, 0, 1], [1, 0, -1], [1, 0, 1]] + + .. SEEALSO:: + + :meth:`FiniteStateMachine.language`, + :meth:`process`. + + TESTS:: + + sage: def R(ell): + ....: return (2^(ell+2)-(-1)^ell)/3 + sage: import itertools + sage: all(len(list(NAFs)) == R(ell) for ell, NAFs in + ....: itertools.groupby(NAF.language(5), key=len)) + True + """ + T = self.with_output() + return T.language(max_length) + + #***************************************************************************** @@ -10208,7 +12347,9 @@ def _repr_(self): else: return "Transducer with %s states" % len(self._states_) - def _latex_transition_label_(self, transition, format_function=latex): + + def _latex_transition_label_(self, transition, + format_function=sage.misc.latex.latex): r""" Returns the proper transition label. @@ -10642,6 +12783,8 @@ def simplification(self): Transition from (1,) to (0,): 0|0, Transition from (1,) to (1,): 1|1] """ + from copy import deepcopy + fsm = deepcopy(self) fsm.prepone_output() return fsm.quotient(fsm.equivalence_classes()) @@ -10711,6 +12854,23 @@ def process(self, *args, **kwargs): process iterator is activated. See also the notes below for multi-tape machines. + - ``process_all_prefixes_of_input`` -- (default: ``False``) a + boolean. If ``True``, then each prefix of the input word is + processed (instead of processing the whole input word at + once). Consequently, there is an output generated for each + of these prefixes. + + - ``process_iterator_class`` -- (default: ``None``) a class + inherited from :class:`FSMProcessIterator`. If ``None``, + then :class:`FSMProcessIterator` is taken. An instance of this + class is created and is used during the processing. + + - ``automatic_output_type`` -- (default: ``False``) a boolean + If set and the input has a parent, then the + output will have the same parent. If the input does not have + a parent, then the output will be of the same type as the + input. + OUTPUT: The full output is a triple (or a list of triples, @@ -10757,7 +12917,7 @@ def process(self, *args, **kwargs): Internally this function creates and works with an instance of :class:`FSMProcessIterator`. This iterator can also be obtained - with :meth:`iter_process`. + with :meth:`~FiniteStateMachine.iter_process`. If working with multi-tape finite state machines, all input words of transitions are words of `k`-tuples of letters. @@ -10781,6 +12941,24 @@ def process(self, *args, **kwargs): sage: binary_inverter([0, 1, 0, 0, 1, 1]) [1, 0, 1, 1, 0, 0] + This can also be used with words as input:: + + sage: W = Words([0, 1]); W + Words over {0, 1} + sage: w = W([0, 1, 0, 0, 1, 1]); w + word: 010011 + sage: binary_inverter(w) + word: 101100 + + In this case it is automatically determined that the output is + a word. The call above is equivalent to:: + + sage: binary_inverter.process(w, + ....: full_output=False, + ....: list_of_outputs=False, + ....: automatic_output_type=True) + word: 101100 + The following transducer transforms `0^n 1` to `1^n 2`:: sage: T = Transducer([(0, 0, 0, 1), (0, 1, 1, 2)]) @@ -10908,6 +13086,8 @@ def process(self, *args, **kwargs): ... TypeError: No input tape given. """ + from copy import copy + # set default values options = copy(self._process_default_options_) options.update(kwargs) @@ -10974,7 +13154,7 @@ def _process_convert_output_(self, output_data, **kwargs): #***************************************************************************** -class _FSMTapeCache_(SageObject): +class _FSMTapeCache_(sage.structure.sage_object.SageObject): """ This is a class for caching an input tape. It is used in :class:`FSMProcessIterator`. @@ -11125,6 +13305,8 @@ def __deepcopy__(self, memo): sage: TC2.tape_cache_manager is TC3.tape_cache_manager True """ + from copy import deepcopy + new = type(self)(self.tape_cache_manager, self.tape, self.tape_ended, self.position, self.is_multitape) @@ -11161,6 +13343,7 @@ def deepcopy(self, memo=None): sage: TC2.cache is TC3.cache False """ + from copy import deepcopy return deepcopy(self, memo) @@ -11506,7 +13689,7 @@ def length(word): increments = (length(transition.word_in),) for track_number, (track_cache, inc) in \ - enumerate(izip(self.cache, increments)): + enumerate(itertools.izip(self.cache, increments)): for _ in range(inc): if not track_cache: if not self.read(track_number)[0]: @@ -11687,6 +13870,8 @@ def __deepcopy__(self, memo): sage: TC3._visited_states_ {1} """ + from copy import copy + new = super(_FSMTapeCacheDetectEpsilon_, self).__deepcopy__(memo) new._visited_states_ = copy(self._visited_states_) return new @@ -11731,34 +13916,54 @@ class _FSMTapeCacheDetectAll_(_FSMTapeCache_): This is a class is similar to :class:`_FSMTapeCache_` but accepts each transition. """ - def _transition_possible_test_(self, word_in): + def compare_to_tape(self, track_number, word): """ - This helper function returns ``True``, i.e., accepts every - ``word_in`` of every transition. + Return whether it is possible to read a word of the same length + as ``word`` (but ignoring its actual content) + from the given track successfully. INPUT: - - ``word_in`` -- an input word of a transition. + - ``track_number`` -- an integer. + + - ``word`` -- a tuple or list of letters. Only its length is used. OUTPUT: - ``True``. + ``True`` or ``False``. + + Note that this method usually returns ``True``. ``False`` can + only be returned at the end of the input tape. TESTS:: - sage: from sage.combinat.finite_state_machine import ( - ....: _FSMTapeCacheDetectAll_) - sage: TCA = _FSMTapeCacheDetectAll_([], (xsrange(37, 42), xsrange(11,15)), - ....: [False, False], ((0, 0), (0, 1)), True) - sage: TCA._transition_possible_test_([(37, 11), (38, 12)]) + sage: from sage.combinat.finite_state_machine import _FSMTapeCacheDetectAll_ + sage: TC = _FSMTapeCacheDetectAll_( + ....: [], (iter((11, 12)),), + ....: [False], ((0, 0),), False) + sage: TC.compare_to_tape(0, []) True - sage: TCA._transition_possible_test_([(37, 11), (38, 13)]) + sage: TC.compare_to_tape(0, [37]) True - sage: TCA._transition_possible_test_([]) + sage: TC.compare_to_tape(0, [37, 38]) True - sage: TCA._transition_possible_test_([(None, None)]) + sage: TC.compare_to_tape(0, [37, 38, 39]) + False + sage: TC.compare_to_tape(0, [1, 2]) True """ + track_cache = self.cache[track_number] + it_word = iter(word) + + # process letters in cache + for _ in track_cache: + next(it_word) + + # check letters not already cached + for letter_in_word in it_word: + successful, letter_on_track = self.read(track_number) + if not successful: + return False return True @@ -11840,7 +14045,8 @@ def is_FSMProcessIterator(PI): #***************************************************************************** -class FSMProcessIterator(SageObject, collections.Iterator): +class FSMProcessIterator(sage.structure.sage_object.SageObject, + collections.Iterator): """ This class takes an input, feeds it into a finite state machine (automaton or transducer, in particular), tests whether this was @@ -11883,6 +14089,12 @@ class FSMProcessIterator(SageObject, collections.Iterator): process iterator is activated. See also the notes below for multi-tape machines. + - ``process_all_prefixes_of_input`` -- (default: ``False``) a + boolean. If ``True``, then each prefix of the input word is + processed (instead of processing the whole input word at + once). Consequently, there is an output generated for each + of these prefixes. + OUTPUT: An iterator. @@ -11985,7 +14197,7 @@ class FSMProcessIterator(SageObject, collections.Iterator): +-- tape at 10, [[1, 0, 0, 1, 0, 1, 0]] process (0 branches) sage: it.result() - [(True, 'A', [1, 0, 0, 1, 0, 1, 0])] + [Branch(accept=True, state='A', output=[1, 0, 0, 1, 0, 1, 0])] :: @@ -12016,7 +14228,8 @@ class FSMProcessIterator(SageObject, collections.Iterator): +-- tape at 3, [['a', 'b', 'c']] process (0 branches) sage: it.result() - [(False, 1, 'abcd'), (True, 2, 'abc')] + [Branch(accept=False, state=1, output='abcd'), + Branch(accept=True, state=2, output='abc')] .. SEEALSO:: @@ -12072,11 +14285,11 @@ class Current(dict): sage: for current in it: ....: print dict(current) ....: print current - {((1, 0),): {'A': (tape at 1, [[1]])}} + {((1, 0),): {'A': Branch(tape_cache=tape at 1, outputs=[[1]])}} process (1 branch) + at state 'A' +-- tape at 1, [[1]] - {((2, 0),): {'A': (tape at 2, [[1, 0]])}} + {((2, 0),): {'A': Branch(tape_cache=tape at 2, outputs=[[1, 0]])}} process (1 branch) + at state 'A' +-- tape at 2, [[1, 0]] @@ -12124,6 +14337,14 @@ def __repr__(self): return result + FinishedBranch = collections.namedtuple('Branch', 'accept, state, output') + r""" + A :func:`named tuple ` representing the + attributes of a branch, once + it is fully processed. + """ + + def __init__(self, fsm, input_tape=None, initial_state=None, initial_states=[], @@ -12131,6 +14352,7 @@ def __init__(self, fsm, check_epsilon_transitions=True, write_final_word_out=True, format_output=None, + process_all_prefixes_of_input=False, **kwargs): """ See :class:`FSMProcessIterator` for more information. @@ -12151,7 +14373,7 @@ def __init__(self, fsm, +-- tape at 2, [[1, 0]] process (0 branches) sage: it.result() - [(True, 'A', [1, 0])] + [Branch(accept=True, state='A', output=[1, 0])] """ # FSM self.fsm = fsm @@ -12190,6 +14412,7 @@ def __init__(self, fsm, self.check_epsilon_transitions = check_epsilon_transitions self.write_final_word_out = write_final_word_out + self.process_all_prefixes_of_input = process_all_prefixes_of_input # init branches self._current_ = self.Current() @@ -12210,6 +14433,13 @@ def __init__(self, fsm, self._finished_ = [] # contains (accept, state, output) + _branch_ = collections.namedtuple('Branch', 'tape_cache, outputs') + r""" + A :func:`named tuple ` representing the + attributes of a branch at a particular state during processing. + """ + + def _push_branch_(self, state, tape_cache, outputs): """ This helper function pushes a ``state`` together with @@ -12283,6 +14513,8 @@ def _push_branch_(self, state, tape_cache, outputs): (True, 3, 'i:)'), (True, 3, 'l:)'), (True, 3, 'n:)')] """ + import heapq + if tape_cache.position in self._current_: states = self._current_[tape_cache.position] else: @@ -12290,13 +14522,15 @@ def _push_branch_(self, state, tape_cache, outputs): heapq.heappush(self._current_positions_, tape_cache.position) if state in states: - existing_tape_cache, existing_outputs = states[state] - existing_outputs.extend(outputs) - existing_outputs = [t for t, _ in - itertools.groupby(sorted(existing_outputs))] - states[state] = (existing_tape_cache, existing_outputs) + existing = states[state] + new_outputs = existing.outputs + new_outputs.extend(outputs) + new_outputs = [t for t, _ in + itertools.groupby(sorted(new_outputs))] + states[state] = FSMProcessIterator._branch_( + existing.tape_cache, new_outputs) else: - states[state] = (tape_cache, outputs) + states[state] = FSMProcessIterator._branch_(tape_cache, outputs) def _push_branches_(self, state, tape_cache, outputs): @@ -12359,6 +14593,8 @@ def _push_branches_(self, state, tape_cache, outputs): + at state 'c' +-- tape at 0, [[]] """ + from copy import deepcopy + self._push_branch_(state, tape_cache, outputs) if not self.check_epsilon_transitions: return @@ -12395,7 +14631,7 @@ def __next__(self): It returns the current status of the iterator (see below). A ``StopIteration`` exception is thrown when there is/was nothing to do (i.e. all branches ended with previous call - of :meth:`.__next__`). + of :meth:`.next`). The current status is a dictionary (encapsulated into an instance of :class:`~FSMProcessIterator.Current`). @@ -12474,6 +14710,9 @@ def __next__(self): 0 [[1, 1]] (False, 0, [1, 1]) """ + from copy import deepcopy + import heapq + if not self._current_: raise StopIteration @@ -12520,14 +14759,29 @@ def step(current_state, input_tape, outputs): if not next_transitions: # this branch has to end here... - if not (input_tape.finished() or state_said_finished): + if not (input_tape.finished() or + state_said_finished or + self.process_all_prefixes_of_input): return + + if not next_transitions or self.process_all_prefixes_of_input: + # this branch has to end here... (continued) successful = current_state.is_final + if self.process_all_prefixes_of_input: + write_outputs = deepcopy(outputs) + else: + write_outputs = outputs if successful and self.write_final_word_out: - write_word(outputs, current_state.final_word_out) - for o in outputs: - self._finished_.append((successful, current_state, - self.format_output(o))) + write_word(write_outputs, current_state.final_word_out) + for o in write_outputs: + self._finished_.append( + FSMProcessIterator.FinishedBranch( + accept=successful, + state=current_state, + output=self.format_output(o))) + + if not next_transitions: + # this branch has to end here... (continued) return # at this point we know that there is at least one @@ -12537,10 +14791,10 @@ def step(current_state, input_tape, outputs): if len(next_transitions) > 1: new_currents.extend( [deepcopy(new_currents[0]) - for _ in srange(len(next_transitions) - 1)]) + for _ in range(len(next_transitions) - 1)]) # process transitions - for transition, (tape, out) in izip(next_transitions, new_currents): + for transition, (tape, out) in itertools.izip(next_transitions, new_currents): if hasattr(transition, 'hook'): transition.hook(transition, self) write_word(out, transition.word_out) @@ -12552,8 +14806,8 @@ def step(current_state, input_tape, outputs): return states_dict = self._current_.pop(heapq.heappop(self._current_positions_)) - for state, (tape, outputs) in states_dict.iteritems(): - step(state, tape, outputs) + for state, branch in states_dict.iteritems(): + step(state, branch.tape_cache, branch.outputs) return self._current_ @@ -12586,7 +14840,7 @@ def result(self, format_output=None): sage: for _ in it: ....: pass sage: it.result() - [(True, 'A', ['one', 'zero', 'zero'])] + [Branch(accept=True, state='A', output=['one', 'zero', 'zero'])] sage: it.result(lambda L: ', '.join(L)) [(True, 'A', 'one, zero, zero')] @@ -12600,7 +14854,7 @@ def result(self, format_output=None): sage: for _ in it: ....: pass sage: it.result() - [(True, 'A', 'one, zero, zero')] + [Branch(accept=True, state='A', output='one, zero, zero')] sage: it.result(lambda L: ', '.join(L)) [(True, 'A', 'o, n, e, ,, , z, e, r, o, ,, , z, e, r, o')] """ @@ -12657,7 +14911,7 @@ def preview_word(self, track_number=None, length=1, return_word=False): Next on the tape is a 1. We are now in state A. sage: it.result() - [(True, 'A', ['one', 'zero', 'zero'])] + [Branch(accept=True, state='A', output=['one', 'zero', 'zero'])] """ return self._current_branch_input_tape_.preview_word( track_number, length, return_word) @@ -12756,7 +15010,7 @@ def output_tape(self): 'or the output of next().') if not self._current_: return None - return next(next(self._current_.itervalues()).itervalues())[1][0] + return next(next(self._current_.itervalues()).itervalues()).outputs[0] @property @@ -12779,7 +15033,7 @@ def accept_input(self): sage: for _ in it: ....: pass sage: it.result() - [(True, 'A', [1, 0, 0])] + [Branch(accept=True, state='A', output=[1, 0, 0])] sage: it.accept_input doctest:...: DeprecationWarning: This attribute will be removed in future releases. Use result() at the end of our iteration @@ -12792,7 +15046,7 @@ def accept_input(self): 'releases. Use result() at the end of our iteration ' 'or the output of next().') try: - return self._finished_[0][0] + return self._finished_[0].accept except KeyError: raise AttributeError @@ -13166,13 +15420,88 @@ def _push_branch_(self, state, tape_cache, outputs): # As tape_cache may have been discarded because current already # contains a branch at the same state, _visited_states_ is # updated manually. - self._current_[tape_cache.position][state][0]._visited_states_.update( - tape_cache._visited_states_) + tape_at_state = self._current_[tape_cache.position][state].tape_cache + tape_at_state._visited_states_.update(tape_cache._visited_states_) + + +class _FSMProcessIteratorAll_(FSMProcessIterator): + r""" + This class is similar to :class:`FSMProcessIterator`, but + accepts all transitions during process. See + :class:`FSMProcessIterator` for more information. + + This is used in :meth:`FiniteStateMachine.language`. + + EXAMPLES:: + + sage: F = FiniteStateMachine( + ....: {'A': [('A', 0, 'z'), ('B', 1, 'o'), ('B', -1, 'm')], + ....: 'B': [('A', 0, 'z')]}, + ....: initial_states=['A'], final_states=['A', 'B']) + sage: from sage.combinat.finite_state_machine import _FSMProcessIteratorAll_ + sage: it = _FSMProcessIteratorAll_(F, max_length=3, + ....: format_output=lambda o: ''.join(o)) + sage: for current in it: + ....: print current + process (2 branches) + + at state 'A' + +-- tape at 1, [['z']] + + at state 'B' + +-- tape at 1, [['m'], ['o']] + process (2 branches) + + at state 'A' + +-- tape at 2, [['m', 'z'], ['o', 'z'], ['z', 'z']] + + at state 'B' + +-- tape at 2, [['z', 'm'], ['z', 'o']] + process (2 branches) + + at state 'A' + +-- tape at 3, [['m', 'z', 'z'], ['o', 'z', 'z'], ['z', 'm', 'z'], + ['z', 'o', 'z'], ['z', 'z', 'z']] + + at state 'B' + +-- tape at 3, [['m', 'z', 'm'], ['m', 'z', 'o'], ['o', 'z', 'm'], + ['o', 'z', 'o'], ['z', 'z', 'm'], ['z', 'z', 'o']] + process (0 branches) + sage: it.result() + [Branch(accept=True, state='A', output='mzz'), + Branch(accept=True, state='A', output='ozz'), + Branch(accept=True, state='A', output='zmz'), + Branch(accept=True, state='A', output='zoz'), + Branch(accept=True, state='A', output='zzz'), + Branch(accept=True, state='B', output='mzm'), + Branch(accept=True, state='B', output='mzo'), + Branch(accept=True, state='B', output='ozm'), + Branch(accept=True, state='B', output='ozo'), + Branch(accept=True, state='B', output='zzm'), + Branch(accept=True, state='B', output='zzo')] + """ + def __init__(self, *args, **kwargs): + """ + See :class:`_FSMProcessIteratorAll_` and + :class:`FSMProcessIterator` for more information. + + TESTS:: + + sage: T = Transducer([(0, 1, 0, 'a'), (1, 2, 1, 'b')], + ....: initial_states=[0], final_states=[0, 1, 2]) + sage: T.determine_alphabets() + sage: list(T.language(2)) # indirect doctest + [[], ['a'], ['a', 'b']] + """ + max_length = kwargs.get('max_length') + if max_length is None: + kwargs['input_tape'] = itertools.count() + else: + kwargs['input_tape'] = iter(0 for _ in xrange(max_length)) + self.TapeCache = _FSMTapeCacheDetectAll_ + self.visited_states = {} + kwargs['check_epsilon_transitions'] = False + return super(_FSMProcessIteratorAll_, self).__init__(*args, **kwargs) + #***************************************************************************** -@cached_function +@sage.misc.cachefunc.cached_function def setup_latex_preamble(): r""" This function adds the package ``tikz`` with support for automata @@ -13197,6 +15526,7 @@ def setup_latex_preamble(): sage: ("\usepackage{tikz}" in latex.extra_preamble()) == latex.has_file("tikz.sty") True """ + from sage.misc.latex import latex latex.add_package_to_preamble_if_available('tikz') latex.add_to_mathjax_avoid_list("tikz") if latex.has_file("tikz.sty"): diff --git a/src/sage/combinat/finite_state_machine_generators.py b/src/sage/combinat/finite_state_machine_generators.py index 8e0e84f4ec9..8a3fe86260b 100644 --- a/src/sage/combinat/finite_state_machine_generators.py +++ b/src/sage/combinat/finite_state_machine_generators.py @@ -1,8 +1,11 @@ r""" -Common Transducers (Finite State Machines Generators) +Common Automata and Transducers (Finite State Machines Generators) -Transducers in Sage can be built through the ``transducers`` -object. It contains generators for common finite state machines. For example, +Automata and Transducers in Sage can be built through the +:class:`automata ` +and :class:`transducers ` objects, respectively. +It contains generators for +common finite state machines. For example, :: @@ -10,9 +13,23 @@ generates an identity transducer on the alphabet `\{0, 1, 2\}`. -To construct transducers manually, you can use the class -:class:`Transducer`. See :mod:`~sage.combinat.finite_state_machine` -for more details and a lot of examples. +To construct automata and transducers manually, you can use the +classes :class:`Automaton` and :class:`Transducer`, respectively. See +:doc:`finite_state_machine` for more details and a lot +of :ref:`examples `. + +**Automata** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~AutomatonGenerators.AnyLetter` | Return an automaton recognizing any letter. + :meth:`~AutomatonGenerators.AnyWord` | Return an automaton recognizing any word. + :meth:`~AutomatonGenerators.EmptyWord` | Return an automaton recognizing the empty word. + :meth:`~AutomatonGenerators.Word` | Return an automaton recognizing the given word. + :meth:`~AutomatonGenerators.ContainsWord` | Return an automaton recognizing words containing the given word. **Transducers** @@ -47,6 +64,8 @@ - Clemens Heuberger, Daniel Krenn (2014-07-18): transducers Wait, all, any - Clemens Heuberger (2014-08-10): transducer Recursion +- Clemens Heuberger (2015-07-31): automaton word +- Daniel Krenn (2015-09-14): cleanup :trac:`18227` ACKNOWLEDGEMENT: @@ -58,8 +77,8 @@ """ #***************************************************************************** -# Copyright (C) 2014 Clemens Heuberger -# 2014 Daniel Krenn +# Copyright (C) 2014--2015 Clemens Heuberger +# 2014--2015 Daniel Krenn # 2014 Sara Kropf # # Distributed under the terms of the GNU General Public License (GPL) @@ -70,16 +89,268 @@ import collections import operator -from sage.symbolic.operators import add_vararg, mul_vararg -from sage.combinat.finite_state_machine import Transducer +from sage.combinat.finite_state_machine import Automaton, Transducer from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from functools import reduce + + +class AutomatonGenerators(object): + r""" + A collection of constructors for several common automata. + + A list of all automata in this database is available via tab + completion. Type "``automata.``" and then hit tab to see which + automata are available. + + The automata currently in this class include: + + - :meth:`~AnyLetter` + - :meth:`~AnyWord` + - :meth:`~EmptyWord` + - :meth:`~Word` + - :meth:`~ContainsWord` + """ + + def AnyLetter(self, input_alphabet): + r""" + Return an automaton recognizing any letter of the given + input alphabet. + + INPUT: + + - ``input_alphabet`` -- a list, the input alphabet + + OUTPUT: + + An :class:`~Automaton`. + + EXAMPLES:: + + sage: A = automata.AnyLetter([0, 1]) + sage: A([]) + False + sage: A([0]) + True + sage: A([1]) + True + sage: A([0, 0]) + False + + .. SEEALSO:: + + :meth:`AnyWord` + """ + z = ZZ(0) + o = ZZ(1) + return Automaton([(z, o, _) for _ in input_alphabet], + initial_states=[z], + final_states=[o]) + + + def AnyWord(self, input_alphabet): + r""" + Return an automaton recognizing any word of the given + input alphabet. + + INPUT: + + - ``input_alphabet`` -- a list, the input alphabet + + OUTPUT: + + An :class:`~Automaton`. + + EXAMPLES:: + + sage: A = automata.AnyWord([0, 1]) + sage: A([0]) + True + sage: A([1]) + True + sage: A([0, 1]) + True + sage: A([0, 2]) + False + + This is equivalent to taking the :meth:`~FiniteStateMachine.kleene_star` + of :meth:`AnyLetter` and minimizing the result. This method + immediately gives a minimized version:: + + sage: B = automata.AnyLetter([0, 1]).kleene_star().minimization().relabeled() + sage: B == A + True + + .. SEEALSO:: + + :meth:`AnyLetter`, + :meth:`Word`. + """ + z = ZZ(0) + return Automaton([(z, z, _) for _ in input_alphabet], + initial_states=[z], + final_states=[z]) + + + def EmptyWord(self, input_alphabet=None): + r""" + Return an automaton recognizing the empty word. + + INPUT: + + - ``input_alphabet`` -- (default: ``None``) an iterable + or ``None``. + + OUTPUT: + + An :class:`~Automaton`. + + EXAMPLES:: + + sage: A = automata.EmptyWord() + sage: A([]) + True + sage: A([0]) + False + + .. SEEALSO:: + + :meth:`AnyLetter`, + :meth:`AnyWord`. + """ + z = ZZ(0) + return Automaton(initial_states=[z], + final_states=[z], + input_alphabet=input_alphabet) + + + def Word(self, word, input_alphabet=None): + r""" + Return an automaton recognizing the given word. + + INPUT: + + - ``word`` -- an iterable. + + - ``input_alphabet`` -- a list or ``None``. If ``None``, + then the letters occurring in the word are used. + + OUTPUT: + + An :class:`~Automaton`. + + EXAMPLES:: + + sage: A = automata.Word([0]) + sage: A.transitions() + [Transition from 0 to 1: 0|-] + sage: [A(w) for w in ([], [0], [1])] + [False, True, False] + sage: A = automata.Word([0, 1, 0]) + sage: A.transitions() + [Transition from 0 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 3: 0|-] + sage: [A(w) for w in ([], [0], [0, 1], [0, 1, 1], [0, 1, 0])] + [False, False, False, False, True] + + If the input alphabet is not given, it is derived from the given + word. :: + + sage: A.input_alphabet + [0, 1] + sage: A = automata.Word([0, 1, 0], input_alphabet=[0, 1, 2]) + sage: A.input_alphabet + [0, 1, 2] + + .. SEEALSO:: + + :meth:`AnyWord`, + :meth:`ContainsWord`. + + TESTS:: + + sage: from sage.rings.integer import is_Integer + sage: all(is_Integer(s.label()) for s in A.states()) + True + """ + letters = list(word) + length = len(letters) + from sage.rings.integer_ring import ZZ + return Automaton([(ZZ(i), ZZ(i+1), letter) + for i, letter in enumerate(letters)], + initial_states=[ZZ(0)], + final_states=[ZZ(length)], + input_alphabet=input_alphabet) + + + def ContainsWord(self, word, input_alphabet): + r""" + Return an automaton recognizing the words containing + the given word as a factor. + + INPUT: + + - ``word`` -- a list (or other iterable) of letters, the + word we are looking for. + + - ``input_alphabet`` -- a list or other iterable, the input + alphabet. + + OUTPUT: + + An :class:`~Automaton`. + + EXAMPLES:: + + sage: A = automata.ContainsWord([0, 1, 0, 1, 1], + ....: input_alphabet=[0, 1]) + sage: A([1, 0, 1, 0, 1, 0, 1, 1, 0, 0]) + True + sage: A([1, 0, 1, 0, 1, 0, 1, 0]) + False + + This is equivalent to taking the concatenation of :meth:`AnyWord`, + :meth:`Word` and :meth:`AnyWord` and minimizing the result. This + method immediately gives a minimized version:: + + sage: B = (automata.AnyWord([0, 1]) * + ....: automata.Word([0, 1, 0, 1, 1], [0, 1]) * + ....: automata.AnyWord([0, 1])).minimization() + sage: B.is_equivalent(A) + True + + .. SEEALSO:: + + :meth:`~TransducerGenerators.CountSubblockOccurrences`, + :meth:`AnyWord`, + :meth:`Word`. + """ + word = tuple(word) + + def starts_with(what, pattern): + return len(what) >= len(pattern) \ + and what[:len(pattern)] == pattern + + def transition_function(read, input): + if read == word: + return (word, None) + current = read + (input,) + k = 0 + while not starts_with(word, current[k:]): + k += 1 + return (current[k:], None) + + return Automaton( + transition_function, + input_alphabet=input_alphabet, + initial_states=[()], + final_states=[word]) + class TransducerGenerators(object): r""" - A class consisting of constructors for several common transducers. + A collection of constructors for several common transducers. A list of all transducers in this database is available via tab completion. Type "``transducers.``" and then hit tab to see which @@ -97,7 +368,6 @@ class TransducerGenerators(object): - :meth:`~CountSubblockOccurrences` - :meth:`~Wait` - :meth:`~GrayCode` - """ def Identity(self, input_alphabet): @@ -127,7 +397,6 @@ def Identity(self, input_alphabet): [0, 1] sage: T.output_alphabet [0, 1] - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: T([0, 1, 0, 1, 1]) [0, 1, 0, 1, 1] @@ -139,6 +408,7 @@ def Identity(self, input_alphabet): initial_states=[0], final_states=[0]) + def CountSubblockOccurrences(self, block, input_alphabet): """ Returns a transducer counting the number of (possibly @@ -185,7 +455,6 @@ def CountSubblockOccurrences(self, block, input_alphabet): Check some sequence:: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: T([0, 1, 0, 1, 1, 0]) [0, 0, 1, 0, 0, 1] @@ -203,7 +472,6 @@ def CountSubblockOccurrences(self, block, input_alphabet): Check some sequence:: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: T([0, 1, 0, 1, 1, 0]) [0, 0, 0, 0, 1, 0] @@ -228,10 +496,12 @@ def CountSubblockOccurrences(self, block, input_alphabet): Transition from (1, 0, 1) to (): 2|0] sage: input = [0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 2] sage: output = [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0] - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: T(input) == output True + .. SEEALSO:: + + :meth:`~AutomatonGenerators.ContainsWord` """ block_as_tuple = tuple(block) @@ -259,6 +529,7 @@ def transition_function(read, input): s.is_final = True return T + def Wait(self, input_alphabet, threshold=1): r""" Writes ``False`` until reading the ``threshold``-th occurrence @@ -302,6 +573,7 @@ def transition(state, input): return T + def map(self, f, input_alphabet): r""" Return a transducer which realizes a function @@ -535,7 +807,6 @@ def any(self, input_alphabet, number_of_operands=2): input_alphabet, number_of_operands) - def add(self, input_alphabet, number_of_operands=2): """ Returns a transducer which realizes addition on pairs over the @@ -630,6 +901,7 @@ def sub(self, input_alphabet): """ return self.operator(operator.sub, input_alphabet) + def weight(self, input_alphabet, zero=0): r""" Returns a transducer which realizes the Hamming weight of the input @@ -752,7 +1024,6 @@ def GrayCode(self): sage: G = transducers.GrayCode() sage: G Transducer with 3 states - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: for v in srange(0, 10): ....: print v, G(v.digits(base=2)) 0 [] @@ -768,7 +1039,7 @@ def GrayCode(self): In the example :ref:`Gray Code ` in the documentation of the - :mod:`~sage.combinat.finite_state_machine` module, the Gray code + :doc:`finite_state_machine` module, the Gray code transducer is derived from the algorithm converting the binary expansion to the Gray code. The result is the same as the one given here. @@ -1036,6 +1307,7 @@ def to_list(output): raise ValueError("%d is less than %d." % (base_power_K, base)) + from sage.symbolic.operators import add_vararg if right_side.operator() == add_vararg: function_calls = [o for o in right_side.operands() if o.operator() == function] @@ -1727,11 +1999,10 @@ def f(n): "Too many initial conditions, only give one of %s." % cycle[1:]) required_initial_values.update(intersection) - output_sum = reduce(operator.add, - [edge[2] - for edge in recursion_digraph.\ - outgoing_edge_iterator(cycle[1:])], - []) + output_sum = sum([edge[2] + for edge in recursion_digraph.\ + outgoing_edge_iterator(cycle[1:])], + []) if not is_zero(output_sum): raise ValueError( "Conflicting recursion for %s." % @@ -1755,5 +2026,6 @@ def f(n): return T -# Easy access to the transducer generators from the command line: +# Easy access to the automaton and transducer generators from the command line: +automata = AutomatonGenerators() transducers = TransducerGenerators() diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 323562aa98d..3a9c4679b2a 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -12,23 +12,19 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element, have_same_parent from sage.structure.parent import Parent -from sage.structure.element import have_same_parent from sage.structure.indexed_generators import IndexedGenerators -from sage.modules.free_module_element import vector from sage.misc.misc import repr_lincomb from sage.modules.module import Module from sage.rings.all import Integer import sage.structure.element from sage.combinat.family import Family from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct +from sage.combinat.cartesian_product import CartesianProduct_iters from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets from sage.misc.cachefunc import cached_method -from sage.misc.all import lazy_attribute -from sage.categories.poor_man_map import PoorManMap +from sage.misc.lazy_attribute import lazy_attribute from sage.categories.all import Category, Sets, ModulesWithBasis from sage.combinat.dict_addition import dict_addition, dict_linear_combination -from sage.sets.family import Family from sage.typeset.ascii_art import AsciiArt, empty_ascii_art # TODO: move the content of this class to CombinatorialFreeModule.Element and ModulesWithBasis.Element @@ -131,12 +127,19 @@ def __hash__(self): """ return hash(frozenset(self._monomial_coefficients.items())) - def monomial_coefficients(self): + def monomial_coefficients(self, copy=True): """ Return the internal dictionary which has the combinatorial objects indexing the basis as keys and their corresponding coefficients as values. + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + EXAMPLES:: sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) @@ -168,6 +171,8 @@ def monomial_coefficients(self): sage: d[ Partition([3,2]) ] 2 """ + if copy: + return dict(self._monomial_coefficients) return self._monomial_coefficients def _sorted_items_for_printing(self): @@ -257,12 +262,10 @@ def _ascii_art_(self): s = empty_ascii_art # "" first = True - i = 0 if scalar_mult is None: scalar_mult = "*" - all_atomic = True for (monomial,c) in terms: b = repr_monomial(monomial) # PCR if c != 0: @@ -516,10 +519,10 @@ def _sub_(self, other): F = self.parent() return F._from_dict( dict_linear_combination( [ ( self._monomial_coefficients, 1 ), (other._monomial_coefficients, -1 ) ] ), remove_zeros=False ) - def _coefficient_fast(self, m, default=None): + def _coefficient_fast(self, m): """ - Returns the coefficient of m in self, where m is key in - self._monomial_coefficients. + Return the coefficient of ``m`` in ``self``, where ``m`` is key in + ``self._monomial_coefficients``. EXAMPLES:: @@ -536,238 +539,24 @@ def _coefficient_fast(self, m, default=None): sage: a._coefficient_fast(p) 1 - sage: a._coefficient_fast(p, 2) - 1 sage: a._coefficient_fast(q) 0 - sage: a._coefficient_fast(q, 2) - 2 sage: a[p] 1 sage: a[q] 0 """ - if default is None: - default = self.base_ring()(0) - return self._monomial_coefficients.get(m, default) + return self._monomial_coefficients.get(m, self.base_ring().zero()) __getitem__ = _coefficient_fast - def coefficient(self, m): - """ - EXAMPLES:: - - sage: s = CombinatorialFreeModule(QQ, Partitions()) - sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficient([4]) - 1 - sage: z.coefficient([2,1]) - -2 - sage: z.coefficient(Partition([2,1])) - -2 - sage: z.coefficient([1,2]) - Traceback (most recent call last): - ... - AssertionError: [1, 2] should be an element of Partitions - sage: z.coefficient(Composition([2,1])) - Traceback (most recent call last): - ... - AssertionError: [2, 1] should be an element of Partitions - - Test that coefficient also works for those parents that do not yet have an element_class:: - - sage: G = DihedralGroup(3) - sage: F = CombinatorialFreeModule(QQ, G) - sage: hasattr(G, "element_class") - False - sage: g = G.an_element() - sage: (2*F.monomial(g)).coefficient(g) - 2 - """ - # NT: coefficient_fast should be the default, just with appropriate assertions - # that can be turned on or off - C = self.parent()._indices - assert m in C, "%s should be an element of %s"%(m, C) - if hasattr(C, "element_class") and not isinstance(m, C.element_class): - m = C(m) - return self._coefficient_fast(m) - - - def is_zero(self): - """ - Returns True if and only self == 0. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.is_zero() - False - sage: F.zero().is_zero() - True - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: s([2,1]).is_zero() - False - sage: s(0).is_zero() - True - sage: (s([2,1]) - s([2,1])).is_zero() - True - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return all( v == zero for v in self._monomial_coefficients.values() ) - - def __len__(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: len(f) - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: len(z) - 4 - """ - return self.length() - - def length(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.length() - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.length() - 4 - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return len( [ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ] ) - - def support(self): - """ - Returns a list of the combinatorial objects indexing the basis - elements of self which non-zero coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.support() - ['a', 'c'] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.support() - [[1], [1, 1, 1], [2, 1], [4]] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return supp - - def monomials(self): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.monomials() - [B['a'], B['c']] - - sage: (F.zero()).monomials() - [] - """ - P = self.parent() - BR = P.base_ring() - zero = BR( 0 ) - one = BR( 1 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return [ P._from_dict( { key : one }, remove_zeros=False ) for key in supp ] - - def terms(self): - """ - Returns a list of the terms of ``self`` - - .. seealso:: :meth:`monomials` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.terms() - [B['a'], 2*B['c']] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - from_dict = self.parent()._from_dict - return [ from_dict( { key : value } ) for key,value in v ] - - def coefficients(self): - """ - Returns a list of the coefficients appearing on the basis elements in - self. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.coefficients() - [1, -3] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficients() - [1, 1, 1, 1] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - return [ value for key,value in v ] - def _vector_(self, new_base_ring=None): """ Returns ``self`` as a dense vector INPUT: - - ``new_base_ring`` -- a ring (default: None) + - ``new_base_ring`` -- a ring (default: ``None``) OUTPUT: a dense :func:`FreeModule` vector @@ -808,8 +597,8 @@ def _vector_(self, new_base_ring=None): sage: a == QS3.from_vector(a.to_vector()) True - If ''new_base_ring'' is specified, then a vector over - ''new_base_ring'' is returned:: + If ``new_base_ring`` is specified, then a vector over + ``new_base_ring`` is returned:: sage: a._vector_(RDF) (2.0, 0.0, 0.0, 0.0, 0.0, 4.0) @@ -838,9 +627,10 @@ def _vector_(self, new_base_ring=None): to_vector = _vector_ - def _acted_upon_(self, scalar, self_on_left = False): + def _acted_upon_(self, scalar, self_on_left=False): """ - Returns the action of a scalar on self + Return the action of ``scalar`` (an element of the base ring) on + ``self``. EXAMPLES:: @@ -920,7 +710,7 @@ def _acted_upon_(self, scalar, self_on_left = False): def __div__(self, x, self_on_left=False ): """ - Division by coefficients + Division by coefficients. EXAMPLES:: @@ -937,19 +727,19 @@ def __div__(self, x, self_on_left=False ): sage: f/2 B[2] + 2*B[3] """ - if self.base_ring().is_field(): - F = self.parent() - x = self.base_ring()( x ) - x_inv = x**-1 - D = self._monomial_coefficients - if self_on_left: - D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) - else: - D = dict_linear_combination( [ ( D, x_inv ) ] ) + if not self.base_ring().is_field(): + return self.map_coefficients(lambda c: _divide_if_possible(c, x)) - return F._from_dict( D, remove_zeros=False ) + F = self.parent() + x = self.base_ring()( x ) + x_inv = x**-1 + D = self._monomial_coefficients + if self_on_left: + D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) else: - return self.map_coefficients(lambda c: _divide_if_possible(c, x)) + D = dict_linear_combination( [ ( D, x_inv ) ] ) + + return F._from_dict( D, remove_zeros=False ) def _divide_if_possible(x, y): """ @@ -1118,7 +908,8 @@ class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): [('bracket', None), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: e = F.basis() sage: e['a'] - 3 * e['b'] @@ -1288,10 +1079,11 @@ def __init__(self, R, basis_keys, element_class = None, category = None, prefix= if element_class is not None: self.Element = element_class - # The following is needed by e.g. root systems that don't call - # the classcall and passes lists as basis_keys - if isinstance(basis_keys, (list, tuple)): - basis_keys = FiniteEnumeratedSet(basis_keys) + # The following is to ensure that basis keys is indeed a parent. + # tuple/list are converted to FiniteEnumeratedSet and set/frozenset to + # Set + # (e.g. root systems passes lists) + basis_keys = Sets()(basis_keys, enumerated_set=True) # ignore the optional 'key' since it only affects CachedRepresentation kwds.pop('key', None) @@ -1406,7 +1198,7 @@ def __contains__(self, x): def _element_constructor_(self, x): """ - Coerce x into self. + Convert ``x`` into ``self``. EXAMPLES:: @@ -1505,7 +1297,6 @@ def _element_constructor_(self, x): 2*[1, 2, 3] """ R = self.base_ring() - eclass = self.element_class #Coerce ints to Integers if isinstance(x, int): @@ -1535,7 +1326,7 @@ def _element_constructor_(self, x): def _an_element_impl(self): """ - Returns an element of self, namely the zero element. + Return an element of ``self``, namely the zero element. EXAMPLES:: @@ -1549,7 +1340,7 @@ def _an_element_impl(self): def dimension(self): """ - Returns the dimension of the combinatorial algebra (which is given + Return the dimension of the free module (which is given by the number of elements in the basis). EXAMPLES:: @@ -1584,17 +1375,19 @@ def gens(self): def set_order(self, order): """ - Sets the order of the elements of the basis. + Set the order of the elements of the basis. If :meth:`set_order` has not been called, then the ordering is the one used in the generation of the elements of self's associated enumerated set. - .. WARNING:: Many cached methods depend on this order, in - particular for constructing subspaces and quotients. - Changing the order after some computations have been - cached does not invalidate the cache, and is likely to - introduce inconsistencies. + .. WARNING:: + + Many cached methods depend on this order, in + particular for constructing subspaces and quotients. + Changing the order after some computations have been + cached does not invalidate the cache, and is likely to + introduce inconsistencies. EXAMPLES:: @@ -1612,7 +1405,7 @@ def set_order(self, order): @cached_method def get_order(self): """ - Returns the order of the elements in the basis. + Return the order of the elements in the basis. EXAMPLES:: @@ -1679,7 +1472,7 @@ def _dense_free_module(self, base_ring=None): - ``base_ring`` -- a ring or ``None`` - If ``base_ring`` is None, then the base ring of ``self`` is used. + If ``base_ring`` is ``None``, then the base ring of ``self`` is used. This method is mostly used by :meth:`CombinatorialFreeModule.Element._vector_` @@ -1702,7 +1495,7 @@ def _dense_free_module(self, base_ring=None): def from_vector(self, vector): """ - Build an element of ``self`` from a (sparse) vector + Build an element of ``self`` from a (sparse) vector. .. SEEALSO:: :meth:`get_order`, :meth:`CombinatorialFreeModule.Element._vector_` @@ -1735,101 +1528,16 @@ def __cmp__(self, other): if c: return c return 0 - def _apply_module_morphism( self, x, on_basis, codomain=False ): - """ - Returns the image of ``x`` under the module morphism defined by - extending :func:`on_basis` by linearity. - - INPUT: - - - - ``x`` : a element of self - - - ``on_basis`` - a function that takes in a combinatorial - object indexing a basis element and returns an element of the - codomain - - - ``codomain`` (optional) - the codomain of the morphism, otherwise it is computed - using :func:`on_basis` - - If ``codomain`` is not specified, then the function tries to compute the codomain - of the module morphism by finding the image of one of the elements in the - support, hence :func:`on_basis` should return an element whose parent is the - codomain. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: a = s([3]) + s([2,1]) + s([1,1,1]) - sage: b = 2*a - sage: f = lambda part: Integer( len(part) ) - sage: s._apply_module_morphism(a, f) #1+2+3 - 6 - sage: s._apply_module_morphism(b, f) #2*(1+2+3) - 12 - sage: s._apply_module_morphism(s(0), f) - 0 - sage: s._apply_module_morphism(s(1), f) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part)) - Traceback (most recent call last): - ... - ValueError: Codomain could not be determined - """ - - if x == self.zero(): - if not codomain: - B = Family(self.basis()) - try: - z = B.first() - except StopIteration: - raise ValueError('Codomain could not be determined') - codomain = on_basis(z).parent() - return codomain.zero() - else: - if not codomain: - keys = x.support() - key = keys[0] - try: - codomain = on_basis(key).parent() - except Exception: - raise ValueError('Codomain could not be determined') - - if hasattr( codomain, 'linear_combination' ): - return codomain.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - else: - return_sum = codomain.zero() - for key, coeff in x._monomial_coefficients.iteritems(): - return_sum += coeff * on_basis( key ) - return return_sum - - def _apply_module_endomorphism(self, x, on_basis): - """ - This takes in a function from the basis elements to the elements of - self and applies it linearly to a. Note that - _apply_module_endomorphism does not require multiplication on - self to be defined. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: f = lambda part: 2*s(part.conjugate()) - sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) - 2*s[2, 1] + 2*s[3] - """ - return self.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - def sum(self, iter_of_elements): """ - overrides method inherited from commutative additive monoid as it is much faster on dicts directly + Return the sum of all elements in ``iter_of_elements``. - INPUT: + Overrides method inherited from commutative additive monoid as it + is much faster on dicts directly. - - ``iter_of_elements``: iterator of elements of ``self`` + INPUT: - Returns the sum of all elements in ``iter_of_elements`` + - ``iter_of_elements`` -- iterator of elements of ``self`` EXAMPLES:: @@ -1839,26 +1547,25 @@ def sum(self, iter_of_elements): sage: F.sum( f for _ in range(5) ) 10*B[1] + 10*B[2] """ - D = dict_addition( element._monomial_coefficients for element in iter_of_elements ) return self._from_dict( D, remove_zeros=False ) - def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + INPUT: - ``iter_of_elements_coeff`` -- iterator of pairs ``(element, coeff)`` with ``element`` in ``self`` and ``coeff`` in ``self.base_ring()`` - - ``factor_on_left`` (optional) -- if ``True``, the coefficients are + - ``factor_on_left`` -- (optional) if ``True``, the coefficients are multiplied from the left if ``False``, the coefficients are multiplied from the right - Returns the linear combination `\lambda_1 v_1 + ... + \lambda_k v_k` - (resp. the linear combination `v_1 \lambda_1 + ... + v_k \lambda_k`) - where ``iter_of_elements_coeff`` iterates through the sequence - `(\lambda_1, v_1) ... (\lambda_k, v_k)`. - EXAMPLES:: sage: F = CombinatorialFreeModule(QQ,[1,2]) @@ -1871,7 +1578,7 @@ def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): def term(self, index, coeff=None): """ - Constructs a term in ``self`` + Construct a term in ``self``. INPUT: @@ -1902,15 +1609,14 @@ def _monomial(self, index): """ return self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) - # This is generic, and should be lifted into modules_with_basis @lazy_attribute def monomial(self): """ - INPUT: + Return the basis element indexed by ``i``. - - ''i'' + INPUT: - Returns the basis element indexed by i + - ``i`` -- an element of the index set EXAMPLES:: @@ -1918,61 +1624,24 @@ def monomial(self): sage: F.monomial('a') B['a'] - F.monomial is in fact (almost) a map:: + ``F.monomial`` is in fact (almost) a map:: sage: F.monomial Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field """ # Should use a real Map, as soon as combinatorial_classes are enumerated sets, and therefore parents + from sage.categories.poor_man_map import PoorManMap return PoorManMap(self._monomial, domain=self._indices, codomain=self, name="Term map") - def _sum_of_monomials(self, indices): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F._sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - """ - # TODO: optimize by calling directly _from_dict if we - # know that all indices are distinct as sum_of_terms; - # otherwise, maybe call dict_addition directly - return self.sum(self.monomial(index) for index in indices) - - @lazy_attribute - def sum_of_monomials(self): - """ - INPUT: - - - ''indices'' -- an list (or iterable) of indices of basis elements - - Returns the sum of the corresponding basis elements - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - - sage: F.sum_of_monomials(['a', 'b', 'a']) - 2*B['a'] + B['b'] - - F.sum_of_monomials is in fact (almost) a map:: - - sage: F.sum_of_monomials - A map to Free module generated by {'a', 'b', 'c'} over Rational Field - """ - # domain = sets of self.combinatorial_class(), - return PoorManMap(self._sum_of_monomials, codomain = self) - def sum_of_terms(self, terms, distinct=False): """ - Constructs a sum of terms of ``self`` + Construct a sum of terms of ``self``. INPUT: - - ``terms`` -- a list (or iterable) of pairs (index, coeff) - - ``distinct`` -- whether the indices are guaranteed to be distinct (default: ``False``) + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + - ``distinct`` -- (default: ``False``) whether the indices are + guaranteed to be distinct EXAMPLES:: @@ -1985,7 +1654,7 @@ def sum_of_terms(self, terms, distinct=False): sage: F.sum_of_terms([('a',2), ('c',3)], distinct = True) 2*B['a'] + 3*B['c'] - .. warning:: + .. WARNING:: Use ``distinct=True`` only if you are sure that the indices are indeed distinct:: @@ -2002,20 +1671,6 @@ def sum_of_terms(self, terms, distinct=False): return self._from_dict(dict(terms)) return self.sum(self.term(index, coeff) for (index, coeff) in terms) - def monomial_or_zero_if_none(self, i): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.monomial_or_zero_if_none('a') - B['a'] - sage: F.monomial_or_zero_if_none(None) - 0 - """ - if i is None: - return self.zero() - return self.monomial(i) - @cached_method def zero(self): """ @@ -2027,21 +1682,22 @@ def zero(self): """ return self._from_dict({}, remove_zeros=False) - def _from_dict( self, d, coerce=False, remove_zeros=True ): - """ - Construct an element of ``self`` from an `{index: coefficient}` dictionary + def _from_dict(self, d, coerce=False, remove_zeros=True): + r""" + Construct an element of ``self`` from an ``{index: coefficient}`` + dictionary. INPUT: - - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is the - index of a basis element and each ``coeff`` belongs to the + - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is + the index of a basis element and each ``coeff`` belongs to the coefficient ring ``self.base_ring()`` - - ``coerce`` -- a boolean (default: ``False``), whether to coerce the - ``coeff``s to the coefficient ring + - ``coerce`` -- a boolean (default: ``False``), whether to coerce + the coefficients ``coeff`` to the coefficient ring - ``remove_zeros`` -- a boolean (default: ``True``), if some - ``coeff``s may be zero and should therefore be removed + coefficients ``coeff`` may be zero and should therefore be removed EXAMPLES:: @@ -2067,7 +1723,7 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): sage: s._from_dict({part:0}) 0 - .. warning:: + .. WARNING:: With ``remove_zeros=True``, it is assumed that no coefficient of the dictionary is zero. Otherwise, this may @@ -2079,9 +1735,9 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): assert isinstance(d, dict) if coerce: R = self.base_ring() - d = dict( (key, R(coeff)) for key,coeff in d.iteritems()) + d = {key: R(coeff) for key,coeff in d.iteritems()} if remove_zeros: - d = dict( (key, coeff) for key, coeff in d.iteritems() if coeff) + d = {key: coeff for key, coeff in d.iteritems() if coeff} return self.element_class( self, d ) class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): @@ -2194,7 +1850,7 @@ def __init__(self, modules, **options): """ from sage.categories.tensor import tensor self._sets = modules - CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct(*[module.basis().keys() for module in modules]).map(tuple), **options) + CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct_iters(*[module.basis().keys() for module in modules]).map(tuple), **options) # the following is not the best option, but it's better than nothing. self._print_options['tensor_symbol'] = options.get('tensor_symbol', tensor.symbol) @@ -2568,11 +2224,11 @@ def _repr_(self): """ TESTS:: - sage: F = CombinatorialFreeModule(ZZ, [2,4,5]) - sage: CP = cartesian_product([F, F]); CP # indirect doctest - Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 5} over Integer Ring - sage: F.__custom_name = "F"; CP - F (+) F + sage: F = CombinatorialFreeModule(ZZ, [2,4,5]) + sage: CP = cartesian_product([F, F]); CP # indirect doctest + Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 5} over Integer Ring + sage: F.__custom_name = "F"; CP + F (+) F """ from sage.categories.cartesian_product import cartesian_product return cartesian_product.symbol.join(["%s"%module for module in self._sets]) @@ -2686,3 +2342,4 @@ class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inherita pass CombinatorialFreeModule.CartesianProduct = CombinatorialFreeModule_CartesianProduct + diff --git a/src/sage/combinat/free_prelie_algebra.py b/src/sage/combinat/free_prelie_algebra.py index a1d05a3304b..80dd0299ed1 100644 --- a/src/sage/combinat/free_prelie_algebra.py +++ b/src/sage/combinat/free_prelie_algebra.py @@ -81,7 +81,7 @@ class FreePreLieAlgebra(CombinatorialFreeModule): sage: x * (y * z) == (x * y) * z False - The default product is with left paretheses:: + The default product is with left parentheses:: sage: x * y * z == (x * y) * z True diff --git a/src/sage/combinat/fully_packed_loop.py b/src/sage/combinat/fully_packed_loop.py new file mode 100644 index 00000000000..a2c3ad3d1da --- /dev/null +++ b/src/sage/combinat/fully_packed_loop.py @@ -0,0 +1,1450 @@ +r""" +Fully packed loops +""" +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.structure.element import Element +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.combinat.six_vertex_model import SquareIceModel, \ +SixVertexConfiguration, SixVertexModel +from sage.combinat.alternating_sign_matrix import AlternatingSignMatrix +from sage.plot.graphics import Graphics +from sage.matrix.constructor import matrix +from sage.plot.line import line +from sage.combinat.perfect_matching import PerfectMatching +from sage.rings.arith import factorial +from sage.rings.integer import Integer +from sage.misc.all import prod +from sage.misc.lazy_attribute import lazy_attribute + +class FullyPackedLoop(Element): + r""" + A class for fully packed loops. + + A fully packed loop is a collection of non-intersecting lattice paths on a square + grid such that every vertex is part of some path, and the paths are either closed + internal loops or have endpoints corresponding to alternate points on the + boundary [Propp2001]_. They are known to be in bijection with alternating sign + matrices. + + .. SEEALSO:: + + :class:`AlternatingSignMatrix` + + To each fully packed loop, we assign a link pattern, which is the non-crossing + matching attained by seeing which points on the boundary are connected + by open paths in the fully packed loop. + + We can create a fully packed loop using the corresponding alternating sign + matrix and also extract the link pattern:: + + sage: A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl.link_pattern() + [(1, 4), (2, 3), (5, 6)] + sage: fpl + | | + | | + + -- + + + | | + | | + -- + + + -- + | | + | | + + + -- + + | | + | | + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fplb = FullyPackedLoop(B) + sage: fplb.link_pattern() + [(1, 6), (2, 5), (3, 4)] + sage: fplb + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + + The class also has a plot method:: + + sage: fpl.plot() + Graphics object consisting of 15 graphics primitives + + which gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + Note that we can also create a fully packed loop from a six vertex model configuration:: + + sage: S = SixVertexModel(3, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: S + ^ ^ ^ + | | | + --> # -> # -> # <-- + ^ ^ | + | | V + --> # -> # <- # <-- + ^ | | + | V V + --> # <- # <- # <-- + | | | + V V V + sage: fpl = FullyPackedLoop(S) + sage: fpl + | | + | | + + -- + + + | | + | | + -- + + + -- + | | + | | + + + -- + + | | + | | + + Once we have a fully packed loop we can obtain the corresponding alternating sign matrix:: + + sage: fpl.to_alternating_sign_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + + Here are some more examples using bigger ASMs:: + + sage: A = AlternatingSignMatrix([[0,1,0,0],[0,0,1,0],[1,-1,0,1],[0,1,0,0]]) + sage: S = SixVertexModel(4, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: fpl = FullyPackedLoop(S) + sage: fpl.link_pattern() + [(1, 2), (3, 6), (4, 5), (7, 8)] + sage: fpl + | | + | | + + -- + -- + + -- + | + | + -- + + -- + -- + + | | + | | + + + + -- + -- + | | | + | | | + -- + + + -- + + | | + | | + + sage: m = AlternatingSignMatrix([[0,0,1,0,0,0], + ....: [1,0,-1,0,1,0], + ....: [0,0,0,1,0,0], + ....: [0,1,0,0,-1,1], + ....: [0,0,0,0,1,0], + ....: [0,0,1,0,0,0]]) + sage: fpl = FullyPackedLoop(m) + sage: fpl.link_pattern() + [(1, 12), (2, 7), (3, 4), (5, 6), (8, 9), (10, 11)] + sage: fpl + | | | + | | | + + -- + + + -- + + -- + | | | | + | | | | + -- + -- + + + -- + -- + + | + | + + -- + + -- + -- + + -- + | | | | + | | | | + -- + + + -- + + + + | | | | | + | | | | | + + -- + + -- + + + -- + | | + | | + -- + + -- + -- + + -- + + | | | + | | | + + sage: m = AlternatingSignMatrix([[0,1,0,0,0,0,0], + ....: [1,-1,0,0,1,0,0], + ....: [0,0,0,1,0,0,0], + ....: [0,1,0,0,-1,1,0], + ....: [0,0,0,0,1,0,0], + ....: [0,0,1,0,-1,0,1], + ....: [0,0,0,0,1,0,0]]) + sage: fpl = FullyPackedLoop(m) + sage: fpl.link_pattern() + [(1, 2), (3, 4), (5, 6), (7, 8), (9, 14), (10, 11), (12, 13)] + sage: fpl + | | | | + | | | | + + -- + -- + + -- + + -- + + | | + | | + -- + -- + -- + + -- + -- + + -- + | | + | | + + -- + + -- + -- + + -- + + | | | | + | | | | + -- + + + -- + + + + -- + | | | | | | + | | | | | | + + -- + + -- + + + -- + + | | + | | + -- + + -- + -- + + + -- + -- + | | | | + | | | | + + -- + + -- + + + -- + + | | | | + | | | | + + Gyration on an alternating sign matrix/fully packed loop ``fpl`` + of the link pattern corresponding to ``fpl``:: + + sage: ASMs = AlternatingSignMatrices(3).list() + sage: ncp = FullyPackedLoop(ASMs[1]).link_pattern() # fpl's gyration orbit size is 2 + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,5): + ....: a,b=a%6+1,b%6+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(ASMs[1].gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: fpl = FullyPackedLoop(ASMs[0]) + sage: ncp = fpl.link_pattern() # fpl's gyration size is 3 + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,5): + ....: a,b=a%6+1,b%6+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(ASMs[0].gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: mat = AlternatingSignMatrix([[0,0,1,0,0,0,0],[1,0,-1,0,1,0,0],\ + [0,0,1,0,0,0,0],[0,1,-1,0,0,1,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0],[0,0,0,0,0,0,1]]) + sage: fpl = FullyPackedLoop(mat) # n=7 + sage: ncp = fpl.link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,13): + ....: a,b=a%14+1,b%14+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(mat.gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: mat = AlternatingSignMatrix([[0,0,0,1,0,0], [0,0,1,-1,1,0],\ + [0,1,0,0,-1,1], [1,0,-1,1,0,0], [0,0,1,0,0,0], [0,0,0,0,1,0]]) + sage: fpl = FullyPackedLoop(mat) # n =6 + sage: ncp = fpl.link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,11): + ....: a,b=a%12+1,b%12+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(mat.gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + More examples:: + + We can initiate a fully packed loop using an alternating sign matrix:: + + sage: A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl + | | + | | + + -- + + + | | + | | + -- + + + -- + | | + | | + + + -- + + | | + | | + sage: FullyPackedLoops(3)(A) == fpl + True + + We can also input a matrix:: + + sage: FullyPackedLoop([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + | | + | | + + -- + + + | | + | | + -- + + + -- + | | + | | + + + -- + + | | + | | + sage: FullyPackedLoop([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) ==\ + ....: FullyPackedLoops(3)([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + True + + Otherwise we initiate a fully packed loop using a six vertex model:: + + sage: S = SixVertexModel(3, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: fpl = FullyPackedLoop(S) + sage: fpl + | | + | | + + -- + + + | | + | | + -- + + + -- + | | + | | + + + -- + + | | + | | + + sage: FullyPackedLoops(3)(S) == FullyPackedLoop(S) + True + + sage: fpl.six_vertex_model().to_alternating_sign_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + + We can also input the matrix associated to a six vertex model:: + + sage: SixVertexModel(2)([[3,1],[5,3]]) + ^ ^ + | | + --> # <- # <-- + | ^ + V | + --> # -> # <-- + | | + V V + + sage: FullyPackedLoop([[3,1],[5,3]]) + | + | + + + -- + | | + | | + -- + + + | + | + + sage: FullyPackedLoops(2)([[3,1],[5,3]]) == FullyPackedLoop([[3,1],[5,3]]) + True + + Note that the matrix corresponding to a six vertex model without + the ice boundary condition is not allowed:: + + sage: SixVertexModel(2)([[3,1],[5,5]]) + ^ ^ + | | + --> # <- # <-- + | ^ + V V + --> # -> # --> + | | + V V + + sage: FullyPackedLoop([[3,1],[5,5]]) + Traceback (most recent call last): + ... + ValueError: Invalid alternating sign matrix + + sage: FullyPackedLoops(2)([[3,1],[5,5]]) + Traceback (most recent call last): + ... + ValueError: Invalid alternating sign matrix + + Note that if anything else is used to generate the fully packed loop an error will occur:: + + sage: fpl = FullyPackedLoop(5) + Traceback (most recent call last): + ... + ValueError: Invalid alternating sign matrix + + sage: fpl = FullyPackedLoop((1, 2, 3)) + Traceback (most recent call last): + ... + ValueError: The alternating sign matrices must be square + + sage: SVM = SixVertexModel(3)[0] + sage: FullyPackedLoop(SVM) + Traceback (most recent call last): + ... + ValueError: Invalid alternating sign matrix + + REFERENCES: + + .. [Propp2001] James Propp. + *The Many Faces of Alternating Sign Matrices*, + Discrete Mathematics and Theoretical Computer Science 43 (2001): 58 + :arxiv:`math/0208125` + + .. [Striker2015] Jessica Striker. + *The toggle group, homomesy, and the Razumov-Stroganov correspondence*, + Electron. J. Combin. 22 (2015) no. 2 + :arxiv:`1503.08898` + """ + __metaclass__ = InheritComparisonClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, generator): + """ + Create a FPL. + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: FullyPackedLoop(A) + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + + sage: SVM = SixVertexModel(4, boundary_conditions='ice')[0] + sage: FullyPackedLoop(SVM) + | | + | | + + + -- + + -- + | | | + | | | + -- + + + -- + + | | + | | + + -- + + + -- + | | | + | | | + -- + + -- + + + | | + | | + """ + if isinstance(generator, AlternatingSignMatrix): + SVM = generator.to_six_vertex_model() + elif isinstance(generator, SquareIceModel.Element): + SVM = generator + elif isinstance(generator, SixVertexConfiguration): + # Check that this is an ice square model + generator = SixVertexModel(generator.parent()._nrows, \ + boundary_conditions='ice')(generator) + M = generator.to_alternating_sign_matrix().to_matrix() + M = AlternatingSignMatrix(M) + SVM = generator + else: # Not ASM nor SVM + try: + SVM = AlternatingSignMatrix(generator).to_six_vertex_model() + except (TypeError, ValueError): + generator = matrix(generator) + generator = SixVertexModel(generator.nrows(), boundary_conditions='ice')(generator) + # Check that this is an ice square model + M = generator.to_alternating_sign_matrix() + SVM = generator + + if not SVM: + raise TypeError('generator for FullyPackedLoop must either be an \ + AlternatingSignMatrix or a SquareIceModel.Element') + FPLs = FullyPackedLoops(len(SVM)) + return FPLs(generator) + + def __init__(self, parent, generator): + """ + Initialise object, can take ASM of FPL as generator. + + TESTS:: + + sage: A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: TestSuite(fpl).run() + + """ + if isinstance(generator, AlternatingSignMatrix): + self._six_vertex_model = generator.to_six_vertex_model() + elif isinstance(generator, SquareIceModel.Element): + self._six_vertex_model = generator + + self.configuration = matrix(list(self._six_vertex_model)) + self._n = len(self._end_point_dictionary)/2 + Element.__init__(self, parent) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + + sage: A = AlternatingSignMatrix([[0,1,0,0],[0,0,1,0],[1,-1,0,1],[0,1,0,0]]) + sage: S = SixVertexModel(4, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: fpl = FullyPackedLoop(S) + sage: fpl + | | + | | + + -- + -- + + -- + | + | + -- + + -- + -- + + | | + | | + + + + -- + -- + | | | + | | | + -- + + + -- + + | | + | | + + """ + # List are in the order of URDL + # One set of rules for how to draw around even vertex, one set of rules for odd vertex + n=len(self._six_vertex_model)-1 + ascii1 = [[r' ', ' -', r' ', '- '], # LR + [r' | ', ' ', r' ', '- '], # LU + [r' ', ' ', r' | ', '- '], # LD + [r' | ', ' ', r' | ', ' '], # UD + [r' | ', ' -', r' ', ' '], # UR + [r' ', ' -', r' | ', ' ']] # RD + + ascii2 = [[r' | ', ' ', r' | ', ' '], # LR + [r' ', ' -', r' | ', ' '], # LU + [r' | ', ' -', r' ', ' '], # LD + [r' ', ' -', r' ', '- '], # UD + [r' ', ' ', r' | ', '- '], # UR + [r' | ', ' ', r' ', '- ']] # RD + ret = ' ' + # Do the top line + for i,entry in enumerate(self._six_vertex_model[0]): + if i % 2 == 0: + ret += ' | ' + else: + ret += ' ' + + plus_sign = '+' + + # Do the meat of the ascii art + for j,row in enumerate(self._six_vertex_model): + ret += '\n ' + # Do the top row + for i,entry in enumerate(row): + if (i+j) % 2 == 0: + ret += ascii1[entry][0] + else: + ret += ascii2[entry][0] + ret += '\n' + + # Do the left-most entry + if (j) % 2 == 0: + ret += ' ' + else: + ret += ' -' + + # Do the middle row + for i,entry in enumerate(row): + if (i+j) % 2 == 0: + ret += ascii1[entry][3] + plus_sign + ascii1[entry][1] + else: + ret += ascii2[entry][3] + plus_sign + ascii2[entry][1] + + # Do the right-most entry + if (j+n) % 2 ==0: + ret += ' ' + else: + ret += '- ' + + # Do the bottom row + ret += '\n ' + for i,entry in enumerate(row): + if (i+j) % 2 ==0: + ret += ascii1[entry][2] + else: + ret += ascii2[entry][2] + + # Do the bottom line + ret += '\n ' + for i,entry in enumerate(self._six_vertex_model[-1]): + if (i+n+1) % 2 ==0: + ret += ' ' + else: + ret += ' | ' + + return ret + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: A = AlternatingSignMatrices(3) + sage: M = A.random_element() + sage: FullyPackedLoop(M) == M.to_fully_packed_loop() + True + + sage: FullyPackedLoop(A([[1, 0, 0],[0, 1, 0],[0, 0, 1]])) ==\ + FullyPackedLoop(A([[1, 0, 0],[0, 0, 1],[0, 1, 0]])) + False + + sage: FullyPackedLoop(M) == M + False + """ + return repr(self) == repr(other) and \ + self._end_point_dictionary == other._end_point_dictionary\ + and self._six_vertex_model == other._six_vertex_model + + def to_alternating_sign_matrix(self): + """ + Returns the alternating sign matrix corresponding to this class. + + .. SEEALSO:: + + :class:`AlternatingSignMatrix` + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: S = SixVertexModel(3, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: fpl = FullyPackedLoop(S) + sage: fpl.to_alternating_sign_matrix() + [ 0 1 0] + [ 1 -1 1] + [ 0 1 0] + sage: A = AlternatingSignMatrix([[0,1,0,0],[0,0,1,0],[1,-1,0,1],[0,1,0,0]]) + sage: S = SixVertexModel(4, boundary_conditions='ice').from_alternating_sign_matrix(A) + sage: fpl = FullyPackedLoop(S) + sage: fpl.to_alternating_sign_matrix() + [ 0 1 0 0] + [ 0 0 1 0] + [ 1 -1 0 1] + [ 0 1 0 0] + """ + return self._six_vertex_model.to_alternating_sign_matrix() + + def plot(self): + r""" + Return a graphical object of the Fully Packed Loop + + EXAMPLES: + + Here is the fully packed loop for + + .. MATH:: + + \begin{pmatrix} 0&1&1 \\ 1&-1&1 \\ 0&1&0 \end{pmatrix}: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + Here is how Sage represents this:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: print fpl.plot().description() + Line defined by 2 points: [(-1.0, 1.0), (0.0, 1.0)] + Line defined by 2 points: [(0.0, 0.0), (0.0, -1.0)] + Line defined by 2 points: [(0.0, 0.0), (1.0, 0.0)] + Line defined by 2 points: [(0.0, 2.0), (0.0, 3.0)] + Line defined by 2 points: [(0.0, 2.0), (0.0, 3.0)] + Line defined by 2 points: [(0.0, 2.0), (1.0, 2.0)] + Line defined by 2 points: [(1.0, 1.0), (0.0, 1.0)] + Line defined by 2 points: [(1.0, 1.0), (2.0, 1.0)] + Line defined by 2 points: [(2.0, 0.0), (1.0, 0.0)] + Line defined by 2 points: [(2.0, 0.0), (2.0, -1.0)] + Line defined by 2 points: [(2.0, 2.0), (1.0, 2.0)] + Line defined by 2 points: [(2.0, 2.0), (2.0, 3.0)] + Line defined by 2 points: [(2.0, 2.0), (2.0, 3.0)] + Line defined by 2 points: [(3.0, 1.0), (2.0, 1.0)] + Line defined by 2 points: [(3.0, 1.0), (2.0, 1.0)] + + Here are the other 3 by 3 Alternating Sign Matrices and their corresponding + fully packed loops: + + .. MATH:: + + A = \begin{pmatrix} 1&0&0 \\ 0&1&0 \\ 0&0&1 \\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + .. MATH:: + + A = \begin{pmatrix} 1&0&0 \\ 0&0&1 \\ 0&1&0 \\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[1, 0, 0], [0, 0, 1], [0, 1, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + .. MATH:: + + A = \begin{pmatrix} 0&1&0\\ 1&0&0\\ 0&0&1\\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + .. MATH:: + + A = \begin{pmatrix} 0&1&0\\ 0&0&1\\ 1&0&0\\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + .. MATH:: + + A = \begin{pmatrix} 0&0&1\\ 1&0&0\\ 0&1&0\\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + .. MATH:: + + A = \begin{pmatrix} 0&0&1\\ 0&1&0\\ 1&0&0\\ \end{pmatrix} + + gives: + + .. PLOT:: + :width: 200 px + + A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[0, 1, 0, 0], [1, -1, 0, 1], \ + [0, 1, 0, 0],[0, 0, 1, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: print fpl.plot().description() + Line defined by 2 points: [(-1.0, 0.0), (0.0, 0.0)] + Line defined by 2 points: [(-1.0, 2.0), (0.0, 2.0)] + Line defined by 2 points: [(0.0, 1.0), (0.0, 0.0)] + Line defined by 2 points: [(0.0, 1.0), (1.0, 1.0)] + Line defined by 2 points: [(0.0, 3.0), (0.0, 4.0)] + Line defined by 2 points: [(0.0, 3.0), (0.0, 4.0)] + Line defined by 2 points: [(0.0, 3.0), (1.0, 3.0)] + Line defined by 2 points: [(1.0, 0.0), (1.0, -1.0)] + Line defined by 2 points: [(1.0, 0.0), (2.0, 0.0)] + Line defined by 2 points: [(1.0, 2.0), (0.0, 2.0)] + Line defined by 2 points: [(1.0, 2.0), (2.0, 2.0)] + Line defined by 2 points: [(2.0, 1.0), (1.0, 1.0)] + Line defined by 2 points: [(2.0, 1.0), (2.0, 2.0)] + Line defined by 2 points: [(2.0, 3.0), (1.0, 3.0)] + Line defined by 2 points: [(2.0, 3.0), (2.0, 4.0)] + Line defined by 2 points: [(2.0, 3.0), (2.0, 4.0)] + Line defined by 2 points: [(3.0, 0.0), (2.0, 0.0)] + Line defined by 2 points: [(3.0, 0.0), (3.0, -1.0)] + Line defined by 2 points: [(3.0, 2.0), (3.0, 1.0)] + Line defined by 2 points: [(3.0, 2.0), (3.0, 3.0)] + Line defined by 2 points: [(4.0, 1.0), (3.0, 1.0)] + Line defined by 2 points: [(4.0, 1.0), (3.0, 1.0)] + Line defined by 2 points: [(4.0, 3.0), (3.0, 3.0)] + Line defined by 2 points: [(4.0, 3.0), (3.0, 3.0)] + + Here is the plot: + + .. PLOT:: + :width: 300 px + + A = AlternatingSignMatrix([[0, 1, 0, 0], [1, -1, 0, 1], [0, 1, 0, 0],[0, 0, 1, 0]]) + fpl = FullyPackedLoop(A) + p = fpl.plot() + sphinx_plot(p) + + """ + G = Graphics() + n=len(self._six_vertex_model)-1 + for j,row in enumerate(reversed(self._six_vertex_model)): + for i,entry in enumerate(row): + if i == 0 and (i+j+n+1) % 2 ==0: + G+= line([(i-1,j),(i,j)]) + if i == n and (i+j+n+1) % 2 ==0: + G+= line([(i+1,j),(i,j)]) + if j == 0 and (i+j+n) % 2 ==0: + G+= line([(i,j),(i,j-1)]) + if j == n and (i+j+n) % 2 ==0: + G+= line([(i,j),(i,j+1)]) + if entry == 0: # LR + if (i+j+n) % 2==0: + G += line([(i,j), (i+1,j)]) + else: + G += line([(i,j),(i,j+1)]) + elif entry == 1: # LU + if (i+j+n) % 2 ==0: + G += line([(i,j), (i,j+1)]) + else: + G += line([(i+1,j), (i,j)]) + elif entry == 2: # LD + if (i+j+n) % 2 == 0: + pass + else: + G += line([(i,j+1), (i,j)]) + G += line([(i+1,j), (i,j)]) + elif entry == 3: # UD + if (i+j+n) % 2 == 0: + G += line([(i,j), (i,j+1)]) + else: + G += line([(i+1,j), (i,j)]) + elif entry == 4: # UR + if (i+j+n) % 2 ==0: + G += line([(i,j), (i,j+1)]) + G += line([(i,j), (i+1,j)]) + else: + pass + elif entry == 5: # RD + if (i+j+n) % 2 ==0: + G += line([(i,j), (i+1,j)]) + else: + G += line([(i,j+1), (i,j)]) + G.axes(False) + return G + + def gyration(self): + r""" + Return the fully packed loop obtained by applying gyration + to the alternating sign matrix in bijection with ``self``. + + Gyration was first defined in [Wieland00]_ as an action on + fully-packed loops. + + REFERENCES: + + .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of + alternating sign matrices*. Electron. J. Combin. 7 (2000). + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl.gyration().to_alternating_sign_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + sage: asm = AlternatingSignMatrix([[0, 0, 1],[1, 0, 0],[0, 1, 0]]) + sage: f = FullyPackedLoop(asm) + sage: f.gyration().to_alternating_sign_matrix() + [0 1 0] + [0 0 1] + [1 0 0] + """ + return FullyPackedLoop(self.to_alternating_sign_matrix().gyration()) + + + def link_pattern(self): + """ + Return a link pattern corresponding to a fully packed loop. + + Here we define a link pattern `LP` to be a partition of the list + `[1, ..., 2k]` into 2-element sets (such a partition is also known as + a perfect matching) such that the following non-crossing condition holds: + Let the numbers `1, ..., 2k` be written on the perimeter of a circle. + For every 2-element set `(a,b)` of the partition `LP`, draw an arc + linking the two numbers `a` and `b`. We say that `LP` is non-crossing + if every arc can be drawn so that no two arcs intersect. + + Since every endpoint of a fully packed loop `fpl` is connected to a different + endpoint, there is a natural surjection from the fully packed loops on an + nxn grid onto the link patterns on the list `[1, \dots, 2n]`. + The pairs of connected endpoints of a fully packed loop `fpl` correspond to + the 2-element tuples of the corresponding link pattern. + + .. SEEALSO:: + + :class:`PerfectMatching` + + .. NOTE:: + + by convention, we choose the top left vertex to be even. + See [Propp2001]_ and [Striker2015]_. + + EXAMPLES: + + We can extract the underlying link pattern (a non-crossing + partition) from a fully packed loop:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl.link_pattern() + [(1, 2), (3, 6), (4, 5)] + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(B) + sage: fpl.link_pattern() + [(1, 6), (2, 5), (3, 4)] + + Gyration on an alternating sign matrix/fully packed loop ``fpl`` + corresponds to a rotation (i.e. a becomes a-1 mod 2n) + of the link pattern corresponding to ``fpl``:: + + sage: ASMs = AlternatingSignMatrices(3).list() + sage: ncp = FullyPackedLoop(ASMs[1]).link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,5): + ....: a,b=a%6+1,b%6+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(ASMs[1].gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: fpl = FullyPackedLoop(ASMs[0]) + sage: ncp = fpl.link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,5): + ....: a,b=a%6+1,b%6+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(ASMs[0].gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: mat = AlternatingSignMatrix([[0,0,1,0,0,0,0],[1,0,-1,0,1,0,0],[0,0,1,0,0,0,0],\ + [0,1,-1,0,0,1,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0],[0,0,0,0,0,0,1]]) + sage: fpl = FullyPackedLoop(mat) # n=7 + sage: ncp = fpl.link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,13): + ....: a,b=a%14+1,b%14+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(mat.gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + sage: mat = AlternatingSignMatrix([[0,0,0,1,0,0], [0,0,1,-1,1,0], [0,1,0,0,-1,1], [1,0,-1,1,0,0], \ + [0,0,1,0,0,0], [0,0,0,0,1,0]]) + sage: fpl = FullyPackedLoop(mat) + sage: ncp = fpl.link_pattern() + sage: rotated_ncp=[] + sage: for (a,b) in ncp: + ....: for i in range(0,11): + ....: a,b=a%12+1,b%12+1; + ....: rotated_ncp.append((a,b)) + sage: PerfectMatching(mat.gyration().to_fully_packed_loop().link_pattern()) ==\ + PerfectMatching(rotated_ncp) + True + + TESTS: + + We test previous two bugs which showed up when this method is called twice:: + + sage: A = AlternatingSignMatrices(6) + sage: B = A.random_element() + sage: C = FullyPackedLoop(B) + sage: D = C.link_pattern() + sage: E = C.link_pattern() + sage: D == E + True + + """ + link_pattern = [] + boundary_d = self._end_point_dictionary.copy() + vertices_d = self._vertex_dictionary.copy() + + while len(boundary_d) > 2: + startpoint = boundary_d.keys()[0] + position = boundary_d[startpoint] + + boundary_d.pop(startpoint) + vertices_d[position] = False # allows us to start + + while not vertices_d[position]: + vertices_d.pop(position) + choices = self._get_coordinates(position) + if choices[0] in vertices_d: + position = choices[0] + elif choices[1] in vertices_d: + position = choices[1] + else: + raise ValueError('No valid choices') + + endpoint = vertices_d[position] + vertices_d.pop(position) + link_pattern.append((startpoint, endpoint)) + boundary_d.pop(endpoint) + + link_pattern.append(tuple(boundary_d.keys())) + + return link_pattern + + def six_vertex_model(self): + """ + Return the underlying six vertex model configuration. + + EXAMPLES:: + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(B) + sage: fpl + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + sage: fpl.six_vertex_model() + ^ ^ ^ + | | | + --> # <- # <- # <-- + | ^ ^ + V | | + --> # -> # <- # <-- + | | ^ + V V | + --> # -> # -> # <-- + | | | + V V V + """ + return self._six_vertex_model + + @lazy_attribute + def _vertex_dictionary(self): + """ + A helper function for :meth:`link_pattern`. + Return a dictionary where the keys are the coordinates of each vertex and + the values are either 0 or the values 1 ,..., 2n. The vertices connected to + endpoints of paths have values 1, ..., 2n. Other vertices have values 0. + + TESTS:: + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(B) + sage: fpl._vertex_dictionary + {(0, 0): 1, + (0, 1): 0, + (0, 2): 2, + (1, 0): 6, + (1, 1): 0, + (1, 2): 3, + (2, 0): 5, + (2, 1): 0, + (2, 2): 4} + sage: fpl._vertex_dictionary[(2,2)] + 4 + """ + n = len(self._six_vertex_model) + vertices = {} + for i in range(n): + for j in range(n): + vertices[(i, j)] = 0 + + for end, vertex in self._end_point_dictionary.iteritems(): + vertices[vertex] = end + + return vertices + + def _get_coordinates(self, current_pos): + """ + Return a list of 2 coordinates that refer to the moves that could + potentialy be made. + + TESTS:: + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(B) + sage: matrix(list(fpl._six_vertex_model)) + [3 1 1] + [5 3 1] + [5 5 3] + sage: fpl._get_coordinates((0, 1)) + [(1, 1), (0, 2)] + sage: fpl._get_coordinates((1, 1)) + [(2, 1), (0, 1)] + sage: fpl._get_coordinates((0, 0)) + [(1, 0), (-1, 0)] + sage: fpl._get_coordinates((0, 2)) + [(-1, 2), (0, 1)] + sage: fpl._get_coordinates((2, 1)) + [(1, 1), (2, 0)] + + sage: B = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: fpl = FullyPackedLoop(B) + sage: matrix(list(fpl._six_vertex_model)) + [4 3 1] + [3 0 3] + [5 3 2] + sage: fpl._get_coordinates((1, 1)) + [(1, 0), (1, 2)] + sage: fpl._get_coordinates((0, 0)) + [(-1, 0), (0, 1)] + sage: fpl._get_coordinates((0, 2)) + [(-1, 2), (0, 1)] + sage: fpl._get_coordinates((2, 1)) + [(2, 0), (2, 2)] + """ + # 0 UD, 1 RD, 2 UR, 3 LR, 4 LD, 5 LU + odd = {0: [(1, 0), (-1, 0)], + 1: [(1, 0), (0, 1)], + 2: [(-1, 0), (0, 1)], + 3: [(0, -1), (0, 1)], + 4: [(0, -1), (1, 0)], + 5: [(-1, 0), (0, -1)] + } + + # 0 LR, 1 LU, 2 LD, 3 UD, 4 UR, 5 RD + even = {0: [(0, -1), (0, 1)], + 1: [(-1, 0), (0, -1)], + 2: [(0, -1), (1, 0)], + 3: [(1, 0), (-1, 0)], + 4: [(-1, 0), (0, 1)], + 5: [(1, 0), (0, 1)] + } + + parity = sum(current_pos) + conf = self.configuration[current_pos] + + if parity % 2 == 0: + potential_directions = even[conf] + else: + potential_directions = odd[conf] + + return [(current_pos[0] + d[0], current_pos[1] + d[1]) for d in potential_directions] + + @lazy_attribute + def _end_point_dictionary(self): + r""" + A helper function for :meth:`link_pattern`. + Return a dictionary where the keys are the labels 1, ..., 2n and the values + are the coordinates of the endpoints of the paths of ``self``. + + TESTS:: + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: fpl = FullyPackedLoop(B) + sage: fpl._end_point_dictionary + {1: (0, 0), 2: (0, 2), 3: (1, 2), 4: (2, 2), 5: (2, 0), 6: (1, 0)} + sage: fpl._end_point_dictionary[2] + (0, 2) + + """ + n = len(self._six_vertex_model) + end_points = {} + + for k in range(n): + if k % 2 == 0: + # top row + end_points[1 + k/2] = (0, k) + + # bottom row + end_points[n + 1 + k/2] = (n-1, n-1-k) + + # sides for even case + if n % 2 == 0: + for k in range(n): + if k % 2 == 0: + # left side + end_points[((3*n + 2 + k)/2)] = (n-1-k, 0) + + # right side + end_points[(n + 2 + k)/2] = (k, n-1) + + # side for odd case + if n % 2 == 1: + for k in range(n): + if k % 2 == 1: + # left side + end_points[(3*n + 2 + k)/2] = (n-1-k, 0) + + # right side + end_points[(n + 2 + k)/2] = (k, n-1) + + return end_points + +class FullyPackedLoops(Parent, UniqueRepresentation): + r""" + Class of all fully packed loops on an `n \times n` grid. + + They are known to be in bijection with alternating sign matrices. + + .. SEEALSO:: + + :class:`AlternatingSignMatrices` + + INPUT: + + - ``n`` -- the number of row (and column) or grid + + EXAMPLES: + + This will create an instance to manipulate the fully packed loops of size 3:: + + sage: FPLs = FullyPackedLoops(3) + sage: FPLs + Fully packed loops on a 3x3 grid + sage: FPLs.cardinality() + 7 + + When using the square ice model, it is known that the number of + configurations is equal to the number of alternating sign matrices:: + + sage: M = FullyPackedLoops(1) + sage: len(M) + 1 + sage: M = FullyPackedLoops(4) + sage: len(M) + 42 + sage: all(len(SixVertexModel(n, boundary_conditions='ice')) + ....: == FullyPackedLoops(n).cardinality() for n in range(1, 7)) + True + """ + def __init__(self, n): + r""" + Initialize ``self``. + + TESTS:: + + sage: FPLs = FullyPackedLoops(3) + sage: TestSuite(FPLs).run() + """ + self._n = n + Parent.__init__(self, category=FiniteEnumeratedSets()) + + def __iter__(self): + """ + Iterate through ``self``. + + EXAMPLES:: + + sage: FPLs = FullyPackedLoops(2) + sage: len(FPLs) + 2 + """ + for X in SixVertexModel(self._n, boundary_conditions='ice'): + yield self.element_class(self, X) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + TESTS:: + + sage: FPLs = FullyPackedLoops(4); FPLs + Fully packed loops on a 4x4 grid + """ + return "Fully packed loops on a %sx%s grid" % (self._n,self._n) + + def __contains__(self, fpl): + """ + Check if ``fpl`` is in ``self``. + + TESTS:: + + sage: FPLs = FullyPackedLoops(3) + sage: FullyPackedLoop(AlternatingSignMatrix([[0,1,0],[1,0,0],[0,0,1]])) in FPLs + True + sage: FullyPackedLoop(AlternatingSignMatrix([[0,1,0],[1,-1,1],[0,1,0]])) in FPLs + True + sage: FullyPackedLoop(AlternatingSignMatrix([[0, 1],[1,0]])) in FPLs + False + sage: FullyPackedLoop(AlternatingSignMatrix([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]])) in FPLs + False + sage: [1,2,3] in FPLs + False + """ + if isinstance(fpl, FullyPackedLoop): + return fpl._n == self._n + return False + + def _element_constructor_(self, generator): + """ + Construct an element of ``self``. + + EXAMPLES:: + + sage: FPLs = FullyPackedLoops(4) + sage: M = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]] + sage: A = AlternatingSignMatrix(M) + sage: elt = FullyPackedLoop(A) + sage: FPL = FPLs(elt); FPL + | | + | | + + + -- + + -- + | | | + | | | + -- + + + -- + + | | + | | + + -- + + + -- + | | | + | | | + -- + + -- + + + | | + | | + + sage: FPLs(A) == FPL + True + + sage: FPLs(M) == FPL + True + + sage: FPLs(FPL._six_vertex_model) == FPL + True + + sage: FPL.parent() is FPLs + True + + sage: FPL = FullyPackedLoops(2) + sage: FPL([[3,1],[5,3]]) + | + | + + + -- + | | + | | + -- + + + | + | + """ + if isinstance(generator, AlternatingSignMatrix): + SVM = generator.to_six_vertex_model() + elif isinstance(generator, SquareIceModel.Element) or \ + isinstance(generator, SixVertexConfiguration): + SVM = generator + else: # Not ASM nor SVM + try: + SVM = AlternatingSignMatrix(generator).to_six_vertex_model() + except (TypeError, ValueError): + SVM = SixVertexModel(self._n, boundary_conditions='ice')(generator) + M = SVM.to_alternating_sign_matrix() + + if len(SVM) != self._n: + raise ValueError("invalid size") + return self.element_class(self,SVM) + + Element = FullyPackedLoop + + def size(self): + r""" + Return the size of the matrices in ``self``. + + TESTS:: + + sage: FPLs = FullyPackedLoops(4) + sage: FPLs.size() + 4 + """ + return self._n + + def cardinality(self): + r""" + Return the cardinality of ``self``. + + The number of fully packed loops on `n \times n` grid + + .. MATH:: + + \prod_{k=0}^{n-1} \frac{(3k+1)!}{(n+k)!} = \frac{1! 4! 7! 10! + \cdots (3n-2)!}{n! (n+1)! (n+2)! (n+3)! \cdots (2n-1)!}. + + EXAMPLES:: + + sage: [AlternatingSignMatrices(n).cardinality() for n in range(0, 11)] + [1, 1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460, 129534272700] + """ + return Integer(prod( [ factorial(3*k+1)/factorial(self._n+k) + for k in range(self._n)] )) + + def _an_element_(self): + """ + Return an element of ``self``. + + EXAMPLES:: + + sage: FPLs = FullyPackedLoops(3) + sage: FPLs.an_element() + | | + | | + + + -- + + | | + | | + -- + + + -- + | | + | | + + -- + + + | | + | | + """ + #ASM = AlternatingSignMatrix(matrix.identity(self._n)) + #SVM = ASM.to_six_vertex_model() + SVM = SixVertexModel(self._n,boundary_conditions='ice').an_element() + return self.element_class(self, SVM) diff --git a/src/sage/combinat/gelfand_tsetlin_patterns.py b/src/sage/combinat/gelfand_tsetlin_patterns.py index c723fc6c4c2..c08f65958e5 100644 --- a/src/sage/combinat/gelfand_tsetlin_patterns.py +++ b/src/sage/combinat/gelfand_tsetlin_patterns.py @@ -507,7 +507,7 @@ def Tokuyama_coefficient(self, name='t'): return (t+1)**(self.number_of_special_entries()) * t**(self.number_of_boxes()) -class GelfandTsetlinPatterns(Parent, UniqueRepresentation): +class GelfandTsetlinPatterns(UniqueRepresentation, Parent): """ Gelfand-Tsetlin patterns. diff --git a/src/sage/combinat/integer_list.py b/src/sage/combinat/integer_list.py index d37523b6e8a..136d7baa95f 100644 --- a/src/sage/combinat/integer_list.py +++ b/src/sage/combinat/integer_list.py @@ -1,2398 +1,15 @@ -r""" -Enumerated set of lists of integers with constraints, in inverse lexicographic order - -- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative - integers with specified constraints, in inverse lexicographic order. - -- :class:`Envelope`: a utility class for upper (lower) envelope of a - function under constraints. - -HISTORY: - -This generic tool was originally written by Hivert and Thiery in -MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in -2007. It was then completely rewritten in 2015 by Gillespie, -Schilling, and Thiery, with the help of many, to deal with -limitations and lack of robustness w.r.t. input. """ -#***************************************************************************** -# Copyright (C) 2015 Bryan Gillespie -# Nicolas M. Thiery -# Anne Schilling -# -# 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 inspect import ismethod -from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall -from sage.misc.constant_function import ConstantFunction -from sage.misc.cachefunc import cached_method -from sage.categories.enumerated_sets import EnumeratedSets -from sage.structure.list_clone import ClonableArray -from sage.structure.parent import Parent -from sage.sets.family import Family -from sage.rings.integer_ring import ZZ - -Infinity = float('+inf') - -class IntegerListsLex(Parent): - r""" - Lists of nonnegative integers with constraints, in inverse lexicographic order. - - An *integer list* is a list `l` of nonnegative integers, its *parts*. The - slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two - consecutive parts. - - This class allows to construct the set `S` of all integer lists - `l` satisfying specified bounds on the sum, the length, the slope, - and the individual parts, enumerated in *inverse* lexicographic - order, that is from largest to smallest in lexicographic - order. Note that, to admit such an enumeration, `S` is almost - necessarily finite (see :ref:`IntegerListsLex_finiteness`). - - The main purpose is to provide a generic iteration engine for all the - enumerated sets like :class:`Partitions`, :class:`Compositions`, - :class:`IntegerVectors`. It can also be used to generate many other - combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically - speaking, this is a special case of set of integral points of a polytope (or - union thereof, when the length is not fixed). - - INPUT: - - - ``min_sum`` -- a nonnegative integer (default: 0): - a lower bound on ``sum(l)``. - - - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): - an upper bound on ``sum(l)``. - - - ``n`` -- a nonnegative integer (optional): if specified, this - overrides ``min_sum`` and ``max_sum``. - - - ``min_length`` -- a nonnegative integer (default: `0`): a lower - bound on ``len(l)``. - - - ``max_length`` -- a nonnegative integer or `\infty` (default: - `\infty`): an upper bound on ``len(l)``. - - - ``length`` -- an integer (optional); overrides ``min_length`` - and ``max_length`` if specified; - - - ``min_part`` -- a nonnegative integer: a lower bounds on all - parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. - - - ``floor`` -- a list of nonnegative integers or a function: lower - bounds on the individual parts `l[i]`. - - If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 - <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a - function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. - - - ``max_part`` -- a nonnegative integer or `\infty`: an upper - bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. - - - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; - this takes the same type of input as ``floor``, except that - `\infty` is allowed in addition to integers, and the default - value is `\infty`. - - - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): - an lower bound on the slope between consecutive parts: - ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` - - - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) - an upper bound on the slope between consecutive parts: - ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` - - - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) - - - ``check`` -- boolean (default: ``True``): whether to display the - warnings raised when functions are given as input to ``floor`` - or ``ceiling`` and the errors raised when there is no proper - enumeration. - - - ``name`` -- a string or ``None`` (default: ``None``) if set, - this will be passed down to :meth:`Parent.rename` to specify the - name of ``self``. It is recommented to use directly the rename - method as this feature may become deprecated. - - - ``element_constructor`` -- a function (or callable) that creates - elements of ``self`` from a list. See also :class:`Parent`. - - - ``element_class`` -- a class for the elements of ``self`` - (default: `ClonableArray`). This merely sets the attribute - ``self.Element``. See the examples for details. - - - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` - object that will be assigned to the attribute - ``_global_options``; for internal use only (subclasses, ...). - - - .. NOTE:: - - When several lists satisfying the constraints differ only by - trailing zeroes, only the shortest one is enumerated (and - therefore counted). The others are still considered valid. - See the examples below. - - This feature is questionable. It is recommended not to rely on - it, as it may eventually be discontinued. - - EXAMPLES: - - We create the enumerated set of all lists of nonnegative integers - of length `3` and sum `2`:: - - sage: C = IntegerListsLex(2, length=3) - sage: C - Integer lists of sum 2 satisfying certain constraints - sage: C.cardinality() - 6 - sage: [p for p in C] - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - sage: [2, 0, 0] in C - True - sage: [2, 0, 1] in C - False - sage: "a" in C - False - sage: ["a"] in C - False - sage: C.first() - [2, 0, 0] - - One can specify lower and upper bounds on each part:: - - sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) - [[3, 2, 0], [2, 2, 1], [1, 2, 2]] - - When the length is fixed as above, one can also use - :class:`IntegerVectors`:: - - sage: IntegerVectors(2,3).list() - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - Using the slope condition, one can generate integer partitions - (but see :class:`Partitions`):: - - sage: list(IntegerListsLex(4, max_slope=0)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] - - The following is the list of all partitions of `7` with parts at least `2`:: - - sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - - - .. RUBRIC:: floor and ceiling conditions - - Next we list all partitions of `5` of length at most `3` which are - bounded below by ``[2,1,1]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) - [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] - - Note that ``[5]`` is considered valid, because the floor - constraints only apply to existing positions in the list. To - obtain instead the partitions containing ``[2,1,1]``, one needs to - use ``min_length`` or ``length``:: - - sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) - [[3, 1, 1], [2, 2, 1]] - - Here is the list of all partitions of `5` which are contained in - ``[3,2,2]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) - [[3, 2], [3, 1, 1], [2, 2, 1]] - - This is the list of all compositions of `4` (but see :class:`Compositions`):: - - sage: list(IntegerListsLex(4, min_part=1)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - - This is the list of all integer vectors of sum `4` and length `3`:: - - sage: list(IntegerListsLex(4, length=3)) - [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], - [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], - [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] - - For whatever it is worth, the ``floor`` and ``min_part`` - constraints can be combined:: - - sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) - sage: L.list() - [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] - - This is achieved by updating the floor upon constructing ``L``:: - - sage: [L._floor(i) for i in range(5)] - [2, 1, 2, 1, 1] - - Similarly, the ``ceiling`` and ``max_part`` constraints can be - combined:: - - sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) - sage: L.list() - [[2, 2, 0], [2, 1, 1], [1, 2, 1]] - sage: [L._ceiling(i) for i in range(5)] - [2, 2, 1, 2, 2] - - - This can be used to generate Motzkin words (see - :wikipedia:`Motzkin_number`):: - - sage: def motzkin_words(n): - ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, - ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) - sage: motzkin_words(4).list() - [[0, 1, 2, 1, 0], - [0, 1, 1, 1, 0], - [0, 1, 1, 0, 0], - [0, 1, 0, 1, 0], - [0, 1, 0, 0, 0], - [0, 0, 1, 1, 0], - [0, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 0, 0, 0]] - sage: [motzkin_words(n).cardinality() for n in range(8)] - [1, 1, 2, 4, 9, 21, 51, 127] - sage: oeis(_) # optional -- internet - 0: A001006: Motzkin numbers: number of ways of drawing any number - of nonintersecting chords joining n (labeled) points on a circle. - - or Dyck words (see also :class:`DyckWords`), through the bijection - with paths from `(0,0)` to `(n,n)` with left and up steps that remain - below the diagonal:: - - sage: def dyck_words(n): - ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) - sage: [dyck_words(n).cardinality() for n in range(8)] - [1, 1, 2, 5, 14, 42, 132, 429] - sage: dyck_words(3).list() - [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] - - - .. _IntegerListsLex_finiteness: - - .. RUBRIC:: On finiteness and inverse lexicographic enumeration - - The set of all lists of integers cannot be enumerated in inverse - lexicographic order, since there is no largest list (take `[n]` - for `n` as large as desired):: - - sage: IntegerListsLex().first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Here is a variant which could be enumerated in lexicographic order - but not in inverse lexicographic order:: - - sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) - sage: for l in L: print l - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Even when the sum is specified, it is not necessarily possible to - enumerate *all* elements in inverse lexicographic order. In the - following example, the list ``[1, 1, 1]`` will never appear in the - enumeration:: - - sage: IntegerListsLex(3).first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - If one wants to proceed anyway, one can sign a waiver by setting - ``check=False`` (again, be warned that some valid lists may never appear):: - - sage: L = IntegerListsLex(3, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - In fact, being inverse lexicographically enumerable is almost - equivalent to being finite. The only infinity that can occur would - be from a tail of numbers `0,1` as in the previous example, where - the `1` moves further and further to the right. If there is any - list that is inverse lexicographically smaller than such a - configuration, the iterator would not reach it and hence would not - be considered iterable. Given that the infinite cases are very - specific, at this point only the finite cases are supported - (without signing the waiver). - - The finiteness detection is not complete yet, so some finite cases - may not be supported either, at least not without disabling the - checks. Practical examples of such are welcome. - - .. RUBRIC:: On trailing zeroes, and their caveats - - As mentioned above, when several lists satisfying the constraints - differ only by trailing zeroes, only the shortest one is listed:: - - sage: L = IntegerListsLex(max_length=4, max_part=1) - sage: L.list() - [[1, 1, 1, 1], - [1, 1, 1], - [1, 1, 0, 1], - [1, 1], - [1, 0, 1, 1], - [1, 0, 1], - [1, 0, 0, 1], - [1], - [0, 1, 1, 1], - [0, 1, 1], - [0, 1, 0, 1], - [0, 1], - [0, 0, 1, 1], - [0, 0, 1], - [0, 0, 0, 1], - []] - - and counted:: - - sage: L.cardinality() - 16 - - Still, the others are considered as elements of `L`:: - - sage: L = IntegerListsLex(4,min_length=3,max_length=4) - sage: L.list() - [..., [2, 2, 0], ...] - - sage: [2, 2, 0] in L # in L.list() - True - sage: [2, 2, 0, 0] in L # not in L.list() ! - True - sage: [2, 2, 0, 0, 0] in L - False - - .. RUBRIC:: Specifying functions as input for the floor or ceiling - - We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: - - sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) - [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] - - .. WARNING:: - - When passing a function as ``floor`` or ``ceiling``, it may - become undecidable to detect improper inverse lexicographic - enumeration. For example, the following example has a finite - enumeration:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) - sage: L.list() - [[3], - [2, 1], - [2, 0, 1], - [1, 2], - [1, 1, 1], - [1, 0, 2], - [1, 0, 1, 1], - [0, 3], - [0, 2, 1], - [0, 1, 2], - [0, 1, 1, 1], - [0, 0, 3], - [0, 0, 2, 1], - [0, 0, 1, 2], - [0, 0, 1, 1, 1]] - - but one cannot decide whether the following has an improper - inverse lexicographic enumeration without computing the floor - all the way to ``Infinity``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - Hence a warning is raised when a function is specified as - input, unless the waiver is signed by setting ``check=False``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) - doctest:... - A function has been given as input of the floor=[...] or ceiling=[...] - arguments of IntegerListsLex. Please see the documentation for the caveats. - If you know what you are doing, you can set check=False to skip this warning. - - Similarly, the algorithm may need to search forever for a - solution when the ceiling is ultimately zero:: - - sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) - sage: L.first() # not tested: will hang forever - sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) - sage: it = iter(L) - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] - - - .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility - - Sometimes, specifying a range for the sum or the length may be too - restrictive. One would want instead to specify a list, or - iterable `L`, of acceptable values. This is easy to achieve using - a :class:`disjoint union of enumerated sets `. - Here we want to accept the values `n=0,2,3`:: - - sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Finite family - {0: Integer lists of sum 0 satisfying certain constraints, - 2: Integer lists of sum 2 satisfying certain constraints, - 3: Integer lists of sum 3 satisfying certain constraints} - sage: C.list() - [[0, 0], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - The price to pay is that the enumeration order is now *graded - lexicographic* instead of lexicographic: first choose the value - according to the order specified by `L`, and use lexicographic - order within each value. Here is we reverse `L`:: - - sage: DisjointUnionEnumeratedSets(Family([3,2,0], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[3, 0], [2, 1], [1, 2], [0, 3], - [2, 0], [1, 1], [0, 2], - [0, 0]] - - Note that if a given value appears several times, the - corresponding elements will be enumerated several times, which - may, or not, be what one wants:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - - Here is a variant where we specify acceptable values for the - length:: - - sage: DisjointUnionEnumeratedSets(Family([0,1,3], - ....: lambda l: IntegerListsLex(2, length=l))).list() - [[2], - [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - - This technique can also be useful to obtain a proper enumeration - on infinite sets by using a graded lexicographic enumeration:: - - sage: C = DisjointUnionEnumeratedSets(Family(NN, - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(C) - sage: [next(it) for i in range(10)] - [[0, 0], - [1, 0], [0, 1], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - - .. RUBRIC:: Specifying how to construct elements - - This is the list of all monomials of degree `4` which divide the - monomial `x^3y^1z^2` (a monomial being identified with its - exponent vector):: - - sage: R. = QQ[] - sage: m = [3,1,2] - sage: def term(exponents): - ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] - sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) - [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] - - Note the use of the ``element_constructor`` option to specify how - to construct elements from a plain list. - - A variant is to specify a class for the elements. With the default - element constructor, this class should take as input the parent - ``self`` and a list. Here we want the elements to be constructed - in the class :class:`Partition`:: - - sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() - [[3], [2, 1], [1, 1, 1]] - - Note that the :class:`Partition` further assumes the existence of - an attribute ``_global_options`` in the parent, hence the use of the - ``global_options`` parameter. - - .. WARNING:: - - The protocol for specifying the element class and constructor - is subject to changes. - - ALGORITHM: - - The iteration algorithm uses a depth first search through the - prefix tree of the list of integers (see also - :ref:`section-generic-integerlistlex`). While doing so, it does - some lookahead heuristics to attempt to cut dead branches. - - In most practical use cases, most dead branches are cut. Then, - roughly speaking, the time needed to iterate through all the - elements of `S` is proportional to the number of elements, where - the proportion factor is controlled by the length `l` of the - longest element of `S`. In addition, the memory usage is also - controlled by `l`, which is to say negligible in practice. - - Still, there remains much room for efficiency improvements; see - :trac:`18055`, :trac:`18056`. - - .. NOTE:: - - The generation algorithm could in principle be extended to - deal with non-constant slope constraints and with negative - parts. - - TESTS: - - This example from the combinatorics tutorial used to fail before - :trac:`17979` because the floor conditions did not satisfy the - slope conditions:: - - sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) - sage: I.list() - [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], - [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], - [6, 4, 3, 2, 1]] - - :: - - sage: Partitions(2, max_slope=-1, length=2).list() - [] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) - [[]] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) - [[]] - sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) - [[1]] - sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) - [] - sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) - [[0]] - sage: list(IntegerListsLex(3, max_length=2)) - [[3], [2, 1], [1, 2], [0, 3]] - sage: partitions = {"min_part": 1, "max_slope": 0} - sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} - sage: compositions = {"min_part": 1} - sage: integer_vectors = lambda l: {"length": l} - sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} - sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} - sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} - sage: list(IntegerListsLex(6, **partitions)) - [[6], - [5, 1], - [4, 2], - [4, 1, 1], - [3, 3], - [3, 2, 1], - [3, 1, 1, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(6, **constraints)) - [[6], - [3, 3], - [3, 2, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(1, **partitions_min_2)) - [] - sage: list(IntegerListsLex(2, **partitions_min_2)) - [[2]] - sage: list(IntegerListsLex(3, **partitions_min_2)) - [[3]] - sage: list(IntegerListsLex(4, **partitions_min_2)) - [[4], [2, 2]] - sage: list(IntegerListsLex(5, **partitions_min_2)) - [[5], [3, 2]] - sage: list(IntegerListsLex(6, **partitions_min_2)) - [[6], [4, 2], [3, 3], [2, 2, 2]] - sage: list(IntegerListsLex(7, **partitions_min_2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - sage: list(IntegerListsLex(9, **partitions_min_2)) - [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] - sage: list(IntegerListsLex(10, **partitions_min_2)) - [[10], - [8, 2], - [7, 3], - [6, 4], - [6, 2, 2], - [5, 5], - [5, 3, 2], - [4, 4, 2], - [4, 3, 3], - [4, 2, 2, 2], - [3, 3, 2, 2], - [2, 2, 2, 2, 2]] - sage: list(IntegerListsLex(4, **compositions)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - sage: list(IntegerListsLex(6, min_length=1, floor=[7])) - [] - sage: L = IntegerListsLex(10**100,length=1) - sage: L.list() - [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] - - Noted on :trac:`17898`:: - - sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) - [] - sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() - [] - sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() - [] - - Noted in :trac:`17548`, which are now fixed:: - - sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() - [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] - sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() - [] - sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() - [[4], [2, 2], [1, 1, 1, 1]] - sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() - [[6], [3, 2, 1]] - sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() - [] - sage: I = IntegerListsLex(3, max_length=2, min_part=1) - sage: I.list() - [[3], [2, 1], [1, 2]] - sage: [1,1,1] in I - False - sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) - sage: I.list() - [] - sage: [4,6] in I - False - sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) - sage: I.list() - [] - sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) - sage: I.list() - [[3, 4], [1, 2, 4]] - sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) - sage: I.list() - [[2, 2]] - sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) - sage: I.list() - [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], - [5, 3, 2], [4, 3, 2, 1]] - - - .. RUBRIC:: TESTS from comments on :trac:`17979` - - Comment 191:: - - sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) - [] - - Comment 240:: - - sage: L = IntegerListsLex(min_length=2, max_part=0) - sage: L.list() - [[0, 0]] - - .. RUBRIC:: Tests on the element constructor feature and mutability - - Internally, the iterator works on a single list that is mutated - along the way. The following test makes sure that we actually make a copy of - this list before passing it to ``element_constructor`` in order to - avoid reference effects:: - - sage: from sage.misc.c3_controlled import identity - sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=identity) - sage: list(P) - [[3], [2, 1], [1, 1, 1]] - - Same, step by step:: - - sage: it = iter(P) - sage: a = next(it); a - [3] - sage: b = next(it); b - [2, 1] - sage: a - [3] - sage: a is b - False - - Tests from `MuPAD-Combinat `_:: - - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() - 83 - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() - 53 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 30 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 43 - - sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [] - - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [0] - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs - [2] - sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1] - sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [2, 1, 2] - sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2] - sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 1] - sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 2] - sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - - This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: - - sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() - [] - """ - __metaclass__ = ClasscallMetaclass - - @staticmethod - def __classcall_private__(cls, n=None, **kwargs): - r""" - Return a disjoint union if ``n`` is a list or iterable. - - TESTS: - - Specifying a list or iterable as argument is deprecated:: - - sage: IntegerListsLex([2,2], length=2).list() - doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead - See http://trac.sagemath.org/17979 for details. - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: IntegerListsLex(NN, max_length=3) - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - """ - import collections - if isinstance(n, collections.Iterable): - from sage.misc.superseded import deprecation - deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) - else: - return typecall(cls, n=n, **kwargs) - - def __init__(self, - n=None, - length=None, min_length=0, max_length=Infinity, - floor=None, ceiling=None, - min_part=0, max_part=Infinity, - min_slope=-Infinity, max_slope=Infinity, - min_sum=0, max_sum=Infinity, - name=None, - category=None, - element_constructor=None, element_class=None, - global_options=None, - check=True): - """ - Initialize ``self``. - - TESTS:: - - sage: C = IntegerListsLex(2, length=3) - sage: C == loads(dumps(C)) - True - sage: C == loads(dumps(C)) # this did fail at some point, really! - True - sage: C is loads(dumps(C)) # todo: not implemented - True - sage: C.cardinality().parent() is ZZ - True - sage: TestSuite(C).run() - - sage: IntegerListsLex(min_sum=Infinity).list() - Traceback (most recent call last): - ... - TypeError: unable to coerce to an integer - sage: IntegerListsLex(min_sum=1.4).list() - Traceback (most recent call last): - ... - TypeError: Attempt to coerce non-integral RealNumber to Integer - """ - if category is None: - category = EnumeratedSets().Finite() - - self._check = check - - if n is not None: - min_sum = n - max_sum = n - self._min_sum = ZZ(min_sum) - self._max_sum = ZZ(max_sum) if max_sum != Infinity else Infinity - - if length is not None: - min_length = length - max_length = length - self._min_length = max(ZZ(min_length), 0) - self._max_length = ZZ(max_length) if max_length != Infinity else Infinity - - self._min_slope = ZZ(min_slope) if min_slope != -Infinity else -Infinity - self._max_slope = ZZ(max_slope) if max_slope != Infinity else Infinity - - self._min_part = ZZ(min_part) - if self._min_part < 0: - raise NotImplementedError("strictly negative min_part") - self._max_part = ZZ(max_part) if max_part != Infinity else Infinity - - # self._floor_or_ceiling_is_function will be set to ``True`` - # if a function is given as input for floor or ceiling; in - # this case a warning will be emitted, unless the user sets - # check=False. See the documentation. - self._floor_or_ceiling_is_function = False - if floor is None: - floor = 0 - elif isinstance(floor, (list, tuple)): - floor = tuple(ZZ(i) for i in floor) - if not all(i >= 0 for i in floor): - raise NotImplementedError("negative parts in floor={}".format(floor)) - elif callable(floor): - self._floor_or_ceiling_is_function = True - else: - raise TypeError("floor should be a list, tuple, or function") - self._floor = Envelope(floor, sign=-1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if ceiling is None: - ceiling = Infinity - elif isinstance(ceiling, (list, tuple)): - ceiling = tuple(ZZ(i) if i != Infinity else Infinity - for i in ceiling) - if not all(i >= 0 for i in ceiling): - raise NotImplementedError("negative parts in ceiling={}".format(ceiling)) - elif callable(ceiling): - self._floor_or_ceiling_is_function = True - else: - raise ValueError("Unable to parse value of parameter ceiling") - self._ceiling = Envelope(ceiling, sign=1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if name is not None: - self.rename(name) - - if self._floor_or_ceiling_is_function and self._check: - from warnings import warn - warn(""" -A function has been given as input of the floor=[...] or ceiling=[...] -arguments of IntegerListsLex. Please see the documentation for the caveats. -If you know what you are doing, you can set check=False to skip this warning.""") - - # Customization of the class and constructor for the elements - - # We set the following attribute to True if the element - # constructor is known to be safe and does not claim ownership - # on the input list. In this case, we can save a copy in Iter.next. - self._element_constructor_is_copy_safe = False - if element_class is not None: - self.Element = element_class - if element_constructor is not None: - if element_constructor is list or element_constructor is tuple: - self._element_constructor_is_copy_safe = True - elif issubclass(self.Element, ClonableArray): - # Not all element class support check=False - element_constructor = self._element_constructor_nocheck - self._element_constructor_is_copy_safe = True - if global_options is not None: - self.global_options = global_options - - Parent.__init__(self, element_constructor=element_constructor, - category=category) - - @cached_method - def _check_finiteness(self): - """ - Check that the constraints define a finite set. - - As mentioned in the description of this class, being finite is - almost equivalent to being inverse lexicographic iterable, - which is what we really care about. - - This set is finite if and only if: - - #. For each `i` such that there exists a list of length at - least `i+1` satisfying the constraints, there exists a - direct or indirect upper bound on the `i`-th part, that - is ``self._ceiling(i)`` is finite. - - #. There exists a global upper bound on the length. - - Failures for 1. are detected and reported later, during the - iteration, namely the first time a prefix including the `i`-th - part is explored. - - This method therefore focuses on 2., namely trying to prove - the existence of an upper bound on the length. It may fail - to do so even when the set is actually finite. - - OUTPUT: ``None`` if this method finds a proof that there - exists an upper bound on the length. Otherwise a - ``ValueError`` is raised. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_length=4) - sage: L._check_finiteness() - - The following example is infinite:: - - sage: L = IntegerListsLex(4) - sage: L._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Indeed:: - - sage: it = iter(IntegerListsLex(4, check=False)) - sage: for _ in range(10): print next(it) - [4] - [3, 1] - [3, 0, 1] - [3, 0, 0, 1] - [3, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] - - Unless ``check=False`, :meth:`_check_finiteness` is called as - soon as an iteration is attempted:: - - sage: iter(L) - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Some other infinite examples:: - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The following example is actually finite, but not detected as such:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - This is sad because the following equivalent example works just fine:: - - sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() - [[4, 3]] - - Detecting this properly would require some deeper lookahead, - and the difficulty is to decide how far this lookahead should - search. Until this is fixed, one can disable the checks:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() - [[4, 3]] - - If the ceiling or floor is a function, it is much more likely - that a finite set will not be detected as such:: - - sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(7, ceiling=lambda i:0).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The next example shows a case that is finite because we remove - trailing zeroes:: - - sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) - [[]] - sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - In the next examples, there is either no solution, or the region - is bounded:: - - sage: IntegerListsLex(min_sum=10, max_sum=5).list() - [] - sage: IntegerListsLex(max_part=1, min_slope=10).list() - [[1], []] - sage: IntegerListsLex(max_part=100, min_slope=10).first() - [100] - sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() - [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] - sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() - [[1]] - - sage: IntegerListsLex(min_length=2, max_length=1).list() - [] - sage: IntegerListsLex(min_length=-2, max_length=-1).list() - [] - sage: IntegerListsLex(min_length=-1, max_length=-2).list() - [] - sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() - [] - sage: IntegerListsLex(min_part=2, max_part=1).list() - [[]] - - sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() - [[3], [2], [1], []] - sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() - [] - sage: IntegerListsLex(7, max_part=0).list() - [] - sage: IntegerListsLex(5, max_part=0, min_slope=0).list() - [] - sage: IntegerListsLex(max_part=0).list() - [[]] - sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() - [] - """ - # Trivial cases - if self._max_length < Infinity: - return - if self._max_sum < self._min_sum: - return - if self._min_slope > self._max_slope: - return - if self._max_slope < 0: - return - if self._ceiling.limit() < self._floor.limit(): - return - if self._ceiling.limit() == 0: - # This assumes no trailing zeroes - return - if self._min_slope > 0 and self._ceiling.limit() < Infinity: - return - - # Compute a lower bound on the sum of floor(i) for i=1 to infinity - if self._floor.limit() > 0 or self._min_slope > 0: - floor_sum_lower_bound = Infinity - elif self._floor.limit_start() < Infinity: - floor_sum_lower_bound = sum(self._floor(i) for i in range(self._floor.limit_start())) - else: - floor_sum_lower_bound = 0 - if floor_sum_lower_bound > 0 and self._min_slope >= 0: - floor_sum_lower_bound = Infinity - - if self._max_sum < floor_sum_lower_bound: - return - if self._max_sum == floor_sum_lower_bound and self._max_sum < Infinity: - # This assumes no trailing zeroes - return - - # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 - if self._max_slope == 0 and \ - (self._max_sum < Infinity or - (self._ceiling.limit_start() < Infinity and - any(self._ceiling(i) == 0 for i in range(self._ceiling.limit_start()+1)))): - return - - limit_start = max(self._ceiling.limit_start(), self._floor.limit_start()) - if limit_start < Infinity: - for i in range(limit_start+1): - if self._ceiling(i) < self._floor(i): - return - - raise ValueError("Could not prove that the specified constraints yield a finite set") - - - @staticmethod - def _list_function(l, default): - r""" - Generate a function on the nonnegative integers from input. - - This method generates a function on the nonnegative integers - whose values are taken from ``l`` when the input is a valid index - in the list ``l``, and has a default value ``default`` otherwise. - - INPUT: - - - ``l`` -- a list to use as a source of values - - - ``default`` -- a default value to use for indices outside of the list +Deprecated integer list module - OUTPUT: +TESTS:: - A function on the nonnegative integers. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: f = C._list_function([1,2], Infinity) - sage: f(1) - 2 - sage: f(3) - +Infinity - """ - return lambda i: l[i] if i < len(l) else default - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, min_length=3) - sage: F = IntegerListsLex(2, length=3, element_constructor=list) - sage: G = IntegerListsLex(4, length=3) - sage: C == C - True - sage: C == D - True - sage: C == E - False - sage: C == F - False - sage: C == None - False - sage: C == G - False - - This is a minimal implementation enabling pickling tests. It - is safe, but one would want the two following objects to be - detected as equal:: - - sage: C = IntegerListsLex(2, ceiling=[1,1,1]) - sage: D = IntegerListsLex(2, ceiling=[1,1,1]) - sage: C == D - False - - TESTS: - - This used to fail due to poor equality testing. See - :trac:`17979`, comment 433:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=1))).list() - [[2], [2]] - """ - if self.__class__ != other.__class__: - return False - for key in ["_min_length", "_max_length", "_floor", "_ceiling", "_min_part", "_max_part", "_min_sum", "_max_sum", "Element"]: - if getattr(self, key) != getattr(other, key): - return False - a = self._element_constructor - b = other._element_constructor - if ismethod(a): - a = a.__func__ - if ismethod(b): - b = b.__func__ - return a == b - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, max_length=3) - sage: C != D - False - sage: C != E - True - """ - return not self == other - - def _repr_(self): - """ - Return the name of this enumerated set. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: C # indirect doctest - Integer lists of sum 2 satisfying certain constraints - - sage: C = IntegerListsLex(2, length=3, name="A given name") - sage: C - A given name - """ - if self._min_sum == self._max_sum: - return "Integer lists of sum {} satisfying certain constraints".format(self._min_sum) - elif self._max_sum == Infinity: - if self._min_sum == 0: - return "Integer lists with arbitrary sum satisfying certain constraints" - else: - return "Integer lists of sum at least {} satisfying certain constraints".format(self._min_sum) - else: - return "Integer lists of sum between {} and {} satisfying certain constraints".format(self._min_sum,self._max_sum) - - def __contains__(self, comp): - """ - Return ``True`` if ``comp`` meets the constraints imposed by the arguments. - - EXAMPLES:: - - sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) - sage: all([l in C for l in C]) - True - """ - if len(comp) < self._min_length or len(comp) > self._max_length: - return False - n = sum(comp) - if n < self._min_sum or n > self._max_sum: - return False - for i in range(len(comp)): - if comp[i] < self._floor(i): - return False - if comp[i] > self._ceiling(i): - return False - for i in range(len(comp)-1): - slope = comp[i+1] - comp[i] - if slope < self._min_slope or slope > self._max_slope: - return False - return True - - - def __iter__(self): - """ - Return an iterator for the elements of ``self``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: list(C) # indirect doctest - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - """ - if self._check: - self._check_finiteness() - return IntegerListsLexIter(self) - - def _element_constructor_nocheck(self, l): - r""" - A variant of the standard element constructor that passes - ``check=False`` to the element class. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_slope=0) - sage: L._element_constructor_nocheck([1,2,3]) - [1, 2, 3] - - When relevant, this is assigned to - ``self._element_constructor`` by :meth:`__init__`, to avoid - overhead when constructing elements from trusted data in the - iterator:: - - sage: L._element_constructor - - sage: L._element_constructor([1,2,3]) - [1, 2, 3] - """ - return self.element_class(self, l, check=False) - - class Element(ClonableArray): - """ - Element class for :class:`IntegerListsLex`. - """ - def check(self): - """ - Check to make sure this is a valid element in its - :class:`IntegerListsLex` parent. - - EXAMPLES:: - - sage: C = IntegerListsLex(4) - sage: C([4]).check() - True - sage: C([5]).check() # not implemented - False - """ - return self.parent().__contains__(self) - - -# Constants for IntegerListsLexIter._next_state -LOOKAHEAD = 5 -PUSH = 4 -ME = 3 -DECREASE = 2 -POP = 1 -STOP = 0 - -class IntegerListsLexIter: - r""" - Iterator class for IntegerListsLex. - - Let ``T`` be the prefix tree of all lists of nonnegative - integers that satisfy all constraints except possibly for - ``min_length`` and ``min_sum``; let the children of a list - be sorted decreasingly according to their last part. - - The iterator is based on a depth-first search exploration of a - subtree of this tree, trying to cut branches that do not - contain a valid list. Each call of ``next`` iterates through - the nodes of this tree until it finds a valid list to return. - - Here are the attributes describing the current state of the - iterator, and their invariants: - - - ``_parent`` -- the :class:`IntegerListsLex` object this is - iterating on; - - - ``_current_list`` -- the list corresponding to the current - node of the tree; - - - ``_j`` -- the index of the last element of ``_current_list``: - ``self._j == len(self._current_list) - 1``; - - - ``_current_sum`` -- the sum of the parts of ``_current_list``; - - - ``_search_ranges`` -- a list of same length as - ``_current_list``: the range for each part. - - Furthermore, we assume that there is no obvious contradiction - in the contraints: - - - ``self._parent._min_length <= self._parent._max_length``; - - ``self._parent._min_slope <= self._parent._max_slope`` - unless ``self._parent._min_length <= 1``. - - Along this iteration, ``next`` switches between the following - states: - - - LOOKAHEAD: determine whether the current list could be a - prefix of a valid list; - - PUSH: go deeper into the prefix tree by appending the - largest possible part to the current list; - - ME: check whether the current list is valid and if yes return it - - DECREASE: decrease the last part; - - POP: pop the last part of the current list; - - STOP: the iteration is finished. - - The attribute ``_next_state`` contains the next state ``next`` - should enter in. - """ - def __init__(self, parent): - """ - TESTS:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._j - -1 - sage: I._current_sum - 0 - """ - self._parent = parent - - self._search_ranges = [] - self._current_list = [] - self._j = -1 # index of last element of _current_list - self._current_sum = 0 # sum of parts in _current_list - - # Make sure that some invariants are respected in the iterator - if parent._min_length <= parent._max_length and \ - (parent._min_slope <= parent._max_slope or parent._min_length <= 1): - self._next_state = PUSH - else: - self._next_state = STOP - - def __iter__(self): - """ - Return ``self`` as per the iterator protocol. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: it = IntegerListsLexIter(C) - sage: it.__iter__() is it - True - """ - return self - - def _push_search(self): - """ - Push search forward, resetting attributes. - - The push may fail if it is discovered that - ``self._current_list`` cannot be extended in a valid way. - - OUTPUT: a boolean: whether the push succeeded - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._current_sum - 0 - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_list - [2] - sage: I._current_sum - 2 - sage: I._push_search() - True - sage: I._j - 1 - sage: I._search_ranges - [(0, 2), (0, 0)] - sage: I._current_list - [2, 0] - sage: I._current_sum - 2 - """ - p = self._parent - max_sum = p._max_sum - min_length = p._min_length - max_length = p._max_length - if self._j+1 >= max_length: - return False - if self._j+1 >= min_length and self._current_sum == max_sum: - # Cannot add trailing zeroes - return False - - if self._j >= 0: - prev = self._current_list[self._j] - else: - prev = None - interval = self._m_interval(self._j+1, self._parent._max_sum - self._current_sum, prev) - if interval[0] > interval[1]: - return False - - self._j += 1 - m = interval[1] - self._search_ranges.append(interval) - self._current_list.append(m) - self._current_sum += m - return True - - def _pop_search(self): - """ - Go back in search tree. Resetting attributes. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_sum - 2 - sage: I._current_list - [2] - sage: I._pop_search() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_sum - 0 - sage: I._current_list - [] - """ - if self._j >= 0: # TODO: get rid of this condition - self._j -= 1 - self._search_ranges.pop() - self._current_sum -= self._current_list[-1] - self._current_list.pop() - - def next(self): - r""" - Return the next element in the iteration. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: next(I) - [2, 0, 0] - sage: next(I) - [1, 1, 0] - """ - p = self._parent - min_sum = p._min_sum - max_length = p._max_length - search_ranges = self._search_ranges - - while True: - assert self._j == len(self._current_list) - 1 - assert self._j == len(self._search_ranges) - 1 - - # LOOK AHEAD - if self._next_state == LOOKAHEAD: - if self._lookahead(): - self._next_state = PUSH - else: - # We should reuse information about the - # reasons for this failure, to avoid when - # possible retrying with smaller values. - # We just do a special case for now: - if self._j + 1 == max_length and self._current_sum < min_sum: - self._next_state = POP - else: - self._next_state = DECREASE - - # PUSH - if self._next_state == PUSH: - if self._push_search(): - self._next_state = LOOKAHEAD - continue - self._next_state = ME - - # ME - if self._next_state == ME: - if self._j == -1: - self._next_state = STOP - else: - self._next_state = DECREASE - if self._internal_list_valid(): - return p._element_constructor( - self._current_list - if p._element_constructor_is_copy_safe - else self._current_list[:]) - - # DECREASE - if self._next_state == DECREASE: - self._current_list[-1] -= 1 - self._current_sum -= 1 - if self._current_list[-1] >= search_ranges[self._j][0]: - self._next_state = LOOKAHEAD - continue - self._next_state = POP - - # POP - if self._next_state == POP: - self._pop_search() - self._next_state = ME - continue - - # STOP - if self._next_state == STOP: - raise StopIteration() - - assert False - - def _internal_list_valid(self): - """ - Return whether the current list in the iteration variable ``self._current_list`` is a valid list. - - This method checks whether the sum of the parts in ``self._current_list`` - is in the right range, whether its length is in the - required range, and whether there are trailing zeroes. It does not check all of the - necessary conditions to verify that an arbitrary list satisfies the - constraints from the corresponding ``IntegerListsLex`` object, and should - not be used except internally in the iterator class. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._current_list - [] - sage: I._internal_list_valid() - False - sage: next(I) - [2, 0, 0] - sage: I._current_list - [2, 0, 0] - sage: I._internal_list_valid() - True - """ - p = self._parent - mu = self._current_list - nu = self._current_sum - l = self._j + 1 - good_sum = (nu >= p._min_sum and nu <= p._max_sum) - good_length = (l >= p._min_length and l <= p._max_length) - no_trailing_zeros = (l <= max(p._min_length,0) or mu[-1] != 0) - return good_sum and good_length and no_trailing_zeros - - def _m_interval(self, i, max_sum, prev=None): - r""" - Return coarse lower and upper bounds for the part ``m`` at position ``i``. - - INPUT: - - - ``i`` -- a nonnegative integer (position) - - - ``max_sum`` -- a nonnegative integer or ``+oo`` - - - ``prev`` -- a nonnegative integer or ``None`` - - Return coarse lower and upper bounds for the value ``m`` - of the part at position ``i`` so that there could exists - some list suffix `v_i,\ldots,v_k` of sum bounded by - ``max_sum`` and satisfying the floor and upper bound - constraints. If ``prev`` is specified, then the slope - conditions between ``v[i-1]=prev`` and ``v[i]=m`` should - also be satisfied. - - Additionally, this raises an error if it can be detected - that some part is neither directly nor indirectly bounded - above, which implies that the constraints possibly do not allow for - an inverse lexicographic iterator. - - OUTPUT: - - A tuple of two integers ``(lower_bound, upper_bound)``. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._m_interval(1,2) - (0, 2) - - The second part is not bounded above, hence we can not - iterate lexicographically through all the elements:: - - sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Same here:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - In the following examples however, all parts are - indirectly bounded above:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() - 24 - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() - 24 - - sage: IntegerListsLex(max_part=2, max_length=3).cardinality() - 27 - sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n - 10 - sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! - [] - sage: IntegerListsLex(length=0).list() # no part! - [[]] - """ - p = self._parent - - lower_bound = max(0, p._floor(i)) - upper_bound = min(max_sum, p._ceiling(i)) - if prev != None: - lower_bound = max(lower_bound, prev + p._min_slope) - upper_bound = min(upper_bound, prev + p._max_slope) - - ## check for infinite upper bound, in case max_sum is infinite - if p._check and upper_bound == Infinity: - # This assumes that there exists a valid list (which - # is not yet always guaranteed). Then we just - # discovered that part 'i' of this list can be made as - # large as desired, which implies that `self._parent` - # cannot be iterated in inverse lexicographic order - raise ValueError("infinite upper bound for values of m") - - return (lower_bound, upper_bound) - - def _lookahead(self): - r""" - Return whether the current list can possibly be a prefix of a valid list. - - OUTPUT: ``False`` if it is guaranteed that the current - list cannot be a prefix of a valid list and ``True`` - otherwise. - - EXAMPLES:: - - sage: it = iter(IntegerListsLex(length=3, min_sum=2, max_sum=2)) - sage: it._current_list = [0,1] # don't do this at home, kids - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - True - - sage: it = iter(IntegerListsLex(length=3, min_sum=3, max_sum=2)) - sage: it._current_list = [0,1] - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(min_length=2, max_part=0)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - True - sage: it._current_list = [0, 0] - sage: it._j = 1 - sage: it._lookahead() - True - sage: it._current_list = [0, 0, 0] - sage: it._j = 2 - sage: it._lookahead() - False - - sage: n = 10**100 - sage: it = iter(IntegerListsLex(n, length=1)) - sage: it._current_list = [n-1] - sage: it._current_sum = n-1 - sage: it._j = 0 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)) - sage: it._current_list = [2] - sage: it._current_sum = 2 - sage: it._j = 0 - sage: it._lookahead() - False - - ALGORITHM: - - Let ``j=self._j`` be the position of the last part `m` of - ``self._current_list``. The current algorithm computes, - for `k=j,j+1,\ldots`, a lower bound `l_k` and an upper - bound `u_k` for `v_0+\dots+v_k`, and stops if none of the - invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. - - The lower bound `l_k` is given by the area below - `v_0,\dots,v_{j-1}` prolongated by the lower envelope - between `j` and `k` and starting at `m`. The upper bound - `u_k` is given similarly using the upper envelope. - - The complexity of this algorithm is bounded above by - ``O(max_length)``. When ``max_length=oo``, the algorithm - is guaranteed to terminate, unless ``floor`` is a function - which is eventually constant with value `0`, or which - reaches the value `0` while ``max_slope=0``. - - Indeed, the lower bound `l_k` is increasing with `k`; in - fact it is strictly increasing, unless the local lower bound - at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, - we can conclude; we can also conclude if we know that the - floor is eventually constant with value `0`, or there is a - local lower bound at `k` is `0` and ``max_slope=0``. - - .. RUBRIC:: Room for improvement - - Improved prediction: the lower bound `l_k` does not take - the slope conditions into account, except for those imposed - by the value `m` at `j`. Similarly for `u_k`. - - Improved speed: given that `l_k` is increasing with `k`, - possibly some dichotomy could be used to search for `k`, - with appropriate caching / fast calculation of the partial - sums. Also, some of the information gained at depth `j` - could be reused at depth `j+1`. - - TESTS:: - - sage: it = iter(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - False - """ - # Check code for various termination conditions. Possible cases: - # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True - # 1. lower sum surpasses max_sum -- terminate False - # 2. iteration surpasses max_length -- terminate False - # 3. upper envelope is smaller than lower envelope -- terminate False - # 4. max_slope <= 0 -- terminate False after upper passes 0 - # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point - # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs - - m = self._current_list[-1] - j = self._j - min_sum = self._parent._min_sum - (self._current_sum-m) - max_sum = self._parent._max_sum - (self._current_sum-m) - - if min_sum > max_sum: - return False - - p = self._parent - - # Beware that without slope conditions, the functions below - # currently forget about the value m at k! - lower_envelope = self._parent._floor.adapt(m,j) - upper_envelope = self._parent._ceiling.adapt(m,j) - lower = 0 # The lower bound `l_k` - upper = 0 # The upper bound `u_k` - - assert j >= 0 - # get to smallest valid number of parts - for k in range(j, p._min_length-1): - # We are looking at lists `v_j,...,v_k` - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - return False - lower += lo - upper += up - - if j < p._min_length and min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_{min_length-1}` - return True - - k = max(p._min_length-1,j) - # Check if any of the intervals intersect the target interval - while k < p._max_length: - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - # There exists no valid list of length >= k - return False - lower += lo - upper += up - assert lower <= upper - - if lower > max_sum: - # There cannot exist a valid list `v_j,\dots,v_l` with l>=k - return False - - if (p._max_slope <= 0 and up <= 0) or \ - (p._ceiling.limit() == 0 and k > p._ceiling.limit_start()): - # This implies v_l=0 for l>=k: that is we would be generating - # a list with trailing zeroes - return False - - if min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_k` - return True - - k += 1 - - return False - - -class Envelope(object): - """ - The (currently approximated) upper (lower) envelope of a function - under the specified constraints. - - INPUT: - - - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is - considered as the function ``f(i)=f[i]``, completed for larger - `i` with ``f(i)=max_part``. - - - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... - as for :class:`IntegerListsLex` (please consult for details). - - - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` - and multiply the output with ``sign``. Setting this to `-1` can - be used to implement a lower envelope. - - The *upper envelope* `U(f)` of `f` is the (pointwise) largest - function which is bounded above by `f` and satisfies the - ``max_part`` and ``max_slope`` conditions. Furthermore, for - ``i,i+1 inf, - '_f_limit_start': 0, - '_max_part': -3, - '_max_slope': inf, - '_min_slope': 1, - '_precomputed': [-6, -5, -4, -3], - '_sign': -1} - sage: TestSuite(f).run(skip="_test_pickling") - sage: Envelope(3, sign=1/3, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer - sage: Envelope(3, sign=-2, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - ValueError: sign should be +1 or -1 - """ - # self._sign = sign for the output values (the sign change for - # f is handled here in __init__) - self._sign = ZZ(sign) - if self._sign == 1: - self._max_part = max_part - self._min_slope = min_slope - self._max_slope = max_slope - if max_part == 0: - # This uses that all entries are nonnegative. - # This is not for speed optimization but for - # setting the limit start and avoid hangs. - # See #17979: comment 389 - f = 0 - elif self._sign == -1: - self._max_part = -min_part - self._min_slope = -max_slope - self._max_slope = -min_slope - else: - raise ValueError("sign should be +1 or -1") - - # Handle different types of f and multiply f with sign - if f == Infinity or f == -Infinity or f in ZZ: - limit_start = 0 - self._max_part = min(self._sign * f, self._max_part) - f = ConstantFunction(Infinity) - elif isinstance(f, (list, tuple)): - limit_start = len(f) - f_tab = [self._sign * i for i in f] - f = lambda k: f_tab[k] if k < len(f_tab) else Infinity - else: - g = f - f = lambda k: self._sign * g(k) - # At this point, this is not really used - limit_start = Infinity - - self._f = f - # For i >= limit_start, f is constant - # This does not necessarily means that self is constant! - self._f_limit_start = limit_start - self._precomputed = [] - - if min_length > 0: - self(min_length-1) - for i in range(min_length-1,0,-1): - self._precomputed[i-1] = min(self._precomputed[i-1], self._precomputed[i] - self._min_slope) - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f == f, f == h, f == None - (True, False, False) - - This would be desirable:: - - sage: f == g # todo: not implemented - True - """ - return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f != f, f != h, f != None - (False, True, True) - - This would be desirable:: - - sage: f != g # todo: not implemented - False - """ - return not self == other - - def limit_start(self): - """ - Return from which `i` on the bound returned by ``limit`` holds. - - .. SEEALSO:: :meth:`limit` for the precise specifications. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit_start() - 3 - sage: Envelope([4,1,5], sign=-1).limit_start() - 3 - - sage: Envelope([4,1,5], max_part=2).limit_start() - 3 - - sage: Envelope(4).limit_start() - 0 - sage: Envelope(4, sign=-1).limit_start() - 0 - - sage: Envelope(lambda x: 3).limit_start() == Infinity - True - sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity - True - - sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity - True - - """ - return self._f_limit_start - - def limit(self): - """ - Return a bound on the limit of ``self``. - - OUTPUT: a nonnegative integer or `\infty` - - This returns some upper bound for the accumulation points of - this upper envelope. For a lower envelope, a lower bound is - returned instead. - - In particular this gives a bound for the value of ``self`` at - `i` for `i` large enough. Special case: for a lower envelop, - and when the limit is `\infty`, the envelope is guaranteed to - tend to `\infty` instead. - - When ``s=self.limit_start()`` is finite, this bound is - guaranteed to be valid for `i>=s`. - - Sometimes it's better to have a loose bound that starts early; - sometimes the converse holds. At this point which specific - bound and starting point is returned is not set in stone, in - order to leave room for later optimizations. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit() - inf - sage: Envelope([4,1,5], max_part=2).limit() - 2 - sage: Envelope([4,1,5], max_slope=0).limit() - 1 - sage: Envelope(lambda x: 3, max_part=2).limit() - 2 - - Lower envelopes:: - - sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() - 2 - sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() - 5 - sage: Envelope([4,1,5], sign=-1).limit() - 0 - - .. SEEALSO:: :meth:`limit_start` - """ - if self.limit_start() < Infinity and self._max_slope <= 0: - return self(self.limit_start()) - else: - return self._max_part * self._sign - - def __call__(self, k): - """ - Return the value of this envelope at `k`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([4,1,5,3,5]) - sage: f.__call__(2) - 5 - sage: [f(i) for i in range(10)] - [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] - - .. NOTE:: - - See the documentation of :class:`Envelope` for tests and - examples. - """ - if k >= len(self._precomputed): - for i in range(len(self._precomputed), k+1): - value = min(self._f(i), self._max_part) - if i>0: - value = min(value, self._precomputed[i-1] + self._max_slope) - self._precomputed.append(value) - return self._precomputed[k] * self._sign - - def adapt(self, m, j): - """ - Return this envelope adapted to an additional local constraint. - - INPUT: - - - ``m`` -- a nonnegative integer (starting value) - - - ``j`` -- a nonnegative integer (position) - - This method adapts this envelope to the additional local - constraint imposed by having a part `m` at position `j`. - Namely, this returns a function which computes, for any `i>j`, - the minimum of the ceiling function and the value restriction - given by the slope conditions. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope(3) - sage: g = f.adapt(1,1) - sage: g is f - True - sage: [g(i) for i in range(10)] - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] - - sage: f = Envelope(3, max_slope=1) - sage: g = f.adapt(1,1) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Note that, in both cases above, the adapted envelope is only - guaranteed to be valid for `i>j`! This is to leave potential - room in the future for sharing similar adapted envelopes:: - - sage: g = f.adapt(0,0) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(3,3) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Now with a lower envelope:: - - sage: f = Envelope(1, sign=-1, min_slope=-1) - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - sage: g = f.adapt(1,3) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - """ - if self._max_slope == Infinity: - return self - m *= self._sign - m = m - j * self._max_slope - return lambda i: self._sign * min(m + i*self._max_slope, self._sign*self(i) ) - - -def IntegerListsNN(**kwds): - """ - Lists of nonnegative integers with constraints. - - This function returns the union of ``IntegerListsLex(n, **kwds)`` - where `n` ranges over all nonnegative integers. - - .. WARNING:: this function is likely to disappear in :trac:`17927`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsNN - sage: L = IntegerListsNN(max_length=3, max_slope=-1) - sage: L - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(L) - sage: for _ in range(20): - ....: print next(it) - [] - [1] - [2] - [3] - [2, 1] - [4] - [3, 1] - [5] - [4, 1] - [3, 2] - [6] - [5, 1] - [4, 2] - [3, 2, 1] - [7] - [6, 1] - [5, 2] - [4, 3] - [4, 2, 1] - [8] - """ - from sage.rings.semirings.non_negative_integer_semiring import NN - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) + sage: from sage.combinat.integer_list import IntegerListsLex + sage: IntegerListsLex(3) + doctest:...: DeprecationWarning: + Importing IntegerListsLex from here is deprecated. If you need to use it, please import it directly from sage.combinat.integer_lists + See http://trac.sagemath.org/18109 for details. + Integer lists of sum 3 satisfying certain constraints +""" +from sage.misc.lazy_import import lazy_import +lazy_import('sage.combinat.integer_list_old', '*', deprecation=18109) +lazy_import('sage.combinat.integer_lists', '*', deprecation=18109) diff --git a/src/sage/combinat/integer_lists/__init__.py b/src/sage/combinat/integer_lists/__init__.py new file mode 100644 index 00000000000..e69886d8799 --- /dev/null +++ b/src/sage/combinat/integer_lists/__init__.py @@ -0,0 +1,6 @@ +from base import IntegerListsBackend, Envelope +from lists import IntegerLists +from invlex import IntegerListsLex + +from sage.structure.sage_object import register_unpickle_override +register_unpickle_override('sage.combinat.integer_list', 'IntegerListsLex', IntegerListsLex) diff --git a/src/sage/combinat/integer_lists/base.pxd b/src/sage/combinat/integer_lists/base.pxd new file mode 100644 index 00000000000..d7850331ffe --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pxd @@ -0,0 +1,15 @@ +cdef class Envelope(object): + cdef readonly sign + cdef f + cdef f_limit_start + cdef list precomputed + cdef readonly max_part + cdef readonly min_slope, max_slope + +cdef class IntegerListsBackend(object): + cdef readonly min_sum, max_sum + cdef readonly min_length, max_length + cdef readonly min_part, max_part + cdef readonly min_slope, max_slope + cdef readonly Envelope floor, ceiling + cdef public dict __cached_methods # Support cached_method diff --git a/src/sage/combinat/integer_lists/base.pyx b/src/sage/combinat/integer_lists/base.pyx new file mode 100644 index 00000000000..de53d35ddd3 --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pyx @@ -0,0 +1,702 @@ +r""" +Enumerated set of lists of integers with constraints: base classes + +- :class:`IntegerListsBackend`: base class for the Cython back-end of + an enumerated set of lists of integers with specified constraints. + +- :class:`Envelope`: a utility class for upper (lower) envelope of a + function under constraints. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# 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 cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE +from sage.misc.constant_function import ConstantFunction +from sage.structure.element cimport RingElement +from sage.rings.integer cimport Integer + +Infinity = float('+inf') +MInfinity = float('-inf') + + +cdef class IntegerListsBackend(object): + """ + Base class for the Cython back-end of an enumerated set of lists of + integers with specified constraints. + + This base implements the basic operations, including checking for + containment using :meth:`_contains`, but not iteration. For + iteration, subclass this class and implement an ``_iter()`` method. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: L = IntegerListsBackend(6, max_slope=-1) + sage: L._contains([3,2,1]) + True + """ + def __init__(self, + n=None, length=None, *, + min_length=0, max_length=Infinity, + floor=None, ceiling=None, + min_part=0, max_part=Infinity, + min_slope=MInfinity, max_slope=Infinity, + min_sum=0, max_sum=Infinity): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C = IntegerListsBackend(min_sum=1.4) + Traceback (most recent call last): + ... + TypeError: Attempt to coerce non-integral RealNumber to Integer + sage: C = IntegerListsBackend(min_sum=Infinity) + Traceback (most recent call last): + ... + TypeError: unable to coerce to an integer + """ + if n is not None: + min_sum = n + max_sum = n + self.min_sum = Integer(min_sum) if min_sum != -Infinity else -Infinity + self.max_sum = Integer(max_sum) if max_sum != Infinity else Infinity + + if length is not None: + min_length = length + max_length = length + self.min_length = Integer(max(min_length, 0)) + self.max_length = Integer(max_length) if max_length != Infinity else Infinity + + self.min_slope = Integer(min_slope) if min_slope != -Infinity else -Infinity + self.max_slope = Integer(max_slope) if max_slope != Infinity else Infinity + + self.min_part = Integer(min_part) if min_part != -Infinity else -Infinity + self.max_part = Integer(max_part) if max_part != Infinity else Infinity + + if isinstance(floor, Envelope): + self.floor = floor + else: + if floor is None: + floor = -Infinity + elif isinstance(floor, (list, tuple)): + floor = tuple(Integer(i) for i in floor) + elif callable(floor): + pass + else: + raise TypeError("floor should be a list, tuple, or function") + self.floor = Envelope(floor, sign=-1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + if isinstance(ceiling, Envelope): + self.ceiling = ceiling + else: + if ceiling is None: + ceiling = Infinity + elif isinstance(ceiling, (list, tuple)): + ceiling = tuple(Integer(i) if i != Infinity else Infinity + for i in ceiling) + elif callable(ceiling): + pass + else: + raise ValueError("Unable to parse value of parameter ceiling") + self.ceiling = Envelope(ceiling, sign=1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3).backend + sage: D = IntegerListsLex(2, length=3).backend; L = list(D._iter()) + sage: E = IntegerListsLex(2, min_length=3).backend + sage: G = IntegerListsLex(4, length=3).backend + sage: C >= C + True + sage: C == D + True + sage: C != D + False + sage: C == E + False + sage: C != E + True + sage: C == None + False + sage: C == G + False + sage: C <= G + Traceback (most recent call last): + ... + TypeError: IntegerListsBackend can only be compared for equality + """ + cdef IntegerListsBackend left = self + cdef IntegerListsBackend right = other + equal = (type(left) is type(other) and + left.min_length == right.min_length and + left.max_length == right.max_length and + left.min_sum == right.min_sum and + left.max_sum == right.max_sum and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope and + left.floor == right.floor and + left.ceiling == right.ceiling) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("IntegerListsBackend can only be compared for equality") + + def _repr_(self): + """ + Return the name of this enumerated set. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C._repr_() + 'Integer lists of sum 2 satisfying certain constraints' + """ + if self.min_sum == self.max_sum: + return "Integer lists of sum {} satisfying certain constraints".format(self.min_sum) + elif self.max_sum == Infinity: + if self.min_sum == 0: + return "Integer lists with arbitrary sum satisfying certain constraints" + else: + return "Integer lists of sum at least {} satisfying certain constraints".format(self.min_sum) + else: + return "Integer lists of sum between {} and {} satisfying certain constraints".format(self.min_sum, self.max_sum) + + def _contains(self, comp): + """ + Return ``True`` if ``comp`` meets the constraints imposed + by the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) # indirect doctest + True + """ + if len(comp) < self.min_length or len(comp) > self.max_length: + return False + n = sum(comp) + if n < self.min_sum or n > self.max_sum: + return False + for i in range(len(comp)): + if comp[i] < self.floor(i): + return False + if comp[i] > self.ceiling(i): + return False + for i in range(len(comp)-1): + slope = comp[i+1] - comp[i] + if slope < self.min_slope or slope > self.max_slope: + return False + return True + + def __getstate__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C.__getstate__() + {'ceiling': , + 'floor': , + 'max_length': 3, + 'max_part': inf, + 'max_slope': inf, + 'max_sum': 2, + 'min_length': 3, + 'min_part': 0, + 'min_slope': -inf, + 'min_sum': 2} + """ + return {"min_sum": self.min_sum, + "max_sum": self.max_sum, + "min_length": self.min_length, + "max_length": self.max_length, + "min_part": self.min_part, + "max_part": self.max_part, + "min_slope": self.min_slope, + "max_slope": self.max_slope, + "floor": self.floor, + "ceiling": self.ceiling} + + def __setstate__(self, state): + """ + Unpickle ``self`` from the state ``state``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C == loads(dumps(C)) # this did fail at some point, really! + True + sage: C is loads(dumps(C)) # todo: not implemented + True + """ + self.__init__(**state) + + +cdef class Envelope(object): + """ + The (currently approximated) upper (lower) envelope of a function + under the specified constraints. + + INPUT: + + - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is + considered as the function ``f(i)=f[i]``, completed for larger + `i` with ``f(i)=max_part``. + + - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... + as for :class:`IntegerListsLex` (please consult for details). + + - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` + and multiply the output with ``sign``. Setting this to `-1` can + be used to implement a lower envelope. + + The *upper envelope* `U(f)` of `f` is the (pointwise) largest + function which is bounded above by `f` and satisfies the + ``max_part`` and ``max_slope`` conditions. Furthermore, for + ``i,i+1= limit_start, f is constant + # This does not necessarily means that self is constant! + self.f_limit_start = limit_start + self.precomputed = [] + + if min_length > 0: + self(min_length-1) + for i in range(min_length-1,0,-1): + self.precomputed[i-1] = min(self.precomputed[i-1], self.precomputed[i] - self.min_slope) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([3,2,2]) + sage: g = Envelope([3,2,2]) + sage: h = Envelope([3,2,2], min_part=2) + sage: f == f, f == h, f == None + (True, False, False) + sage: f < f, f != h, f != None + (False, True, True) + + This would be desirable:: + + sage: f == g # todo: not implemented + True + """ + cdef Envelope left = self + cdef Envelope right = other + equal = (type(left) is type(other) and + left.sign == right.sign and + left.f == right.f and + left.f_limit_start == right.f_limit_start and + left.max_part == right.max_part and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("Envelopes can only be compared for equality") + + def limit_start(self): + """ + Return from which `i` on the bound returned by ``limit`` holds. + + .. SEEALSO:: :meth:`limit` for the precise specifications. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit_start() + 3 + sage: Envelope([4,1,5], sign=-1).limit_start() + 3 + + sage: Envelope([4,1,5], max_part=2).limit_start() + 3 + + sage: Envelope(4).limit_start() + 0 + sage: Envelope(4, sign=-1).limit_start() + 0 + + sage: Envelope(lambda x: 3).limit_start() == Infinity + True + sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity + True + + sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity + True + + """ + return self.f_limit_start + + def limit(self): + """ + Return a bound on the limit of ``self``. + + OUTPUT: a nonnegative integer or `\infty` + + This returns some upper bound for the accumulation points of + this upper envelope. For a lower envelope, a lower bound is + returned instead. + + In particular this gives a bound for the value of ``self`` at + `i` for `i` large enough. Special case: for a lower envelop, + and when the limit is `\infty`, the envelope is guaranteed to + tend to `\infty` instead. + + When ``s=self.limit_start()`` is finite, this bound is + guaranteed to be valid for `i>=s`. + + Sometimes it's better to have a loose bound that starts early; + sometimes the converse holds. At this point which specific + bound and starting point is returned is not set in stone, in + order to leave room for later optimizations. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit() + inf + sage: Envelope([4,1,5], max_part=2).limit() + 2 + sage: Envelope([4,1,5], max_slope=0).limit() + 1 + sage: Envelope(lambda x: 3, max_part=2).limit() + 2 + + Lower envelopes:: + + sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() + 2 + sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() + 5 + sage: Envelope([4,1,5], sign=-1).limit() + 0 + + .. SEEALSO:: :meth:`limit_start` + """ + if self.limit_start() < Infinity and self.max_slope <= 0: + return self(self.limit_start()) + else: + return self.max_part * self.sign + + def __call__(self, Py_ssize_t k): + """ + Return the value of this envelope at `k`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([4,1,5,3,5]) + sage: f.__call__(2) + 5 + sage: [f(i) for i in range(10)] + [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] + + .. NOTE:: + + See the documentation of :class:`Envelope` for tests and + examples. + """ + if k >= len(self.precomputed): + for i in range(len(self.precomputed), k+1): + value = min(self.f(i), self.max_part) + if i > 0: + value = min(value, self.precomputed[i-1] + self.max_slope) + self.precomputed.append(value) + return self.precomputed[k] * self.sign + + def adapt(self, m, j): + """ + Return this envelope adapted to an additional local constraint. + + INPUT: + + - ``m`` -- a nonnegative integer (starting value) + + - ``j`` -- a nonnegative integer (position) + + This method adapts this envelope to the additional local + constraint imposed by having a part `m` at position `j`. + Namely, this returns a function which computes, for any `i>j`, + the minimum of the ceiling function and the value restriction + given by the slope conditions. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope(3) + sage: g = f.adapt(1,1) + sage: g is f + True + sage: [g(i) for i in range(10)] + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + + sage: f = Envelope(3, max_slope=1) + sage: g = f.adapt(1,1) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Note that, in both cases above, the adapted envelope is only + guaranteed to be valid for `i>j`! This is to leave potential + room in the future for sharing similar adapted envelopes:: + + sage: g = f.adapt(0,0) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(3,3) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Now with a lower envelope:: + + sage: f = Envelope(1, sign=-1, min_slope=-1) + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + sage: g = f.adapt(1,3) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + """ + if self.max_slope == Infinity: + return self + m *= self.sign + m = m - j * self.max_slope + return lambda i: self.sign * min(m + i*self.max_slope, self.sign*self(i) ) + + def __reduce__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: h = Envelope(3, min_part=2) + sage: loads(dumps(h)) == h + True + """ + args = (type(self), + self.sign, self.f, self.f_limit_start, self.precomputed, + self.max_part, self.min_slope, self.max_slope) + return _unpickle_Envelope, args + + +def _unpickle_Envelope(type t, _sign, _f, _f_limit_start, _precomputed, + _max_part, _min_slope, _max_slope): + """ + Internal function to support pickling for :class:`Envelope`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import Envelope, _unpickle_Envelope + sage: _unpickle_Envelope(Envelope, + ....: 1, lambda i:i, Infinity, [], 4, -1, 3) + + """ + cdef Envelope self = t.__new__(t) + self.sign = _sign + self.f = _f + self.f_limit_start = _f_limit_start + self.precomputed = _precomputed + self.max_part = _max_part + self.min_slope = _min_slope + self.max_slope = _max_slope + return self diff --git a/src/sage/combinat/integer_lists/invlex.pxd b/src/sage/combinat/integer_lists/invlex.pxd new file mode 100644 index 00000000000..143306b4448 --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pxd @@ -0,0 +1,3 @@ +from sage.combinat.integer_lists.base cimport IntegerListsBackend +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + cdef public bint check diff --git a/src/sage/combinat/integer_lists/invlex.pyx b/src/sage/combinat/integer_lists/invlex.pyx new file mode 100644 index 00000000000..c1bbc7163ad --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pyx @@ -0,0 +1,1678 @@ +r""" +Enumerated set of lists of integers with constraints, in inverse lexicographic order + +- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative + integers with specified constraints, in inverse lexicographic order. + +- :class:`IntegerListsBackend_invlex`: Cython back-end for + :class:`IntegerListsLex`. + +HISTORY: + +This generic tool was originally written by Hivert and Thiery in +MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in +2007. It was then completely rewritten in 2015 by Gillespie, +Schilling, and Thiery, with the help of many, to deal with +limitations and lack of robustness w.r.t. input. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# +# 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.classcall_metaclass import ClasscallMetaclass, typecall +from sage.misc.cachefunc import cached_method +from sage.combinat.integer_lists.base cimport IntegerListsBackend +from sage.combinat.integer_lists.lists import IntegerLists +from sage.combinat.integer_lists.base import Infinity + + +class IntegerListsLex(IntegerLists): + r""" + Lists of nonnegative integers with constraints, in inverse + lexicographic order. + + An *integer list* is a list `l` of nonnegative integers, its *parts*. The + slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two + consecutive parts. + + This class allows to construct the set `S` of all integer lists + `l` satisfying specified bounds on the sum, the length, the slope, + and the individual parts, enumerated in *inverse* lexicographic + order, that is from largest to smallest in lexicographic + order. Note that, to admit such an enumeration, `S` is almost + necessarily finite (see :ref:`IntegerListsLex_finiteness`). + + The main purpose is to provide a generic iteration engine for all the + enumerated sets like :class:`Partitions`, :class:`Compositions`, + :class:`IntegerVectors`. It can also be used to generate many other + combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically + speaking, this is a special case of set of integral points of a polytope (or + union thereof, when the length is not fixed). + + INPUT: + + - ``min_sum`` -- a nonnegative integer (default: 0): + a lower bound on ``sum(l)``. + + - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): + an upper bound on ``sum(l)``. + + - ``n`` -- a nonnegative integer (optional): if specified, this + overrides ``min_sum`` and ``max_sum``. + + - ``min_length`` -- a nonnegative integer (default: `0`): a lower + bound on ``len(l)``. + + - ``max_length`` -- a nonnegative integer or `\infty` (default: + `\infty`): an upper bound on ``len(l)``. + + - ``length`` -- an integer (optional); overrides ``min_length`` + and ``max_length`` if specified; + + - ``min_part`` -- a nonnegative integer: a lower bounds on all + parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. + + - ``floor`` -- a list of nonnegative integers or a function: lower + bounds on the individual parts `l[i]`. + + If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 + <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a + function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. + + - ``max_part`` -- a nonnegative integer or `\infty`: an upper + bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. + + - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; + this takes the same type of input as ``floor``, except that + `\infty` is allowed in addition to integers, and the default + value is `\infty`. + + - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): + an lower bound on the slope between consecutive parts: + ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` + + - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) + an upper bound on the slope between consecutive parts: + ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` + + - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) + + - ``check`` -- boolean (default: ``True``): whether to display the + warnings raised when functions are given as input to ``floor`` + or ``ceiling`` and the errors raised when there is no proper + enumeration. + + - ``name`` -- a string or ``None`` (default: ``None``) if set, + this will be passed down to :meth:`Parent.rename` to specify the + name of ``self``. It is recommented to use directly the rename + method as this feature may become deprecated. + + - ``element_constructor`` -- a function (or callable) that creates + elements of ``self`` from a list. See also :class:`Parent`. + + - ``element_class`` -- a class for the elements of ``self`` + (default: `ClonableArray`). This merely sets the attribute + ``self.Element``. See the examples for details. + + - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` + object that will be assigned to the attribute + ``_global_options``; for internal use only (subclasses, ...). + + + .. NOTE:: + + When several lists satisfying the constraints differ only by + trailing zeroes, only the shortest one is enumerated (and + therefore counted). The others are still considered valid. + See the examples below. + + This feature is questionable. It is recommended not to rely on + it, as it may eventually be discontinued. + + EXAMPLES: + + We create the enumerated set of all lists of nonnegative integers + of length `3` and sum `2`:: + + sage: C = IntegerListsLex(2, length=3) + sage: C + Integer lists of sum 2 satisfying certain constraints + sage: C.cardinality() + 6 + sage: [p for p in C] + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + sage: [2, 0, 0] in C + True + sage: [2, 0, 1] in C + False + sage: "a" in C + False + sage: ["a"] in C + False + sage: C.first() + [2, 0, 0] + + One can specify lower and upper bounds on each part:: + + sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) + [[3, 2, 0], [2, 2, 1], [1, 2, 2]] + + When the length is fixed as above, one can also use + :class:`IntegerVectors`:: + + sage: IntegerVectors(2,3).list() + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + Using the slope condition, one can generate integer partitions + (but see :class:`Partitions`):: + + sage: list(IntegerListsLex(4, max_slope=0)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + The following is the list of all partitions of `7` with parts at least `2`:: + + sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + + + .. RUBRIC:: floor and ceiling conditions + + Next we list all partitions of `5` of length at most `3` which are + bounded below by ``[2,1,1]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + + Note that ``[5]`` is considered valid, because the floor + constraints only apply to existing positions in the list. To + obtain instead the partitions containing ``[2,1,1]``, one needs to + use ``min_length`` or ``length``:: + + sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) + [[3, 1, 1], [2, 2, 1]] + + Here is the list of all partitions of `5` which are contained in + ``[3,2,2]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) + [[3, 2], [3, 1, 1], [2, 2, 1]] + + This is the list of all compositions of `4` (but see :class:`Compositions`):: + + sage: list(IntegerListsLex(4, min_part=1)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + + This is the list of all integer vectors of sum `4` and length `3`:: + + sage: list(IntegerListsLex(4, length=3)) + [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], + [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], + [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] + + For whatever it is worth, the ``floor`` and ``min_part`` + constraints can be combined:: + + sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) + sage: L.list() + [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] + + This is achieved by updating the floor upon constructing ``L``:: + + sage: [L.floor(i) for i in range(5)] + [2, 1, 2, 1, 1] + + Similarly, the ``ceiling`` and ``max_part`` constraints can be + combined:: + + sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) + sage: L.list() + [[2, 2, 0], [2, 1, 1], [1, 2, 1]] + sage: [L.ceiling(i) for i in range(5)] + [2, 2, 1, 2, 2] + + + This can be used to generate Motzkin words (see + :wikipedia:`Motzkin_number`):: + + sage: def motzkin_words(n): + ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, + ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) + sage: motzkin_words(4).list() + [[0, 1, 2, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 0, 0], + [0, 1, 0, 1, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 0]] + sage: [motzkin_words(n).cardinality() for n in range(8)] + [1, 1, 2, 4, 9, 21, 51, 127] + sage: oeis(_) # optional -- internet + 0: A001006: Motzkin numbers: number of ways of drawing any number + of nonintersecting chords joining n (labeled) points on a circle. + + or Dyck words (see also :class:`DyckWords`), through the bijection + with paths from `(0,0)` to `(n,n)` with left and up steps that remain + below the diagonal:: + + sage: def dyck_words(n): + ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) + sage: [dyck_words(n).cardinality() for n in range(8)] + [1, 1, 2, 5, 14, 42, 132, 429] + sage: dyck_words(3).list() + [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] + + + .. _IntegerListsLex_finiteness: + + .. RUBRIC:: On finiteness and inverse lexicographic enumeration + + The set of all lists of integers cannot be enumerated in inverse + lexicographic order, since there is no largest list (take `[n]` + for `n` as large as desired):: + + sage: IntegerListsLex().first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Here is a variant which could be enumerated in lexicographic order + but not in inverse lexicographic order:: + + sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) + sage: for l in L: print l + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Even when the sum is specified, it is not necessarily possible to + enumerate *all* elements in inverse lexicographic order. In the + following example, the list ``[1, 1, 1]`` will never appear in the + enumeration:: + + sage: IntegerListsLex(3).first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + If one wants to proceed anyway, one can sign a waiver by setting + ``check=False`` (again, be warned that some valid lists may never appear):: + + sage: L = IntegerListsLex(3, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + In fact, being inverse lexicographically enumerable is almost + equivalent to being finite. The only infinity that can occur would + be from a tail of numbers `0,1` as in the previous example, where + the `1` moves further and further to the right. If there is any + list that is inverse lexicographically smaller than such a + configuration, the iterator would not reach it and hence would not + be considered iterable. Given that the infinite cases are very + specific, at this point only the finite cases are supported + (without signing the waiver). + + The finiteness detection is not complete yet, so some finite cases + may not be supported either, at least not without disabling the + checks. Practical examples of such are welcome. + + .. RUBRIC:: On trailing zeroes, and their caveats + + As mentioned above, when several lists satisfying the constraints + differ only by trailing zeroes, only the shortest one is listed:: + + sage: L = IntegerListsLex(max_length=4, max_part=1) + sage: L.list() + [[1, 1, 1, 1], + [1, 1, 1], + [1, 1, 0, 1], + [1, 1], + [1, 0, 1, 1], + [1, 0, 1], + [1, 0, 0, 1], + [1], + [0, 1, 1, 1], + [0, 1, 1], + [0, 1, 0, 1], + [0, 1], + [0, 0, 1, 1], + [0, 0, 1], + [0, 0, 0, 1], + []] + + and counted:: + + sage: L.cardinality() + 16 + + Still, the others are considered as elements of `L`:: + + sage: L = IntegerListsLex(4,min_length=3,max_length=4) + sage: L.list() + [..., [2, 2, 0], ...] + + sage: [2, 2, 0] in L # in L.list() + True + sage: [2, 2, 0, 0] in L # not in L.list() ! + True + sage: [2, 2, 0, 0, 0] in L + False + + .. RUBRIC:: Specifying functions as input for the floor or ceiling + + We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: + + sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) + [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] + + .. WARNING:: + + When passing a function as ``floor`` or ``ceiling``, it may + become undecidable to detect improper inverse lexicographic + enumeration. For example, the following example has a finite + enumeration:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) + sage: L.list() + [[3], + [2, 1], + [2, 0, 1], + [1, 2], + [1, 1, 1], + [1, 0, 2], + [1, 0, 1, 1], + [0, 3], + [0, 2, 1], + [0, 1, 2], + [0, 1, 1, 1], + [0, 0, 3], + [0, 0, 2, 1], + [0, 0, 1, 2], + [0, 0, 1, 1, 1]] + + but one cannot decide whether the following has an improper + inverse lexicographic enumeration without computing the floor + all the way to ``Infinity``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + Hence a warning is raised when a function is specified as + input, unless the waiver is signed by setting ``check=False``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) + doctest:... + A function has been given as input of the floor=[...] or ceiling=[...] + arguments of IntegerListsLex. Please see the documentation for the caveats. + If you know what you are doing, you can set check=False to skip this warning. + + Similarly, the algorithm may need to search forever for a + solution when the ceiling is ultimately zero:: + + sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) + sage: L.first() # not tested: will hang forever + sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) + sage: it = iter(L) + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] + + + .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility + + Sometimes, specifying a range for the sum or the length may be too + restrictive. One would want instead to specify a list, or + iterable `L`, of acceptable values. This is easy to achieve using + a :class:`disjoint union of enumerated sets `. + Here we want to accept the values `n=0,2,3`:: + + sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Finite family + {0: Integer lists of sum 0 satisfying certain constraints, + 2: Integer lists of sum 2 satisfying certain constraints, + 3: Integer lists of sum 3 satisfying certain constraints} + sage: C.list() + [[0, 0], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + The price to pay is that the enumeration order is now *graded + lexicographic* instead of lexicographic: first choose the value + according to the order specified by `L`, and use lexicographic + order within each value. Here is we reverse `L`:: + + sage: DisjointUnionEnumeratedSets(Family([3,2,0], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[3, 0], [2, 1], [1, 2], [0, 3], + [2, 0], [1, 1], [0, 2], + [0, 0]] + + Note that if a given value appears several times, the + corresponding elements will be enumerated several times, which + may, or not, be what one wants:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + + Here is a variant where we specify acceptable values for the + length:: + + sage: DisjointUnionEnumeratedSets(Family([0,1,3], + ....: lambda l: IntegerListsLex(2, length=l))).list() + [[2], + [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + + This technique can also be useful to obtain a proper enumeration + on infinite sets by using a graded lexicographic enumeration:: + + sage: C = DisjointUnionEnumeratedSets(Family(NN, + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(C) + sage: [next(it) for i in range(10)] + [[0, 0], + [1, 0], [0, 1], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + + .. RUBRIC:: Specifying how to construct elements + + This is the list of all monomials of degree `4` which divide the + monomial `x^3y^1z^2` (a monomial being identified with its + exponent vector):: + + sage: R. = QQ[] + sage: m = [3,1,2] + sage: def term(exponents): + ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] + sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) + [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] + + Note the use of the ``element_constructor`` option to specify how + to construct elements from a plain list. + + A variant is to specify a class for the elements. With the default + element constructor, this class should take as input the parent + ``self`` and a list. Here we want the elements to be constructed + in the class :class:`Partition`:: + + sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() + doctest:...: DeprecationWarning: the global_options argument is + deprecated since, in general, pickling is broken; + create your own class instead + See http://trac.sagemath.org/15525 for details. + [[3], [2, 1], [1, 1, 1]] + + Note that the :class:`Partition` further assumes the existence of + an attribute ``_global_options`` in the parent, hence the use of the + ``global_options`` parameter. + + .. WARNING:: + + The protocol for specifying the element class and constructor + is subject to changes. + + ALGORITHM: + + The iteration algorithm uses a depth first search through the + prefix tree of the list of integers (see also + :ref:`section-generic-integerlistlex`). While doing so, it does + some lookahead heuristics to attempt to cut dead branches. + + In most practical use cases, most dead branches are cut. Then, + roughly speaking, the time needed to iterate through all the + elements of `S` is proportional to the number of elements, where + the proportion factor is controlled by the length `l` of the + longest element of `S`. In addition, the memory usage is also + controlled by `l`, which is to say negligible in practice. + + Still, there remains much room for efficiency improvements; see + :trac:`18055`, :trac:`18056`. + + .. NOTE:: + + The generation algorithm could in principle be extended to + deal with non-constant slope constraints and with negative + parts. + + TESTS: + + This example from the combinatorics tutorial used to fail before + :trac:`17979` because the floor conditions did not satisfy the + slope conditions:: + + sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) + sage: I.list() + [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], + [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], + [6, 4, 3, 2, 1]] + + :: + + sage: Partitions(2, max_slope=-1, length=2).list() + [] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) + [[]] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) + [[]] + sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) + [[1]] + sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) + [] + sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) + [[0]] + sage: list(IntegerListsLex(3, max_length=2)) + [[3], [2, 1], [1, 2], [0, 3]] + sage: partitions = {"min_part": 1, "max_slope": 0} + sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} + sage: compositions = {"min_part": 1} + sage: integer_vectors = lambda l: {"length": l} + sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} + sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} + sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} + sage: list(IntegerListsLex(6, **partitions)) + [[6], + [5, 1], + [4, 2], + [4, 1, 1], + [3, 3], + [3, 2, 1], + [3, 1, 1, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(6, **constraints)) + [[6], + [3, 3], + [3, 2, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(1, **partitions_min_2)) + [] + sage: list(IntegerListsLex(2, **partitions_min_2)) + [[2]] + sage: list(IntegerListsLex(3, **partitions_min_2)) + [[3]] + sage: list(IntegerListsLex(4, **partitions_min_2)) + [[4], [2, 2]] + sage: list(IntegerListsLex(5, **partitions_min_2)) + [[5], [3, 2]] + sage: list(IntegerListsLex(6, **partitions_min_2)) + [[6], [4, 2], [3, 3], [2, 2, 2]] + sage: list(IntegerListsLex(7, **partitions_min_2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + sage: list(IntegerListsLex(9, **partitions_min_2)) + [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] + sage: list(IntegerListsLex(10, **partitions_min_2)) + [[10], + [8, 2], + [7, 3], + [6, 4], + [6, 2, 2], + [5, 5], + [5, 3, 2], + [4, 4, 2], + [4, 3, 3], + [4, 2, 2, 2], + [3, 3, 2, 2], + [2, 2, 2, 2, 2]] + sage: list(IntegerListsLex(4, **compositions)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + sage: list(IntegerListsLex(6, min_length=1, floor=[7])) + [] + sage: L = IntegerListsLex(10**100,length=1) + sage: L.list() + [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] + + Noted on :trac:`17898`:: + + sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) + [] + sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() + [] + sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() + [] + + Noted in :trac:`17548`, which are now fixed:: + + sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() + [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] + sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() + [] + sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() + [[4], [2, 2], [1, 1, 1, 1]] + sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() + [[6], [3, 2, 1]] + sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() + [] + sage: I = IntegerListsLex(3, max_length=2, min_part=1) + sage: I.list() + [[3], [2, 1], [1, 2]] + sage: [1,1,1] in I + False + sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) + sage: I.list() + [] + sage: [4,6] in I + False + sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) + sage: I.list() + [] + sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) + sage: I.list() + [[3, 4], [1, 2, 4]] + sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) + sage: I.list() + [[2, 2]] + sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) + sage: I.list() + [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], + [5, 3, 2], [4, 3, 2, 1]] + + + .. RUBRIC:: TESTS from comments on :trac:`17979` + + Comment 191:: + + sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) + [] + + Comment 240:: + + sage: L = IntegerListsLex(min_length=2, max_part=0) + sage: L.list() + [[0, 0]] + + .. RUBRIC:: Tests on the element constructor feature and mutability + + Internally, the iterator works on a single list that is mutated + along the way. Therefore, you need to make sure that the + ``element_constructor`` actually **copies** its input. This example + shows what can go wrong:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=lambda x: x) + sage: list(P) + [[], [], []] + + However, specifying ``list()`` as constructor solves this problem:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=list) + sage: list(P) + [[3], [2, 1], [1, 1, 1]] + + Same, step by step:: + + sage: it = iter(P) + sage: a = next(it); a + [3] + sage: b = next(it); b + [2, 1] + sage: a + [3] + sage: a is b + False + + Tests from `MuPAD-Combinat `_:: + + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() + 83 + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() + 53 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 30 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 43 + + sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [] + + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [0] + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs + [2] + sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1] + sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [2, 1, 2] + sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2] + sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 1] + sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 2] + sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + + This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: + + sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() + [] + """ + backend_class = IntegerListsBackend_invlex + + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, n=None, **kwargs): + r""" + Return a disjoint union if ``n`` is a list or iterable. + + TESTS: + + Specifying a list or iterable as argument is deprecated:: + + sage: IntegerListsLex([2,2], length=2).list() + doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead + See http://trac.sagemath.org/17979 for details. + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: IntegerListsLex(NN, max_length=3) + Disjoint union of Lazy family (<...>(i))_{i in Non negative integer semiring} + """ + import collections + if isinstance(n, collections.Iterable): + from sage.misc.superseded import deprecation + deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') + from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + from sage.sets.family import Family + return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) + else: + return typecall(cls, n=n, **kwargs) + + +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + """ + Cython back-end of an set of lists of integers with specified + constraints enumerated in inverse lexicographic order. + """ + def __init__(self, *args, check=True, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: C = IntegerListsLex(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C.cardinality().parent() is ZZ + True + sage: TestSuite(C).run() + sage: IntegerListsLex(min_part=-1) + Traceback (most recent call last): + ... + NotImplementedError: strictly negative min_part + """ + IntegerListsBackend.__init__(self, *args, **kwds) + + self.check = check + + if self.min_part < 0: + raise NotImplementedError("strictly negative min_part") + + if self.check and ( + self.floor.limit_start() == Infinity or + self.ceiling.limit_start() == Infinity): + from warnings import warn + warn(""" +A function has been given as input of the floor=[...] or ceiling=[...] +arguments of IntegerListsLex. Please see the documentation for the caveats. +If you know what you are doing, you can set check=False to skip this warning.""") + + @cached_method + def _check_finiteness(self): + """ + Check that the constraints define a finite set. + + As mentioned in the description of this class, being finite is + almost equivalent to being inverse lexicographic iterable, + which is what we really care about. + + This set is finite if and only if: + + #. For each `i` such that there exists a list of length at + least `i+1` satisfying the constraints, there exists a + direct or indirect upper bound on the `i`-th part, that + is ``self.ceiling(i)`` is finite. + + #. There exists a global upper bound on the length. + + Failures for 1. are detected and reported later, during the + iteration, namely the first time a prefix including the `i`-th + part is explored. + + This method therefore focuses on 2., namely trying to prove + the existence of an upper bound on the length. It may fail + to do so even when the set is actually finite. + + OUTPUT: + + ``None`` if this method finds a proof that there + exists an upper bound on the length. Otherwise a + ``ValueError`` is raised. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_length=4) + sage: L._check_finiteness() + + The following example is infinite:: + + sage: L = IntegerListsLex(4) + sage: L._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Indeed:: + + sage: it = iter(IntegerListsLex(4, check=False)) + sage: for _ in range(10): print next(it) + [4] + [3, 1] + [3, 0, 1] + [3, 0, 0, 1] + [3, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] + + Unless ``check=False``, :meth:`_check_finiteness` is called as + soon as an iteration is attempted:: + + sage: iter(L) + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Some other infinite examples:: + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The following example is actually finite, but not detected as such:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + This is sad because the following equivalent example works just fine:: + + sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() + [[4, 3]] + + Detecting this properly would require some deeper lookahead, + and the difficulty is to decide how far this lookahead should + search. Until this is fixed, one can disable the checks:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() + [[4, 3]] + + If the ceiling or floor is a function, it is much more likely + that a finite set will not be detected as such:: + + sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(7, ceiling=lambda i:0).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The next example shows a case that is finite because we remove + trailing zeroes:: + + sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) + [[]] + sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + In the next examples, there is either no solution, or the region + is bounded:: + + sage: IntegerListsLex(min_sum=10, max_sum=5).list() + [] + sage: IntegerListsLex(max_part=1, min_slope=10).list() + [[1], []] + sage: IntegerListsLex(max_part=100, min_slope=10).first() + [100] + sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() + [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] + sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() + [[1]] + + sage: IntegerListsLex(min_length=2, max_length=1).list() + [] + sage: IntegerListsLex(min_length=-2, max_length=-1).list() + [] + sage: IntegerListsLex(min_length=-1, max_length=-2).list() + [] + sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() + [] + sage: IntegerListsLex(min_part=2, max_part=1).list() + [[]] + + sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() + [[3], [2], [1], []] + sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() + [] + sage: IntegerListsLex(7, max_part=0).list() + [] + sage: IntegerListsLex(5, max_part=0, min_slope=0).list() + [] + sage: IntegerListsLex(max_part=0).list() + [[]] + sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() + [] + """ + # Trivial cases + if self.max_length < Infinity: + return + if self.max_sum < self.min_sum: + return + if self.min_slope > self.max_slope: + return + if self.max_slope < 0: + return + if self.ceiling.limit() < self.floor.limit(): + return + if self.ceiling.limit() == 0: + # This assumes no trailing zeroes + return + if self.min_slope > 0 and self.ceiling.limit() < Infinity: + return + + # Compute a lower bound on the sum of floor(i) for i=1 to infinity + if self.floor.limit() > 0 or self.min_slope > 0: + floor_sum_lower_bound = Infinity + elif self.floor.limit_start() < Infinity: + floor_sum_lower_bound = sum(self.floor(i) for i in range(self.floor.limit_start())) + else: + floor_sum_lower_bound = 0 + if floor_sum_lower_bound > 0 and self.min_slope >= 0: + floor_sum_lower_bound = Infinity + + if self.max_sum < floor_sum_lower_bound: + return + if self.max_sum == floor_sum_lower_bound and self.max_sum < Infinity: + # This assumes no trailing zeroes + return + + # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 + if ( self.max_slope == 0 and + (self.max_sum < Infinity or + (self.ceiling.limit_start() < Infinity and + any(self.ceiling(i) == 0 for i in range(self.ceiling.limit_start()+1))) + ) ): + return + + limit_start = max(self.ceiling.limit_start(), self.floor.limit_start()) + if limit_start < Infinity: + for i in range(limit_start+1): + if self.ceiling(i) < self.floor(i): + return + + raise ValueError("could not prove that the specified constraints yield a finite set") + + def _iter(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + if self.check: + self._check_finiteness() + return IntegerListsLexIter(self) + + +# Constants for IntegerListsLexIter._next_state +LOOKAHEAD = 5 +PUSH = 4 +ME = 3 +DECREASE = 2 +POP = 1 +STOP = 0 + +class IntegerListsLexIter(object): + r""" + Iterator class for IntegerListsLex. + + Let ``T`` be the prefix tree of all lists of nonnegative + integers that satisfy all constraints except possibly for + ``min_length`` and ``min_sum``; let the children of a list + be sorted decreasingly according to their last part. + + The iterator is based on a depth-first search exploration of a + subtree of this tree, trying to cut branches that do not + contain a valid list. Each call of ``next`` iterates through + the nodes of this tree until it finds a valid list to return. + + Here are the attributes describing the current state of the + iterator, and their invariants: + + - ``backend`` -- the :class:`IntegerListsBackend` object this is + iterating on; + + - ``_current_list`` -- the list corresponding to the current + node of the tree; + + - ``_j`` -- the index of the last element of ``_current_list``: + ``self._j == len(self._current_list) - 1``; + + - ``_current_sum`` -- the sum of the parts of ``_current_list``; + + - ``_search_ranges`` -- a list of same length as + ``_current_list``: the range for each part. + + Furthermore, we assume that there is no obvious contradiction + in the contraints: + + - ``self.backend.min_length <= self.backend.max_length``; + - ``self.backend.min_slope <= self.backend.max_slope`` + unless ``self.backend.min_length <= 1``. + + Along this iteration, ``next`` switches between the following + states: + + - LOOKAHEAD: determine whether the current list could be a + prefix of a valid list; + - PUSH: go deeper into the prefix tree by appending the + largest possible part to the current list; + - ME: check whether the current list is valid and if yes return it + - DECREASE: decrease the last part; + - POP: pop the last part of the current list; + - STOP: the iteration is finished. + + The attribute ``_next_state`` contains the next state ``next`` + should enter in. + """ + def __init__(self, backend): + """ + TESTS:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._j + -1 + sage: I._current_sum + 0 + """ + self.backend = backend + + self._search_ranges = [] + self._current_list = [] + self._j = -1 # index of last element of _current_list + self._current_sum = 0 # sum of parts in _current_list + + # Make sure that some invariants are respected in the iterator + if (backend.min_length <= backend.max_length and + (backend.min_slope <= backend.max_slope or backend.min_length <= 1)): + self._next_state = PUSH + else: + self._next_state = STOP + + def __iter__(self): + """ + Return ``self`` as per the iterator protocol. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: it = IntegerListsLexIter(C) + sage: it.__iter__() is it + True + """ + return self + + def _push_search(self): + """ + Push search forward, resetting attributes. + + The push may fail if it is discovered that + ``self._current_list`` cannot be extended in a valid way. + + OUTPUT: a boolean: whether the push succeeded + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._current_sum + 0 + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_list + [2] + sage: I._current_sum + 2 + sage: I._push_search() + True + sage: I._j + 1 + sage: I._search_ranges + [(0, 2), (0, 0)] + sage: I._current_list + [2, 0] + sage: I._current_sum + 2 + """ + max_sum = self.backend.max_sum + min_length = self.backend.min_length + max_length = self.backend.max_length + if self._j+1 >= max_length: + return False + if self._j+1 >= min_length and self._current_sum == max_sum: + # Cannot add trailing zeroes + return False + + if self._j >= 0: + prev = self._current_list[self._j] + else: + prev = None + interval = self._m_interval(self._j+1, self.backend.max_sum - self._current_sum, prev) + if interval[0] > interval[1]: + return False + + self._j += 1 + m = interval[1] + self._search_ranges.append(interval) + self._current_list.append(m) + self._current_sum += m + return True + + def _pop_search(self): + """ + Go back in search tree. Resetting attributes. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_sum + 2 + sage: I._current_list + [2] + sage: I._pop_search() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_sum + 0 + sage: I._current_list + [] + """ + if self._j >= 0: # TODO: get rid of this condition + self._j -= 1 + self._search_ranges.pop() + self._current_sum -= self._current_list[-1] + self._current_list.pop() + + def next(self): + r""" + Return the next element in the iteration. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: next(I) + [2, 0, 0] + sage: next(I) + [1, 1, 0] + """ + p = self.backend + min_sum = p.min_sum + max_length = p.max_length + search_ranges = self._search_ranges + + while True: + assert self._j == len(self._current_list) - 1 + assert self._j == len(self._search_ranges) - 1 + + # LOOK AHEAD + if self._next_state == LOOKAHEAD: + if self._lookahead(): + self._next_state = PUSH + else: + # We should reuse information about the + # reasons for this failure, to avoid when + # possible retrying with smaller values. + # We just do a special case for now: + if self._j + 1 == max_length and self._current_sum < min_sum: + self._next_state = POP + else: + self._next_state = DECREASE + + # PUSH + if self._next_state == PUSH: + if self._push_search(): + self._next_state = LOOKAHEAD + continue + self._next_state = ME + + # ME + if self._next_state == ME: + if self._j == -1: + self._next_state = STOP + else: + self._next_state = DECREASE + if self._internal_list_valid(): + return self._current_list + + # DECREASE + if self._next_state == DECREASE: + self._current_list[-1] -= 1 + self._current_sum -= 1 + if self._current_list[-1] >= search_ranges[self._j][0]: + self._next_state = LOOKAHEAD + continue + self._next_state = POP + + # POP + if self._next_state == POP: + self._pop_search() + self._next_state = ME + continue + + # STOP + if self._next_state == STOP: + raise StopIteration() + + assert False + + def _internal_list_valid(self): + """ + Return whether the current list in the iteration variable + ``self._current_list`` is a valid list. + + This method checks whether the sum of the parts in + ``self._current_list`` is in the right range, whether its + length is in the required range, and whether there are trailing + zeroes. It does not check all of the necessary conditions to + verify that an arbitrary list satisfies the constraints from + the corresponding ``IntegerListsLex`` object, and should + not be used except internally in the iterator class. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._current_list + [] + sage: I._internal_list_valid() + False + sage: next(I) + [2, 0, 0] + sage: I._current_list + [2, 0, 0] + sage: I._internal_list_valid() + True + """ + p = self.backend + mu = self._current_list + nu = self._current_sum + l = self._j + 1 + return (nu >= p.min_sum and nu <= p.max_sum # Good sum + and l >= p.min_length and l <= p.max_length # Good length + and (l <= max(p.min_length,0) or mu[-1] != 0)) # No trailing zeros + + def _m_interval(self, i, max_sum, prev=None): + r""" + Return coarse lower and upper bounds for the part ``m`` + at position ``i``. + + INPUT: + + - ``i`` -- a nonnegative integer (position) + + - ``max_sum`` -- a nonnegative integer or ``+oo`` + + - ``prev`` -- a nonnegative integer or ``None`` + + Return coarse lower and upper bounds for the value ``m`` + of the part at position ``i`` so that there could exists + some list suffix `v_i,\ldots,v_k` of sum bounded by + ``max_sum`` and satisfying the floor and upper bound + constraints. If ``prev`` is specified, then the slope + conditions between ``v[i-1]=prev`` and ``v[i]=m`` should + also be satisfied. + + Additionally, this raises an error if it can be detected + that some part is neither directly nor indirectly bounded + above, which implies that the constraints possibly do not + allow for an inverse lexicographic iterator. + + OUTPUT: + + A tuple of two integers ``(lower_bound, upper_bound)``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._m_interval(1,2) + (0, 2) + + The second part is not bounded above, hence we can not + iterate lexicographically through all the elements:: + + sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Same here:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + In the following examples however, all parts are + indirectly bounded above:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() + 24 + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() + 24 + + sage: IntegerListsLex(max_part=2, max_length=3).cardinality() + 27 + sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n + 10 + sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! + [] + sage: IntegerListsLex(length=0).list() # no part! + [[]] + """ + p = self.backend + + lower_bound = max(0, p.floor(i)) + upper_bound = min(max_sum, p.ceiling(i)) + if prev != None: + lower_bound = max(lower_bound, prev + p.min_slope) + upper_bound = min(upper_bound, prev + p.max_slope) + + ## check for infinite upper bound, in case max_sum is infinite + if p.check and upper_bound == Infinity: + # This assumes that there exists a valid list (which + # is not yet always guaranteed). Then we just + # discovered that part 'i' of this list can be made as + # large as desired, which implies that `self.backend` + # cannot be iterated in inverse lexicographic order + raise ValueError("infinite upper bound for values of m") + + return (lower_bound, upper_bound) + + def _lookahead(self): + r""" + Return whether the current list can possibly be a prefix + of a valid list. + + OUTPUT: + + ``False`` if it is guaranteed that the current list + cannot be a prefix of a valid list and ``True`` otherwise. + + EXAMPLES:: + + sage: it = IntegerListsLex(length=3, min_sum=2, max_sum=2)._iter() + sage: it._current_list = [0,1] # don't do this at home, kids + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + True + + sage: it = IntegerListsLex(length=3, min_sum=3, max_sum=2)._iter() + sage: it._current_list = [0,1] + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(min_length=2, max_part=0)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + True + sage: it._current_list = [0, 0] + sage: it._j = 1 + sage: it._lookahead() + True + sage: it._current_list = [0, 0, 0] + sage: it._j = 2 + sage: it._lookahead() + False + + sage: n = 10**100 + sage: it = IntegerListsLex(n, length=1)._iter() + sage: it._current_list = [n-1] + sage: it._current_sum = n-1 + sage: it._j = 0 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)._iter() + sage: it._current_list = [2] + sage: it._current_sum = 2 + sage: it._j = 0 + sage: it._lookahead() + False + + ALGORITHM: + + Let ``j = self._j`` be the position of the last part `m` of + ``self._current_list``. The current algorithm computes, + for `k = j, j+1, \ldots`, a lower bound `l_k` and an upper + bound `u_k` for `v_0+\dots+v_k`, and stops if none of the + invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. + + The lower bound `l_k` is given by the area below + `v_0,\dots,v_{j-1}` prolongated by the lower envelope + between `j` and `k` and starting at `m`. The upper bound + `u_k` is given similarly using the upper envelope. + + The complexity of this algorithm is bounded above by + ``O(max_length)``. When ``max_length=oo``, the algorithm + is guaranteed to terminate, unless ``floor`` is a function + which is eventually constant with value `0`, or which + reaches the value `0` while ``max_slope=0``. + + Indeed, the lower bound `l_k` is increasing with `k`; in + fact it is strictly increasing, unless the local lower bound + at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, + we can conclude; we can also conclude if we know that the + floor is eventually constant with value `0`, or there is a + local lower bound at `k` is `0` and ``max_slope=0``. + + .. RUBRIC:: Room for improvement + + Improved prediction: the lower bound `l_k` does not take + the slope conditions into account, except for those imposed + by the value `m` at `j`. Similarly for `u_k`. + + Improved speed: given that `l_k` is increasing with `k`, + possibly some dichotomy could be used to search for `k`, + with appropriate caching / fast calculation of the partial + sums. Also, some of the information gained at depth `j` + could be reused at depth `j+1`. + + TESTS:: + + sage: it = IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + False + """ + # Check code for various termination conditions. Possible cases: + # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True + # 1. lower sum surpasses max_sum -- terminate False + # 2. iteration surpasses max_length -- terminate False + # 3. upper envelope is smaller than lower envelope -- terminate False + # 4. max_slope <= 0 -- terminate False after upper passes 0 + # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point + # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs + + m = self._current_list[-1] + j = self._j + min_sum = self.backend.min_sum - (self._current_sum-m) + max_sum = self.backend.max_sum - (self._current_sum-m) + + if min_sum > max_sum: + return False + + p = self.backend + + # Beware that without slope conditions, the functions below + # currently forget about the value m at k! + lower_envelope = self.backend.floor.adapt(m,j) + upper_envelope = self.backend.ceiling.adapt(m,j) + lower = 0 # The lower bound `l_k` + upper = 0 # The upper bound `u_k` + + assert j >= 0 + # get to smallest valid number of parts + for k in range(j, p.min_length-1): + # We are looking at lists `v_j,...,v_k` + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + return False + lower += lo + upper += up + + if j < p.min_length and min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_{min_length-1}` + return True + + k = max(p.min_length-1,j) + # Check if any of the intervals intersect the target interval + while k < p.max_length: + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + # There exists no valid list of length >= k + return False + lower += lo + upper += up + assert lower <= upper + + if lower > max_sum: + # There cannot exist a valid list `v_j,\dots,v_l` with l>=k + return False + + if ( (p.max_slope <= 0 and up <= 0) + or (p.ceiling.limit() == 0 and k > p.ceiling.limit_start()) ): + # This implies v_l=0 for l>=k: that is we would be generating + # a list with trailing zeroes + return False + + if min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_k` + return True + + k += 1 + + return False + diff --git a/src/sage/combinat/integer_lists/lists.py b/src/sage/combinat/integer_lists/lists.py new file mode 100644 index 00000000000..42ff0dd6214 --- /dev/null +++ b/src/sage/combinat/integer_lists/lists.py @@ -0,0 +1,292 @@ +r""" +Enumerated set of lists of integers with constraints: front-end + +- :class:`IntegerLists`: class which models an enumerated set of lists + of integers with certain constraints. This is a Python front-end + where all user-accessible functionality should be implemented. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# 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 inspect import ismethod +from sage.categories.enumerated_sets import EnumeratedSets +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent +from sage.combinat.integer_lists.base import IntegerListsBackend + + +class IntegerList(ClonableArray): + """ + Element class for :class:`IntegerLists`. + """ + def check(self): + """ + Check to make sure this is a valid element in its + :class:`IntegerLists` parent. + + EXAMPLES:: + + sage: C = IntegerListsLex(4) + sage: C([4]).check() + True + sage: C([5]).check() + False + """ + return self.parent().__contains__(self) + + +class IntegerLists(Parent): + """ + Enumerated set of lists of integers with constraints. + + Currently, this is simply an abstract base class which should not + be used by itself. See :class:`IntegerListsLex` for a class which + can be used by end users. + + ``IntegerLists`` is just a Python front-end, acting as a + :class:`Parent`, supporting element classes and so on. + The attribute ``.backend`` which is an instance of + :class:`sage.combinat.integer_lists.base.IntegerListsBackend` is the + Cython back-end which implements all operations such as iteration. + + The front-end (i.e. this class) and the back-end are supposed to be + orthogonal: there is no imposed correspondence between front-ends + and back-ends. + + For example, the set of partitions of 5 and the set of weakly + decreasing sequences which sum to 5 might be implemented by the + same back-end, but they will be presented to the user by a + different front-end. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists(5) + sage: L + Integer lists of sum 5 satisfying certain constraints + + sage: IntegerListsLex(2, length=3, name="A given name") + A given name + """ + backend = None + backend_class = IntegerListsBackend + + Element = IntegerList + + def __init__(self, *args, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: C = IntegerLists(2, length=3) + sage: C == loads(dumps(C)) + True + """ + if "name" in kwds: + self.rename(kwds.pop("name")) + + if "global_options" in kwds: + from sage.misc.superseded import deprecation + deprecation(15525, 'the global_options argument is deprecated since, in general,' + ' pickling is broken; create your own class instead') + self.global_options = kwds.pop("global_options") + + if "element_class" in kwds: + self.Element = kwds.pop("element_class") + + if "element_constructor" in kwds: + element_constructor = kwds.pop("element_constructor") + elif issubclass(self.Element, ClonableArray): + # Not all element classes support check=False + element_constructor = self._element_constructor_nocheck + else: + element_constructor = None # Parent's default + + category = kwds.pop("category", None) + if category is None: + category = EnumeratedSets().Finite() + + # Let self.backend be some IntegerListsBackend + self.backend = self.backend_class(*args, **kwds) + + Parent.__init__(self, element_constructor=element_constructor, + category=category) + + def __eq__(self, other): + r""" + Return whether ``self == other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, min_length=3) + sage: F = IntegerListsLex(2, length=3, element_constructor=list) + sage: G = IntegerListsLex(4, length=3) + sage: C == C + True + sage: C == D + True + sage: C == E + False + sage: C == F + False + sage: C == None + False + sage: C == G + False + + This is a minimal implementation enabling pickling tests. It + is safe, but one would want the two following objects to be + detected as equal:: + + sage: C = IntegerListsLex(2, ceiling=[1,1,1]) + sage: D = IntegerListsLex(2, ceiling=[1,1,1]) + sage: C == D + False + + TESTS: + + This used to fail due to poor equality testing. See + :trac:`17979`, comment 433:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=1))).list() + [[2], [2]] + """ + if self.__class__ != other.__class__: + return False + if self.backend != other.backend: + return False + a = self._element_constructor + b = other._element_constructor + if ismethod(a): + a = a.im_func + if ismethod(b): + b = b.im_func + return a == b + + def __ne__(self, other): + r""" + Return whether ``self != other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, max_length=3) + sage: C != D + False + sage: C != E + True + """ + return not self == other + + def __iter__(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + return self._element_iter(self.backend._iter(), self._element_constructor) + + @staticmethod + def _element_iter(itr, constructor): + """ + Given an iterator ``itr`` and an element constructor + ``constructor``, iterate over ``constructor(v)`` where `v` + are the values of ``itr``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C._element_iter(C._iter(), tuple)) + [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)] + """ + for v in itr: + yield constructor(v) + + def __getattr__(self, name): + """ + Get an attribute of the implementation backend. + + Ideally, this would be done using multiple inheritance, but + Python doesn't support that for built-in types. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: C.min_length + 3 + + TESTS: + + Check that uninitialized instances do not lead to infinite + recursion because there is no ``backend`` attribute:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists.__new__(IntegerLists) + sage: L.foo + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'foo' + """ + return getattr(self.backend, name) + + def __contains__(self, item): + """ + Return ``True`` if ``item`` meets the constraints imposed by + the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) + True + """ + return self.backend._contains(item) + + def _element_constructor_nocheck(self, l): + r""" + A variant of the standard element constructor that passes + ``check=False`` to the element class. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_slope=0) + sage: L._element_constructor_nocheck([1,2,3]) + [1, 2, 3] + + When relevant, this is assigned to + ``self._element_constructor`` by :meth:`__init__`, to avoid + overhead when constructing elements from trusted data in the + iterator:: + + sage: L._element_constructor + + sage: L._element_constructor([1,2,3]) + [1, 2, 3] + """ + return self.element_class(self, l, check=False) + diff --git a/src/sage/combinat/integer_lists/nn.py b/src/sage/combinat/integer_lists/nn.py new file mode 100644 index 00000000000..24c7e9458fc --- /dev/null +++ b/src/sage/combinat/integer_lists/nn.py @@ -0,0 +1,43 @@ +from sage.sets.family import Family +from sage.combinat.integer_lists import IntegerListsLex +from sage.rings.semirings.non_negative_integer_semiring import NN +from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + +def IntegerListsNN(**kwds): + """ + Lists of nonnegative integers with constraints. + + This function returns the union of ``IntegerListsLex(n, **kwds)`` + where `n` ranges over all nonnegative integers. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.nn import IntegerListsNN + sage: L = IntegerListsNN(max_length=3, max_slope=-1) + sage: L + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(L) + sage: for _ in range(20): + ....: print next(it) + [] + [1] + [2] + [3] + [2, 1] + [4] + [3, 1] + [5] + [4, 1] + [3, 2] + [6] + [5, 1] + [4, 2] + [3, 2, 1] + [7] + [6, 1] + [5, 2] + [4, 3] + [4, 2, 1] + [8] + """ + return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) diff --git a/src/sage/combinat/integer_matrices.py b/src/sage/combinat/integer_matrices.py index 15bf04d4964..0983acbf1f4 100644 --- a/src/sage/combinat/integer_matrices.py +++ b/src/sage/combinat/integer_matrices.py @@ -18,7 +18,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index b11ce3136da..6ca77961adc 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -27,6 +27,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools import misc from __builtin__ import list as builtinlist from sage.categories.enumerated_sets import EnumeratedSets @@ -34,9 +35,8 @@ from sage.rings.integer import Integer from sage.rings.arith import binomial from sage.rings.infinity import PlusInfinity -import cartesian_product import functools -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex def is_gale_ryser(r,s): @@ -114,86 +114,6 @@ def is_gale_ryser(r,s): # same number of 1s domination return len(rstar) <= len(s2) and sum(r2) == sum(s2) and rstar.dominates(s) -def _slider01(A, t, k, p1, p2, fixedcols=[]): - r""" - Assumes `A` is a `(0,1)`-matrix. For each of the - `t` rows with highest row sums, this function - returns a matrix `B` which is the same as `A` except that it - has slid `t` of the `1` in each of these rows of `A` - over towards the `k`-th column. Care must be taken when the - last leading 1 is in column >=k. It avoids those in columns - listed in fixedcols. - - This is a 'private' function for use in gale_ryser_theorem. - - INPUT: - - - ``A`` -- an `m\times n` (0,1) matrix - - ``t``, ``k`` -- integers satisfying `0 < t < m`, `0 < k < n` - - ``fixedcols`` -- those columns (if any) whose entries - aren't permitted to slide - - OUTPUT: - - An `m\times n` (0,1) matrix, which is the same as `A` except - that it has exactly one `1` in `A` slid over to the `k`-th - column. - - EXAMPLES:: - - sage: from sage.combinat.integer_vector import _slider01 - sage: A = matrix([[1,1,1,0],[1,1,1,0],[1,0,0,0],[1,0,0,0]]) - sage: A - [1 1 1 0] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 1, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 3, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 0 1 1] - [0 0 0 1] - [1 0 0 0] - - """ - # we assume that the rows of A are arranged so that - # there row sums are decreasing as you go from the - # top row to the bottom row - import copy - from sage.matrix.constructor import matrix - m = len(A.rows()) - rs = [sum(x) for x in A.rows()] - n = len(A.columns()) - cs = [sum(x) for x in A.columns()] - B = [copy.deepcopy(list(A.row(j))) for j in range(m)] - c = 0 # initializing counter - for ii in range(m): - rw = copy.deepcopy(B[ii]) # to make mutable - # now we want to move the rightmost left 1 to the k-th column - fixedcols = [l for l in range(n) if p2[l]==sum(matrix(B).column(l))] - JJ = range(n) - JJ.reverse() - for jj in JJ: - if t==sum(matrix(B).column(k)): - break - if jj=t: next - j=n-1 - else: - next - if c>=t: - break - return matrix(B) - def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. @@ -234,8 +154,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): Ryser's Algorithm: - (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, - due to Ryser, is described as follows. The + (Ryser [Ryser63]_): The construction of an `m\times n` matrix + `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining @@ -255,6 +175,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. + Note: when performing the induction on step `k`, we consider + the row sums of the first `k` columns. * Set `A = B`. Return `A`. @@ -282,10 +204,10 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") - [1 1 0 1] [1 1 1 0] - [0 1 0 0] + [1 1 0 1] [1 0 0 0] + [0 1 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") @@ -311,19 +233,19 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") - [1 0 1 1 0] [1 1 1 0 0] + [1 0 1 1 0] [0 0 0 0 0] - [0 0 1 0 0] [1 0 0 0 0] + [0 0 1 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] - [0 0 1 0] - [0 1 0 0] [1 0 0 0] [1 0 0 0] + [0 1 0 0] + [0 0 1 0] TESTS: @@ -334,20 +256,20 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): checked for correctness.:: sage: def test_algorithm(algorithm, low = 10, high = 50): - ... n,m = randint(low,high), randint(low,high) - ... g = graphs.RandomBipartite(n, m, .3) - ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) - ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) - ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) - ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) - ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) - ... if ((ss1 != s1) or (ss2 != s2)): - ... print "Algorithm %s failed with this input:" % algorithm - ... print s1, s2 - - sage: for algorithm in ["gale", "ryser"]: # long time - ... for i in range(50): # long time - ... test_algorithm(algorithm, 3, 10) # long time + ....: n,m = randint(low,high), randint(low,high) + ....: g = graphs.RandomBipartite(n, m, .3) + ....: s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) + ....: s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) + ....: m = gale_ryser_theorem(s1, s2, algorithm = algorithm) + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print "Algorithm %s failed with this input:" % algorithm + ....: print s1, s2 + + sage: for algorithm in ["gale", "ryser"]: # long time + ....: for i in range(50): # long time + ....: test_algorithm(algorithm, 3, 10) # long time Null matrix:: @@ -359,14 +281,28 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): [0 0 0 0] [0 0 0 0] [0 0 0 0] - + + Check that :trac:`16638` is fixed:: + + sage: tests = [([4, 3, 3, 2, 1, 1, 1, 1, 0], [6, 5, 1, 1, 1, 1, 1]), + ....: ([4, 4, 3, 3, 1, 1, 0], [5, 5, 2, 2, 1, 1]), + ....: ([4, 4, 3, 2, 1, 1], [5, 5, 1, 1, 1, 1, 1, 0, 0]), + ....: ([3, 3, 3, 3, 2, 1, 1, 1, 0], [7, 6, 2, 1, 1, 0]), + ....: ([3, 3, 3, 1, 1, 0], [4, 4, 1, 1, 1])] + sage: for s1, s2 in tests: + ....: m = gale_ryser_theorem(s1, s2, algorithm="ryser") + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print("Error in Ryser algorithm") + ....: print(s1, s2) REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, - Carus Monographs, MAA, 1963. + Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. - 7(1957)1073-1082. + 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix @@ -380,28 +316,39 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) - r = [x[1] for x in tmp if x[1]>0] + r = [x[1] for x in tmp] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) - s = [x[1] for x in tmp if x[1]>0] + s = [x[1] for x in tmp] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] n = len(s) - A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)]) - - for k in range(1,n+1): - goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))] - if sum(A0.column(n-k)) != s[n-k]: - A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols) - - # If we need to add empty rows/columns - if len(p1)!=m: - A0 = A0.stack(matrix([[0]*n]*(len(p1)-m))) - - if len(p2)!=n: - A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose() + # This is the partition equivalent to the sliding algorithm + cols = [] + for t in reversed(s): + c = [0] * m + i = 0 + while t: + k = i + 1 + while k < m and r[i] == r[k]: + k += 1 + if t >= k - i: # == number rows of the same length + for j in range(i, k): + r[j] -= 1 + c[j] = 1 + t -= k - i + else: # Remove the t last rows of that length + for j in range(k-t, k): + r[j] -= 1 + c[j] = 1 + t = 0 + i = k + cols.append(c) + + # We added columns to the back instead of the front + A0 = matrix(list(reversed(cols))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input @@ -923,34 +870,30 @@ def __init__(self, n, k, constraints, category=None): sage: type(v) - TESTS: - - All the attributes below are private; don't use them! - - :: + TESTS:: - sage: IV._min_length + sage: IV.min_length 3 - sage: IV._max_length + sage: IV.max_length 3 - sage: floor = IV._floor + sage: floor = IV.floor sage: [floor(i) for i in range(1,10)] [0, 0, 0, 0, 0, 0, 0, 0, 0] - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: [ceiling(i) for i in range(1,5)] [inf, inf, inf, inf] - sage: IV._min_slope + sage: IV.min_slope 0 - sage: IV._max_slope + sage: IV.max_slope inf sage: IV = IntegerVectors(3, 10, inner=[4,1,3], min_part=2) - sage: floor = IV._floor + sage: floor = IV.floor sage: floor(0), floor(1), floor(2) (4, 2, 3) sage: IV = IntegerVectors(3, 10, outer=[4,1,3], max_part=3) - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: ceiling(0), ceiling(1), ceiling(2) (3, 1, 3) """ @@ -1072,7 +1015,7 @@ def next(self, x): [0, 0, 2] """ from sage.combinat.integer_list_old import next - return next(x, self._min_length, self._max_length, self._floor, self._ceiling, self._min_slope, self._max_slope) + return next(x, self.min_length, self.max_length, self.floor, self.ceiling, self.min_slope, self.max_slope) class IntegerVectors_nconstraints(IntegerVectors_nkconstraints): def __init__(self, n, constraints): @@ -1221,7 +1164,7 @@ def __iter__(self): """ for iv in IntegerVectors(self.n, len(self.comp)): blocks = [ IntegerVectors(iv[i], self.comp[i], max_slope=0).list() for i in range(len(self.comp))] - for parts in cartesian_product.CartesianProduct(*blocks): + for parts in itertools.product(*blocks): res = [] for part in parts: res += part diff --git a/src/sage/combinat/k_tableau.py b/src/sage/combinat/k_tableau.py index 12d7a0da215..80850a88a3d 100644 --- a/src/sage/combinat/k_tableau.py +++ b/src/sage/combinat/k_tableau.py @@ -4211,9 +4211,9 @@ def marked_given_unmarked_and_weight_iterator( cls, unmarkedT, k, weight ): if td == {}: # the tableau is empty yield StrongTableau( unmarkedT, k, [] ) else: - allmarkings = cartesian_product.CartesianProduct(*[td[v] for v in td.keys()]) + import itertools dsc = Composition(weight).descents() - for m in allmarkings: + for m in itertools.product(*td.values()): if all(((m[i][1]-m[i][0]"(dancing_links *mem) cdef class dancing_linksWrapper: + r""" + A simple class that implements dancing links. + + The main methods to list the solutions are :meth:`search` and + :meth:`get_solution`. You can also use :meth:`number_of_solutions` to count + them. + + This class simply wraps a C++ implementation of Carlo Hamalainen. + """ cdef dancing_links _x - cdef _rows + cdef list _rows def __init__(self, rows): """ @@ -127,16 +138,7 @@ cdef class dancing_linksWrapper: sage: X == Y 0 """ - - cdef int equal - equal = left._rows == right._rows - - if op == 2: # == - return equal - elif op == 3: # != - return not equal - else: - return NotImplemented + return rich_to_bool(op, cmp(left._rows, right._rows)) def _init_rows(self, rows): """ @@ -185,9 +187,10 @@ cdef class dancing_linksWrapper: def get_solution(self): """ - After calling search(), we can extract a solution - from the instance variable self._x.solution, a C++ vector - listing the rows that make up the current solution. + Return the current solution. + + After a new solution is found using the method :meth:`search` this + method return the rows that make up the current solution. TESTS:: @@ -203,8 +206,7 @@ cdef class dancing_linksWrapper: [3, 0] """ cdef size_t i - - s = [] + cdef list s = [] for i in range(self._x.solution.size()): s.append(self._x.solution.at(i)) @@ -212,6 +214,11 @@ cdef class dancing_linksWrapper: def search(self): """ + Search for a new solution. + + Return ``1`` if a new solution is found and ``0`` otherwise. To recover + the solution, use the method :meth:`get_solution`. + EXAMPLES:: sage: from sage.combinat.matrices.dancing_links import dlx_solver @@ -249,6 +256,168 @@ cdef class dancing_linksWrapper: sig_off() return x + def split(self, column): + r""" + Return a dict of rows solving independent cases. + + For each ``i``-th row containing a ``1`` in the ``column``, the + dict associates the list of rows where each other row containing a + ``1`` in the same ``column`` is replaced by the empty list. + + This is used for parallel computations. + + INPUT: + + - ``column`` -- integer, the column used to split the problem + + OUTPUT: + + dict where keys are row numbers and values are lists of rows + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [3,4,5], [0,1], [2,3,4,5], [0], [1,2,3,4,5]] + sage: d = dlx_solver(rows) + sage: d + Dancing links solver for 6 columns and 6 rows + sage: sorted(d.solutions_iterator()) + [[0, 1], [2, 3], [4, 5]] + + After the split each subproblem has the same number of columns and + rows as the orginal one:: + + sage: D = d.split(0) + sage: D + {0: [[0, 1, 2], [3, 4, 5], [], [2, 3, 4, 5], [], [1, 2, 3, 4, 5]], + 2: [[], [3, 4, 5], [0, 1], [2, 3, 4, 5], [], [1, 2, 3, 4, 5]], + 4: [[], [3, 4, 5], [], [2, 3, 4, 5], [0], [1, 2, 3, 4, 5]]} + + The (disjoint) union of the solutions of the subproblems is equal to the + set of solutions shown above:: + + sage: for a in D.values(): list(dlx_solver(a).solutions_iterator()) + [[0, 1]] + [[2, 3]] + [[4, 5]] + """ + from copy import deepcopy + indices = [i for (i,row) in enumerate(self._rows) if column in row] + D = dict() + for i in indices: + rows = deepcopy(self._rows) + for j in indices: + if j != i: + rows[j] = [] + D[i] = rows + return D + + def solutions_iterator(self): + r""" + Return an iterator of the solutions. + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [3,4,5], [0,1], [2,3,4,5], [0], [1,2,3,4,5]] + sage: d = dlx_solver(rows) + sage: list(d.solutions_iterator()) + [[0, 1], [2, 3], [4, 5]] + """ + while self.search(): + yield self.get_solution() + + def _number_of_solutions_iterator(self, ncpus=1, column=0): + r""" + Return an iterator over the number of solutions using each row + containing a ``1`` in the given ``column``. + + INPUT: + + - ``ncpus`` -- integer (default: ``1``), maximal number of + subprocesses to use at the same time + - ``column`` -- integer (default: ``0``), the column used to split + the problem + + OUPUT: + + iterator of tuples (row number, number of solutions) + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [3,4,5], [0,1], [2,3,4,5], [0], [1,2,3,4,5]] + sage: d = dlx_solver(rows) + sage: sorted(d._number_of_solutions_iterator(ncpus=2, column=3)) + [(1, 1), (3, 1), (5, 1)] + + :: + + sage: S = Subsets(range(5)) + sage: rows = map(list, S) + sage: d = dlx_solver(rows) + sage: d.number_of_solutions() + 52 + sage: sum(b for a,b in d._number_of_solutions_iterator(ncpus=2, column=3)) + 52 + """ + D = self.split(column) + from sage.parallel.decorate import parallel + @parallel(ncpus=ncpus) + def nb_sol(i): + return dlx_solver(D[i]).number_of_solutions() + K = sorted(D.keys()) + for ((args, kwds), val) in nb_sol(K): + yield args[0], val + + def number_of_solutions(self, ncpus=1, column=0): + r""" + Return the number of distinct solutions. + + INPUT: + + - ``ncpus`` -- integer (default: ``1``), maximal number of + subprocesses to use at the same time. If `ncpus>1` the dancing + links problem is split into independent subproblems to + allow parallel computation. + - ``column`` -- integer (default: ``0``), the column used to split + the problem (ignored if ``ncpus`` is ``1``) + + OUPUT: + + integer + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2]] + sage: rows += [[0,2]] + sage: rows += [[1]] + sage: rows += [[3]] + sage: x = dlx_solver(rows) + sage: x.number_of_solutions() + 2 + + :: + + sage: rows = [[0,1,2], [3,4,5], [0,1], [2,3,4,5], [0], [1,2,3,4,5]] + sage: x = dlx_solver(rows) + sage: x.number_of_solutions(ncpus=2, column=3) + 3 + + TESTS:: + + sage: dlx_solver([]).number_of_solutions() + 0 + """ + cdef int N = 0 + if ncpus == 1: + while self.search(): + N += 1 + return N + else: + it = self._number_of_solutions_iterator(ncpus, column) + return sum(val for (k,val) in it) def dlx_solver(rows): """ diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 116c8a527bb..3d827f93233 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -48,62 +48,13 @@ from sage.rings.arith import kronecker_symbol from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer -from sage.matrix.constructor import matrix, block_matrix +from sage.matrix.constructor import matrix, block_matrix, block_diagonal_matrix, diagonal_matrix from urllib import urlopen -from sage.misc.functional import is_even -from sage.rings.arith import is_prime - - -def H1(i, j, p): - """ - Returns the i,j-th entry of the Paley matrix, type I case. - The Paley type I case corresponds to the case `p \cong 3 \mod{4}` - for a prime `p`. - - .. TODO:: - - This construction holds more generally for prime powers `q` - congruent to `3 \mod{4}`. We should implement these but we - first need to implement Quadratic character for `GF(q)`. - - EXAMPLES:: - - sage: sage.combinat.matrices.hadamard_matrix.H1(1,2,3) - 1 - """ - if i == 0 or j == 0: - return 1 - # what follows will not be executed for (i, j) = (0, 0). - if i == j: - return -1 - return -kronecker_symbol(i - j, p) - - -def H2(i, j, p): - """ - Returns the i,j-th entry of the Paley matrix, type II case. - - The Paley type II case corresponds to the case `p \cong 1 \mod{4}` - for a prime `p` (see [Hora]_). - - .. TODO:: - - This construction holds more generally for prime powers `q` - congruent to `1 \mod{4}`. We should implement these but we - first need to implement Quadratic character for `GF(q)`. - - EXAMPLES:: - - sage: sage.combinat.matrices.hadamard_matrix.H2(1,2,5) - 1 - """ - if i == 0 and j == 0: - return 0 - if i == 0 or j == 0: - return 1 - if i == j: - return 0 - return kronecker_symbol(i - j, p) +from sage.rings.arith import is_prime, is_square, is_prime_power, divisors +from math import sqrt +from sage.matrix.constructor import identity_matrix as I +from sage.matrix.constructor import ones_matrix as J +from sage.misc.unknown import Unknown def normalise_hadamard(H): """ @@ -118,14 +69,12 @@ def normalise_hadamard(H): sage: H == hadamard_matrix(4) True """ - Hc1 = H.column(0) - Hr1 = H.row(0) for i in range(H.ncols()): - if Hc1[i] < 0: - H.rescale_row(i, -1) - for i in range(H.nrows()): - if Hr1[i] < 0: + if H[0,i] < 0: H.rescale_col(i, -1) + for i in range(H.nrows()): + if H[i,0] < 0: + H.rescale_row(i, -1) return H def hadamard_matrix_paleyI(n): @@ -139,18 +88,38 @@ def hadamard_matrix_paleyI(n): We note that this method returns a normalised Hadamard matrix :: - sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyI(4) + sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyI(4) # random [ 1 1 1 1] + [ 1 -1 1 -1] [ 1 -1 -1 1] [ 1 1 -1 -1] - [ 1 -1 1 -1] + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_paleyI + sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix + sage: test_cases = [x+1 for x in range(100) if is_prime_power(x) and x%4==3] + sage: all(is_hadamard_matrix(hadamard_matrix_paleyI(n),normalized=True,verbose=True) + ....: for n in test_cases) + True """ p = n - 1 - if not(is_prime(p) and (p % 4 == 3)): + if not(is_prime_power(p) and (p % 4 == 3)): raise ValueError("The order %s is not covered by the Paley type I construction." % n) - H = matrix(ZZ, [[H1(i, j, p) for i in range(n)] for j in range(n)]) - # normalising H so that first row and column have only +1 entries. - return normalise_hadamard(H) + + from sage.rings.finite_rings.constructor import FiniteField + K = FiniteField(p,'x') + K_list = list(K) + K_list.insert(0,K.zero()) + H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) + for x in K_list] + for y in K_list]) + for i in range(n): + H[0,i] = 1 + H[i,i] = -1 + H[i,0] = -1 + H = normalise_hadamard(H) + return H def hadamard_matrix_paleyII(n): """ @@ -168,35 +137,151 @@ def hadamard_matrix_paleyII(n): We note that the method returns a normalised Hadamard matrix :: - sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyII(12) - [ 1 1 1 1 1 1| 1 1 1 1 1 1] - [ 1 1 1 -1 -1 1|-1 -1 1 -1 -1 1] - [ 1 1 1 1 -1 -1|-1 1 -1 1 -1 -1] - [ 1 -1 1 1 1 -1|-1 -1 1 -1 1 -1] - [ 1 -1 -1 1 1 1|-1 -1 -1 1 -1 1] - [ 1 1 -1 -1 1 1|-1 1 -1 -1 1 -1] - [-----------------+-----------------] - [ 1 -1 -1 -1 -1 -1|-1 1 1 1 1 1] - [ 1 -1 1 -1 -1 1| 1 -1 -1 1 1 -1] - [ 1 1 -1 1 -1 -1| 1 -1 -1 -1 1 1] - [ 1 -1 1 -1 1 -1| 1 1 -1 -1 -1 1] - [ 1 -1 -1 1 -1 1| 1 1 1 -1 -1 -1] - [ 1 1 -1 -1 1 -1| 1 -1 1 1 -1 -1] + sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyII(12) # random + [ 1 1| 1 1| 1 1| 1 1| 1 1| 1 1] + [ 1 -1|-1 1|-1 1|-1 1|-1 1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 -1| 1 1|-1 -1|-1 -1| 1 1] + [ 1 1|-1 -1| 1 -1|-1 1|-1 1| 1 -1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 1| 1 -1| 1 1|-1 -1|-1 -1] + [ 1 1| 1 -1|-1 -1| 1 -1|-1 1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1|-1 -1| 1 1| 1 -1| 1 1|-1 -1] + [ 1 1|-1 1| 1 -1|-1 -1| 1 -1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1|-1 -1|-1 -1| 1 1| 1 -1| 1 1] + [ 1 1|-1 1|-1 1| 1 -1|-1 -1| 1 -1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 1|-1 -1|-1 -1| 1 1| 1 -1] + [ 1 1| 1 -1|-1 1|-1 1| 1 -1|-1 -1] + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_paleyII + sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix + sage: test_cases = [2*(x+1) for x in range(50) if is_prime_power(x) and x%4==1] + sage: all(is_hadamard_matrix(hadamard_matrix_paleyII(n),normalized=True,verbose=True) + ....: for n in test_cases) + True """ - N = Integer(n/2) - p = N - 1 - if not(is_prime(p) and (p % 4 == 1)): + q = n//2 - 1 + if not(n%2==0 and is_prime_power(q) and (q % 4 == 1)): raise ValueError("The order %s is not covered by the Paley type II construction." % n) - S = matrix(ZZ, [[H2(i, j, p) for i in range(N)] for j in range(N)]) - H = block_matrix([[S + 1, S - 1], [1 - S, S + 1]]) - # normalising H so that first row and column have only +1 entries. + + from sage.rings.finite_rings.constructor import FiniteField + K = FiniteField(q,'x') + K_list = list(K) + K_list.insert(0,K.zero()) + H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) + for x in K_list] + for y in K_list]) + for i in range(q+1): + H[0,i] = 1 + H[i,0] = 1 + H[i,i] = 0 + + tr = { 0: matrix(2,2,[ 1,-1,-1,-1]), + 1: matrix(2,2,[ 1, 1, 1,-1]), + -1: matrix(2,2,[-1,-1,-1, 1])} + + H = block_matrix(q+1,q+1,[tr[v] for r in H for v in r]) + return normalise_hadamard(H) -def hadamard_matrix(n): +def is_hadamard_matrix(M, normalized=False, verbose=False): + r""" + Test if `M` is a hadamard matrix. + + INPUT: + + - ``M`` -- a matrix + + - ``normalized`` (boolean) -- whether to test if ``M`` is a normalized + Hadamard matrix, i.e. has its first row/column filled with +1. + + - ``verbose`` (boolean) -- whether to be verbose when the matrix is not + Hadamard. + + EXAMPLE:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix + sage: is_hadamard_matrix(matrix.hadamard(12)) + True + sage: h = matrix.hadamard(12) + sage: h[0,0] = 2 + sage: is_hadamard_matrix(h,verbose=True) + The matrix does not only contain +1 and -1 entries, e.g. 2 + False + sage: h = matrix.hadamard(12) + sage: for i in range(12): + ....: h[i,2] = -h[i,2] + sage: is_hadamard_matrix(h,verbose=True,normalized=True) + The matrix is not normalized + False + + """ + n = M.ncols() + if n != M.nrows(): + if verbose: + print "The matrix is not square ({}x{})".format(M.nrows(),n) + return False + + if n == 0: + return True + + for r in M: + for v in r: + if v*v != 1: + if verbose: + print "The matrix does not only contain +1 and -1 entries, e.g. "+str(v) + return False + + prod = (M*M.transpose()).dict() + if (len(prod) != n or + set(prod.itervalues()) != {n} or + any( (i,i) not in prod for i in range(n) )): + if verbose: + print "The product M*M.transpose() is not equal to nI" + return False + + if normalized: + if (set(M.row(0) ) != {1} or + set(M.column(0)) != {1}): + if verbose: + print "The matrix is not normalized" + return False + + return True + +from sage.matrix.constructor import matrix_method +@matrix_method +def hadamard_matrix(n,existence=False, check=True): + r""" Tries to construct a Hadamard matrix using a combination of Paley and Sylvester constructions. + INPUT: + + - ``n`` (integer) -- dimension of the matrix + + - ``existence`` (boolean) -- whether to build the matrix or merely query if + a construction is available in Sage. When set to ``True``, the function + returns: + + - ``True`` -- meaning that Sage knows how to build the matrix + + - ``Unknown`` -- meaning that Sage does not know how to build the + matrix, but that the design may exist (see :mod:`sage.misc.unknown`). + + - ``False`` -- meaning that the matrix does not exist. + + - ``check`` (boolean) -- whether to check that output is correct before + returning it. As this is expected to be useless (but we are cautious + guys), you may want to disable it whenever you want speed. Set to ``True`` + by default. + EXAMPLES:: sage: hadamard_matrix(12).det() @@ -208,7 +293,7 @@ def hadamard_matrix(n): sage: hadamard_matrix(2) [ 1 1] [ 1 -1] - sage: hadamard_matrix(8) + sage: hadamard_matrix(8) # random [ 1 1 1 1 1 1 1 1] [ 1 -1 1 -1 1 -1 1 -1] [ 1 1 -1 -1 1 1 -1 -1] @@ -223,44 +308,77 @@ def hadamard_matrix(n): We note that the method `hadamard_matrix()` returns a normalised Hadamard matrix (the entries in the first row and column are all +1) :: - sage: hadamard_matrix(12) - [ 1 1 1 1 1 1| 1 1 1 1 1 1] - [ 1 1 1 -1 -1 1|-1 -1 1 -1 -1 1] - [ 1 1 1 1 -1 -1|-1 1 -1 1 -1 -1] - [ 1 -1 1 1 1 -1|-1 -1 1 -1 1 -1] - [ 1 -1 -1 1 1 1|-1 -1 -1 1 -1 1] - [ 1 1 -1 -1 1 1|-1 1 -1 -1 1 -1] - [-----------------+-----------------] - [ 1 -1 -1 -1 -1 -1|-1 1 1 1 1 1] - [ 1 -1 1 -1 -1 1| 1 -1 -1 1 1 -1] - [ 1 1 -1 1 -1 -1| 1 -1 -1 -1 1 1] - [ 1 -1 1 -1 1 -1| 1 1 -1 -1 -1 1] - [ 1 -1 -1 1 -1 1| 1 1 1 -1 -1 -1] - [ 1 1 -1 -1 1 -1| 1 -1 1 1 -1 -1] + sage: hadamard_matrix(12) # random + [ 1 1| 1 1| 1 1| 1 1| 1 1| 1 1] + [ 1 -1|-1 1|-1 1|-1 1|-1 1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 -1| 1 1|-1 -1|-1 -1| 1 1] + [ 1 1|-1 -1| 1 -1|-1 1|-1 1| 1 -1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 1| 1 -1| 1 1|-1 -1|-1 -1] + [ 1 1| 1 -1|-1 -1| 1 -1|-1 1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1|-1 -1| 1 1| 1 -1| 1 1|-1 -1] + [ 1 1|-1 1| 1 -1|-1 -1| 1 -1|-1 1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1|-1 -1|-1 -1| 1 1| 1 -1| 1 1] + [ 1 1|-1 1|-1 1| 1 -1|-1 -1| 1 -1] + [-----+-----+-----+-----+-----+-----] + [ 1 -1| 1 1|-1 -1|-1 -1| 1 1| 1 -1] + [ 1 1| 1 -1|-1 1|-1 1| 1 -1|-1 -1] + + TESTS:: + + sage: matrix.hadamard(10,existence=True) + False + sage: matrix.hadamard(12,existence=True) + True + sage: matrix.hadamard(92,existence=True) + Unknown + sage: matrix.hadamard(10) + Traceback (most recent call last): + ... + ValueError: The Hadamard matrix of order 10 does not exist """ if not(n % 4 == 0) and (n > 2): + if existence: + return False raise ValueError("The Hadamard matrix of order %s does not exist" % n) if n == 2: - return matrix([[1, 1], [1, -1]]) - if is_even(n): - N = Integer(n / 2) + if existence: + return True + M = matrix([[1, 1], [1, -1]]) elif n == 1: - return matrix([1]) - if is_prime(N - 1) and (N - 1) % 4 == 1: - return hadamard_matrix_paleyII(n) + if existence: + return True + M = matrix([1]) + elif is_prime_power(n//2 - 1) and (n//2 - 1) % 4 == 1: + if existence: + return True + M = hadamard_matrix_paleyII(n) elif n == 4 or n % 8 == 0: - had = hadamard_matrix(Integer(n / 2)) + if existence: + return hadamard_matrix(n//2,existence=True) + had = hadamard_matrix(n//2,check=False) chad1 = matrix([list(r) + list(r) for r in had.rows()]) mhad = (-1) * had R = len(had.rows()) chad2 = matrix([list(had.rows()[i]) + list(mhad.rows()[i]) for i in range(R)]) - return chad1.stack(chad2) - elif is_prime(N - 1) and (N - 1) % 4 == 3: - return hadamard_matrix_paleyI(n) + M = chad1.stack(chad2) + elif is_prime_power(n - 1) and (n - 1) % 4 == 3: + if existence: + return True + M = hadamard_matrix_paleyI(n) else: + if existence: + return Unknown raise ValueError("The Hadamard matrix of order %s is not yet implemented." % n) + if check: + assert is_hadamard_matrix(M, normalized=True) + + return M def hadamard_matrix_www(url_file, comments=False): """ @@ -319,3 +437,275 @@ def hadamard_matrix_www(url_file, comments=False): if lastline[0] == "A": print lastline return matrix(rws) + +_rshcd_cache = {} + +def regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e,existence=False): + r""" + Return a Regular Symmetric Hadamard Matrix with Constant Diagonal. + + A Hadamard matrix is said to be *regular* if its rows all sum to the same + value. + + When `\epsilon\in\{-1,+1\}`, we say that `M` is a `(n,\epsilon)-RSHCD` if + `M` is a regular symmetric Hadamard matrix with constant diagonal + `\delta\in\{-1,+1\}` and row values all equal to `\delta \epsilon + \sqrt(n)`. For more information, see [HX10]_ or 10.5.1 in + [BH12]_. + + INPUT: + + - ``n`` (integer) -- side of the matrix + + - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal + sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,1) + [ 1 1 1 -1] + [ 1 1 -1 1] + [ 1 -1 1 1] + [-1 1 1 1] + sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,-1) + [ 1 -1 -1 -1] + [-1 1 -1 -1] + [-1 -1 1 -1] + [-1 -1 -1 1] + + Other hardcoded values:: + + sage: for n,e in [(36,1),(36,-1),(100,1),(100,-1),(196, 1)]: + ....: print regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e) + 36 x 36 dense matrix over Integer Ring + 36 x 36 dense matrix over Integer Ring + 100 x 100 dense matrix over Integer Ring + 100 x 100 dense matrix over Integer Ring + 196 x 196 dense matrix over Integer Ring + + From two close prime powers:: + + sage: print regular_symmetric_hadamard_matrix_with_constant_diagonal(64,-1) + 64 x 64 dense matrix over Integer Ring + + Recursive construction:: + + sage: print regular_symmetric_hadamard_matrix_with_constant_diagonal(144,-1) + 144 x 144 dense matrix over Integer Ring + + REFERENCE: + + .. [BH12] A. Brouwer and W. Haemers, + Spectra of graphs, + Springer, 2012, + http://homepages.cwi.nl/~aeb/math/ipm/ipm.pdf + + .. [HX10] W. Haemers and Q. Xiang, + Strongly regular graphs with parameters `(4m^4,2m^4+m^2,m^4+m^2,m^4+m^2)` exist for all `m>1`, + European Journal of Combinatorics, + Volume 31, Issue 6, August 2010, Pages 1553-1559, + http://dx.doi.org/10.1016/j.ejc.2009.07.009. + """ + if existence and (n,e) in _rshcd_cache: + return _rshcd_cache[n,e] + + from sage.graphs.strongly_regular_db import strongly_regular_graph + + def true(): + _rshcd_cache[n,e] = True + return True + + M = None + if abs(e) != 1: + raise ValueError + if n<0: + if existence: + return False + raise ValueError + elif n == 4: + if existence: + return true() + if e == 1: + M = J(4)-2*matrix(4,[[int(i+j == 3) for i in range(4)] for j in range(4)]) + else: + M = -J(4)+2*I(4) + elif n == 36: + if existence: + return true() + if e == 1: + M = strongly_regular_graph(36, 15, 6, 6).adjacency_matrix() + M = J(36) - 2*M + else: + M = strongly_regular_graph(36,14,4,6).adjacency_matrix() + M = -J(36) + 2*M + 2*I(36) + elif n == 100: + if existence: + return true() + if e == -1: + M = strongly_regular_graph(100,44,18,20).adjacency_matrix() + M = 2*M - J(100) + 2*I(100) + else: + M = strongly_regular_graph(100,45,20,20).adjacency_matrix() + M = J(100) - 2*M + elif n == 196 and e == 1: + if existence: + return true() + M = strongly_regular_graph(196,91,42,42).adjacency_matrix() + M = J(196) - 2*M + elif ( e == 1 and + n%16 == 0 and + is_square(n) and + is_prime_power(sqrt(n)-1) and + is_prime_power(sqrt(n)+1)): + if existence: + return true() + M = -rshcd_from_close_prime_powers(int(sqrt(n))) + + # Recursive construction: the kronecker product of two RSHCD is a RSHCD + else: + from itertools import product + for n1,e1 in product(divisors(n)[1:-1],[-1,1]): + e2 = e1*e + n2 = n//n1 + if (regular_symmetric_hadamard_matrix_with_constant_diagonal(n1,e1,existence=True) and + regular_symmetric_hadamard_matrix_with_constant_diagonal(n2,e2,existence=True)): + if existence: + return true() + M1 = regular_symmetric_hadamard_matrix_with_constant_diagonal(n1,e1) + M2 = regular_symmetric_hadamard_matrix_with_constant_diagonal(n2,e2) + M = M1.tensor_product(M2) + break + + if M is None: + from sage.misc.unknown import Unknown + _rshcd_cache[n,e] = Unknown + if existence: + return Unknown + raise ValueError("I do not know how to build a {}-RSHCD".format((n,e))) + + assert M*M.transpose() == n*I(n) + assert set(map(sum,M)) == {e*sqrt(n)} + + return M + +def _helper_payley_matrix(n): + r""" + Return the marix constructed in Lemma 1.19 page 291 of [SWW72]_. + + This function return a `n^2` matrix `M` whose rows/columns are indexed by + the element of a finite field on `n` elements `x_1,...,x_n`. The value + `M_{i,j}` is equal to `\chi(x_i-x_j)`. Note that `n` must be an odd prime power. + + The elements `x_1,...,x_n` are ordered in such a way that the matrix is + symmetric with respect to its second diagonal. The matrix is symmetric if + n==4k+1, and skew-symmetric if n=4k-1. + + INPUT: + + - ``n`` -- a prime power + + .. SEEALSO:: + + :func:`rshcd_from_close_prime_powers` + + EXAMPLE:: + + sage: from sage.combinat.matrices.hadamard_matrix import _helper_payley_matrix + sage: _helper_payley_matrix(5) + [ 0 1 -1 -1 1] + [ 1 0 1 -1 -1] + [-1 1 0 1 -1] + [-1 -1 1 0 1] + [ 1 -1 -1 1 0] + """ + from sage.rings.finite_rings.constructor import FiniteField as GF + K = GF(n,conway=True,prefix='x') + + # Order the elements of K in K_list + # so that K_list[i] = -K_list[n-i-1] + K_pairs = set(frozenset([x,-x]) for x in K) + K_pairs.discard(frozenset([0])) + K_list = [None]*n + for i,(x,y) in enumerate(K_pairs): + K_list[i] = x + K_list[-i-1] = y + K_list[n//2] = K(0) + + M = matrix(n,[[2*((x-y).is_square())-1 + for x in K_list] + for y in K_list]) + M = M-I(n) + assert (M*J(n)).is_zero() + assert (M*M.transpose()) == n*I(n)-J(n) + return M + +def rshcd_from_close_prime_powers(n): + r""" + Return a `(n^2,1)`-RSHCD when `n-1` and `n+1` are odd prime powers and `n=0\pmod{4}`. + + The construction implemented here appears in Theorem 4.3 from [GS14]_. + + Note that the authors of [SWW72]_ claim in Corollary 5.12 (page 342) to have + proved the same result without the `n=0\pmod{4}` restriction with a *very* + similar construction. So far, however, I (Nathann Cohen) have not been able + to make it work. + + INPUT: + + - ``n`` -- an integer congruent to `0\pmod{4}` + + .. SEEALSO:: + + :func:`regular_symmetric_hadamard_matrix_with_constant_diagonal` + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import rshcd_from_close_prime_powers + sage: rshcd_from_close_prime_powers(4) + [-1 -1 1 -1 1 -1 -1 1 -1 1 -1 -1 1 -1 1 -1] + [-1 -1 1 1 -1 -1 -1 -1 -1 1 1 -1 -1 1 -1 1] + [ 1 1 -1 1 1 -1 -1 -1 -1 -1 1 -1 -1 -1 1 -1] + [-1 1 1 -1 1 1 -1 -1 -1 -1 -1 1 -1 -1 -1 1] + [ 1 -1 1 1 -1 1 1 -1 -1 -1 -1 -1 1 -1 -1 -1] + [-1 -1 -1 1 1 -1 1 1 -1 -1 -1 1 -1 1 -1 -1] + [-1 -1 -1 -1 1 1 -1 -1 1 -1 1 -1 1 1 -1 -1] + [ 1 -1 -1 -1 -1 1 -1 -1 -1 1 -1 1 -1 1 1 -1] + [-1 -1 -1 -1 -1 -1 1 -1 -1 -1 1 1 1 -1 1 1] + [ 1 1 -1 -1 -1 -1 -1 1 -1 -1 -1 -1 1 1 -1 1] + [-1 1 1 -1 -1 -1 1 -1 1 -1 -1 -1 -1 1 1 -1] + [-1 -1 -1 1 -1 1 -1 1 1 -1 -1 -1 -1 -1 1 1] + [ 1 -1 -1 -1 1 -1 1 -1 1 1 -1 -1 -1 -1 -1 1] + [-1 1 -1 -1 -1 1 1 1 -1 1 1 -1 -1 -1 -1 -1] + [ 1 -1 1 -1 -1 -1 -1 1 1 -1 1 1 -1 -1 -1 -1] + [-1 1 -1 1 -1 -1 -1 -1 1 1 -1 1 1 -1 -1 -1] + + REFERENCE: + + .. [GS14] J.M. Goethals, and J. J. Seidel, + Strongly regular graphs derived from combinatorial designs, + Canadian Journal of Mathematics 22(1970), 597-614, + http://dx.doi.org/10.4153/CJM-1970-067-9 + + .. [SWW72] A Street, W. Wallis, J. Wallis, + Combinatorics: Room squares, sum-free sets, Hadamard matrices. + Lecture notes in Mathematics 292 (1972). + """ + if n%4: + raise ValueError("n(={}) must be congruent to 0 mod 4") + + a,b = sorted([n-1,n+1],key=lambda x:-x%4) + Sa = _helper_payley_matrix(a) + Sb = _helper_payley_matrix(b) + U = matrix(a,[[int(i+j == a-1) for i in range(a)] for j in range(a)]) + + K = (U*Sa).tensor_product(Sb) + U.tensor_product(J(b)-I(b)) - J(a).tensor_product(I(b)) + + F = lambda x:diagonal_matrix([-(-1)**i for i in range(x)]) + G = block_diagonal_matrix([J(1),I(a).tensor_product(F(b))]) + e = matrix(a*b,[1]*(a*b)) + H = block_matrix(2,[-J(1),e.transpose(),e,K]) + + HH = G*H*G + assert len(set(map(sum,HH))) == 1 + assert HH**2 == n**2*I(n**2) + return HH diff --git a/src/sage/combinat/matrices/latin.py b/src/sage/combinat/matrices/latin.py index 35bb467b50a..494a57b9567 100644 --- a/src/sage/combinat/matrices/latin.py +++ b/src/sage/combinat/matrices/latin.py @@ -2175,24 +2175,12 @@ def LatinSquare_generator(L_start, check_assertions = False): sage: next(g).is_latin_square() True - REFERENCE:: - - @article{MR1410617, - AUTHOR = {Jacobson, Mark T. and Matthews, Peter}, - TITLE = {Generating uniformly distributed random {L}atin squares}, - JOURNAL = {J. Combin. Des.}, - FJOURNAL = {Journal of Combinatorial Designs}, - VOLUME = {4}, - YEAR = {1996}, - NUMBER = {6}, - PAGES = {405--437}, - ISSN = {1063-8539}, - MRCLASS = {05B15 (60J10)}, - MRNUMBER = {MR1410617 (98b:05021)}, - MRREVIEWER = {Lars D{\o}vling Andersen}, - } - """ + REFERENCES: + .. [JacMat96] Mark T. Jacobson and Peter Matthews, "Generating uniformly + distributed random Latin squares", Journal of Combinatorial Designs, + 4 (1996) + """ if check_assertions: assert L_start.is_latin_square() n = L_start.nrows() @@ -2296,6 +2284,7 @@ def LatinSquare_generator(L_start, check_assertions = False): # usual proper = False # for emphasis + def group_to_LatinSquare(G): """ Construct a latin square on the symbols [0, 1, ..., n-1] for a @@ -2334,6 +2323,7 @@ def group_to_LatinSquare(G): T = G.cayley_table() return matrix(ZZ, T.table()) + def alternating_group_bitrade_generators(m): """ Construct generators a, b, c for the alternating group on 3m+1 @@ -2429,6 +2419,7 @@ def pq_group_bitrade_generators(p, q): return (a, b, c, PermutationGroup([P, Q])) + def p3_group_bitrade_generators(p): """ Generators for a group of order p3 where p is a prime. @@ -2439,7 +2430,6 @@ def p3_group_bitrade_generators(p): sage: p3_group_bitrade_generators(3) ((2,6,7)(3,8,9), (1,2,3)(4,7,8)(5,6,9), (1,9,2)(3,7,4)(5,8,6), Permutation Group with generators [(2,6,7)(3,8,9), (1,2,3)(4,7,8)(5,6,9)]) """ - assert is_prime(p) F = gap.new("FreeGroup(3)") @@ -2465,6 +2455,7 @@ def p3_group_bitrade_generators(p): return (x, y, (x*y)**(-1), PermutationGroup([x, y])) + def check_bitrade_generators(a, b, c): """ Three group elements a, b, c will generate a bitrade if a\*b\*c = 1 @@ -2490,6 +2481,7 @@ def check_bitrade_generators(a, b, c): X = gap.Intersection(gap.Intersection(A, B), C) return X.Size() == 1 + def is_bitrade(T1, T2): """ Combinatorially, a pair (T1, T2) of partial latin squares is a @@ -2515,6 +2507,7 @@ def is_bitrade(T1, T2): return True + def is_primary_bitrade(a, b, c, G): """ A bitrade generated from elements a, b, c is primary if a, b, c = @@ -2532,6 +2525,7 @@ def is_primary_bitrade(a, b, c, G): return G == H + def tau_to_bitrade(t1, t2, t3): """ Given permutations t1, t2, t3 that represent a latin bitrade, @@ -2648,6 +2642,7 @@ def bitrade_from_group(a, b, c, G): return tau_to_bitrade(t1, t2, t3) + def is_disjoint(T1, T2): """ The partial latin squares T1 and T2 are disjoint if T1[r, c] != @@ -2704,6 +2699,7 @@ def is_same_shape(T1, T2): return True + def is_row_and_col_balanced(T1, T2): """ Partial latin squares T1 and T2 are balanced if the symbols @@ -2737,6 +2733,7 @@ def is_row_and_col_balanced(T1, T2): return True + def dlxcpp_rows_and_map(P): """ Internal function for dlxcpp_find_completions. Given a partial @@ -2828,8 +2825,6 @@ def dlxcpp_find_completions(P, nr_to_find = None): [[0 1] [1 0]] """ - - assert P.nrows() == P.ncols() n = P.nrows() @@ -2863,6 +2858,7 @@ def dlxcpp_find_completions(P, nr_to_find = None): return comps + def bitrade(T1, T2): r""" Form the bitrade (Q1, Q2) from (T1, T2) by setting empty the cells @@ -2907,5 +2903,3 @@ def bitrade(T1, T2): Q2[r, c] = -1 return Q1, Q2 - - diff --git a/src/sage/combinat/ncsf_qsym/generic_basis_code.py b/src/sage/combinat/ncsf_qsym/generic_basis_code.py index e27e00e3ece..ea514d73de7 100644 --- a/src/sage/combinat/ncsf_qsym/generic_basis_code.py +++ b/src/sage/combinat/ncsf_qsym/generic_basis_code.py @@ -978,15 +978,16 @@ def degree(self): sage: S.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree sage: F = QuasiSymmetricFunctions(QQ).F() sage: F.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree """ return self.maximal_degree() + class AlgebraMorphism(ModuleMorphismByLinearity): # Find a better name """ A class for algebra morphism defined on a free algebra from the image of the generators diff --git a/src/sage/combinat/ncsf_qsym/ncsf.py b/src/sage/combinat/ncsf_qsym/ncsf.py index df18bf6d83b..fff8dd0393c 100644 --- a/src/sage/combinat/ncsf_qsym/ncsf.py +++ b/src/sage/combinat/ncsf_qsym/ncsf.py @@ -951,8 +951,9 @@ def bernstein_creation_operator(self, n): ....: for i in reversed(xs): ....: res = res.bernstein_creation_operator(i) ....: return res + sage: import itertools sage: all( immaculate_by_bernstein(p) == I.immaculate_function(p) - ....: for p in CartesianProduct(range(-1, 3), range(-1, 3), range(-1, 3)) ) + ....: for p in itertools.product(range(-1, 3), repeat=3)) True Some examples:: @@ -2339,7 +2340,7 @@ def coproduct_on_generators(self, i): Psi[] # Psi[3] + Psi[3] # Psi[] TESTS:: - + sage: Psi.coproduct_on_generators(0) Traceback (most recent call last): ... diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index 6e077ca001e..26568000798 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -22,7 +22,7 @@ .. [Haz2004] Michiel Hazewinkel, *Explicit polynomial generators for the ring of quasisymmetric functions over the integers*. - :arXiv:`math/0410366v1` + :arxiv:`math/0410366v1` .. [Rad1979] David E. Radford, *A natural ring basis for the shuffle algebra and an application to group schemes*, J. Algebra **58** (1979), 432-454. @@ -42,6 +42,12 @@ May 23, 2013, Springer. http://www.math.ubc.ca/%7Esteph/papers/QuasiSchurBook.pdf +.. [BBSSZ2012] Chris Berg, Nantel Bergeron, Franco Saliola, + Luis Serrano, Mike Zabrocki, + *A lift of the Schur and Hall-Littlewood bases to + non-commutative symmetric functions*, + :arxiv:`1208.5191v3`. + AUTHOR: - Jason Bandlow @@ -1060,10 +1066,6 @@ def frobenius(self, n): unexpectedly, the `n`-th Frobenius operator of the ring of symmetric functions. - :meth:`adams_operation` serves as alias for :meth:`frobenius`, - since the Frobenius operators are the Adams operations of - the `\Lambda`-ring of quasi-symmetric functions. - .. SEEALSO:: :meth:`Symmetric functions plethsym @@ -1128,7 +1130,9 @@ def frobenius(self, n): result_in_M_basis = M._from_dict(dct) return parent(result_in_M_basis) - adams_operation = frobenius + def adams_operation(self, *args, **opts): + from sage.misc.superseded import deprecation + deprecation(19255, "Do not use this method! Please use `frobenius` or `adams_operator` methods following what you expect.") def star_involution(self): r""" @@ -2730,14 +2734,6 @@ def __init__(self, QSym): This basis first appears in [BBSSZ2012]_. - REFERENCES: - - .. [BBSSZ2012] Chris Berg, Nantel Bergeron, Franco Saliola, - Luis Serrano, Mike Zabrocki, - *A lift of the Schur and Hall-Littlewood bases to - non-commutative symmetric functions*, - :arXiv:`1208.5191v3`. - EXAMPLES:: sage: QSym = QuasiSymmetricFunctions(QQ) @@ -2773,13 +2769,13 @@ def _to_Monomial_on_basis(self, J): EXAMPLES:: - sage: dI = QuasiSymmetricFunctions(QQ).dI() - sage: dI._to_Monomial_on_basis(Composition([1,3])) - M[1, 1, 1, 1] + M[1, 1, 2] + M[1, 2, 1] + M[1, 3] - sage: dI._to_Monomial_on_basis(Composition([])) - M[] - sage: dI._to_Monomial_on_basis(Composition([2,1,2])) - 4*M[1, 1, 1, 1, 1] + 3*M[1, 1, 1, 2] + 2*M[1, 1, 2, 1] + M[1, 1, 3] + M[1, 2, 1, 1] + M[1, 2, 2] + M[2, 1, 1, 1] + M[2, 1, 2] + sage: dI = QuasiSymmetricFunctions(QQ).dI() + sage: dI._to_Monomial_on_basis(Composition([1,3])) + M[1, 1, 1, 1] + M[1, 1, 2] + M[1, 2, 1] + M[1, 3] + sage: dI._to_Monomial_on_basis(Composition([])) + M[] + sage: dI._to_Monomial_on_basis(Composition([2,1,2])) + 4*M[1, 1, 1, 1, 1] + 3*M[1, 1, 1, 2] + 2*M[1, 1, 2, 1] + M[1, 1, 3] + M[1, 2, 1, 1] + M[1, 2, 2] + M[2, 1, 1, 1] + M[2, 1, 2] """ M = self.realization_of().Monomial() if not J._list: diff --git a/src/sage/combinat/ncsf_qsym/tutorial.py b/src/sage/combinat/ncsf_qsym/tutorial.py index 1c987793d64..5cf429b0d17 100644 --- a/src/sage/combinat/ncsf_qsym/tutorial.py +++ b/src/sage/combinat/ncsf_qsym/tutorial.py @@ -92,8 +92,8 @@ sage: y.expand(4) x0*x1^2*x2 + x0*x1^2*x3 + x0*x2^2*x3 + x1*x2^2*x3 -The usual methods on free modules are available such as coefficients, degrees, -and the support:: +The usual methods on free modules are available such as coefficients, +degrees, and the support:: sage: z=3*M[1,2]+M[3]^2; z 3*M[1, 2] + 2*M[3, 3] + M[6] @@ -104,10 +104,10 @@ sage: z.degree() 6 - sage: z.coefficients() - [3, 2, 1] + sage: sorted(z.coefficients()) + [1, 2, 3] - sage: z.monomials() + sage: sorted(z.monomials(), key=lambda x: x.support()) [M[1, 2], M[3, 3], M[6]] sage: z.monomial_coefficients() diff --git a/src/sage/combinat/ordered_tree.py b/src/sage/combinat/ordered_tree.py index a0e39880a95..b69f00686ab 100644 --- a/src/sage/combinat/ordered_tree.py +++ b/src/sage/combinat/ordered_tree.py @@ -14,6 +14,9 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** + +import itertools + from sage.structure.list_clone import ClonableArray, ClonableList from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -940,7 +943,6 @@ def _element_constructor_(self, *args, **keywords): from sage.misc.lazy_attribute import lazy_attribute from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.composition import Compositions -from sage.combinat.cartesian_product import CartesianProduct ################################################################# # Enumerated set of binary trees of a given size ################################################################# @@ -1073,7 +1075,7 @@ def __iter__(self): return else: for c in Compositions(self._size - 1): - for lst in CartesianProduct(*[self.__class__(_) for _ in c]): + for lst in itertools.product(*[self.__class__(_) for _ in c]): yield self._element_constructor_(lst) @lazy_attribute diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 144751c12df..2a9b05a9482 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -311,7 +311,7 @@ from sage.combinat.partitions import number_of_partitions as bober_number_of_partitions from sage.combinat.partitions import ZS1_iterator, ZS1_iterator_nk from sage.combinat.integer_vector import IntegerVectors -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.root_system.weyl_group import WeylGroup from sage.combinat.combinatorial_map import combinatorial_map from sage.groups.perm_gps.permgroup import PermutationGroup @@ -4032,7 +4032,8 @@ def remove_horizontal_border_strip(self, k): sage: Partition([5,3,1]).remove_horizontal_border_strip(6).list() [] - The result is returned as an instance of :class:`IntegerListsLex`:: + The result is returned as an instance of + :class:`Partitions_with_constraints`:: sage: Partition([5,3,1]).remove_horizontal_border_strip(5) The subpartitions of [5, 3, 1] obtained by removing an horizontal border strip of length 5 @@ -4048,15 +4049,13 @@ def remove_horizontal_border_strip(self, k): sage: Partition([]).remove_horizontal_border_strip(6).list() [] """ - return IntegerListsLex(n = self.size()-k, - min_length = len(self)-1, - max_length = len(self), - floor = self[1:]+[0], - ceiling = self[:], - max_slope = 0, - element_class = Partition, - global_options = Partitions.global_options, - name = "The subpartitions of %s obtained by removing an horizontal border strip of length %s"%(self,k)) + return Partitions_with_constraints(n = self.size()-k, + min_length = len(self)-1, + max_length = len(self), + floor = self[1:]+[0], + ceiling = self[:], + max_slope = 0, + name = "The subpartitions of {} obtained by removing an horizontal border strip of length {}".format(self,k)) def k_conjugate(self, k): r""" @@ -4636,14 +4635,12 @@ def coloring(i): if directed: from sage.graphs.digraph import DiGraph - G = DiGraph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DDEG = G.copy(immutable=True) + self._DDEG = DiGraph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) else: from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DEG = G.copy(immutable=True) + self._DEG = Graph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) return self.dual_equivalence_graph(directed, coloring) ############## @@ -4658,8 +4655,8 @@ class Partitions(UniqueRepresentation, Parent): Valid keywords are: ``starting``, ``ending``, ``min_part``, ``max_part``, ``max_length``, ``min_length``, ``length``, - ``max_slope``, ``min_slope``, ``inner``, ``outer``, and - ``parts_in``. They have the following meanings: + ``max_slope``, ``min_slope``, ``inner``, ``outer``, ``parts_in`` + and ``regular``. They have the following meanings: - ``starting=p`` specifies that the partitions should all be less than or equal to `p` in lex order. This argument cannot be combined @@ -4693,6 +4690,19 @@ class Partitions(UniqueRepresentation, Parent): integers. This argument cannot be combined with any other (see :trac:`15467`). + - ``regular=ell`` specifies that the partitions are `\ell`-regular, + and can only be combined with the ``max_length`` or ``max_part``, but + not both, keywords if `n` is not specified + + The ``max_*`` versions, along with ``inner`` and ``ending``, work + analogously. + + Right now, the ``parts_in``, ``starting``, ``ending``, and ``regular`` + keyword arguments are mutually exclusive, both of each other and of other + keyword arguments. If you specify, say, ``parts_in``, all other + keyword arguments will be ignored; ``starting``, ``ending``, and + ``regular`` work the same way. + EXAMPLES: If no arguments are passed, then the combinatorial class @@ -4769,6 +4779,16 @@ class Partitions(UniqueRepresentation, Parent): sage: Partitions(10, min_part=2, length=3).list() [[6, 2, 2], [5, 3, 2], [4, 4, 2], [4, 3, 3]] + Some examples using the ``regular`` keyword:: + + sage: Partitions(regular=4) + 4-Regular Partitions + sage: Partitions(regular=4, max_length=3) + 4-Regular Partitions with max length 3 + sage: Partitions(regular=4, max_part=3) + 4-Regular 3-Bounded Partitions + sage: Partitions(3, regular=4) + 4-Regular Partitions of the integer 3 Here are some further examples using various constraints:: @@ -4826,7 +4846,7 @@ class Partitions(UniqueRepresentation, Parent): sage: TestSuite(Partitions(0)).run() sage: TestSuite(Partitions(5)).run() - sage: TestSuite(Partitions(5, min_part=2)).run() # Not tested: todo - IntegerListsLex needs to pickle properly + sage: TestSuite(Partitions(5, min_part=2)).run() sage: repr( Partitions(5, min_part=2) ) 'Partitions of the integer 5 satisfying constraints min_part=2' @@ -4940,12 +4960,21 @@ def __classcall_private__(cls, n=None, **kwargs): raise ValueError("n cannot be infinite") if n is None or n is NN or n is NonNegativeIntegers(): if len(kwargs) > 0: - if len(kwargs) == 1 and 'max_part' in kwargs: - return Partitions_all_bounded(kwargs['max_part']) - else: - raise ValueError("the size must be specified with any keyword argument") - else: - return Partitions_all() + if len(kwargs) == 1: + if 'max_part' in kwargs: + return Partitions_all_bounded(kwargs['max_part']) + if 'regular' in kwargs: + return RegularPartitions_all(kwargs['regular']) + elif len(kwargs) == 2: + if 'regular' in kwargs: + if kwargs['regular'] < 2: + raise ValueError("the regularity must be at least 2") + if 'max_part' in kwargs: + return RegularPartitions_bounded(kwargs['regular'], kwargs['max_part']) + if 'max_length' in kwargs: + return RegularPartitions_truncated(kwargs['regular'], kwargs['max_length']) + raise ValueError("the size must be specified with any keyword argument") + return Partitions_all() elif isinstance(n, (int,Integer)): if len(kwargs) == 0: return Partitions_n(n) @@ -4964,11 +4993,13 @@ def __classcall_private__(cls, n=None, **kwargs): "'ending' cannot be combined with anything else.") if 'parts_in' in kwargs: - return Partitions_parts_in(n, kwargs['parts_in']) + return Partitions_parts_in(n, kwargs['parts_in']) elif 'starting' in kwargs: return Partitions_starting(n, kwargs['starting']) elif 'ending' in kwargs: return Partitions_ending(n, kwargs['ending']) + elif 'regular' in kwargs: + return RegularPartitions_n(n, kwargs['regular']) # FIXME: should inherit from IntegerListLex, and implement repr, or _name as a lazy attribute kwargs['name'] = "Partitions of the integer %s satisfying constraints %s"%(n, ", ".join( ["%s=%s"%(key, kwargs[key]) for key in sorted(kwargs.keys())] )) @@ -4995,13 +5026,10 @@ def __classcall_private__(cls, n=None, **kwargs): kwargs['min_length'] = max(len(inner), kwargs.get('min_length',0)) del kwargs['inner'] + return Partitions_with_constraints(n, **kwargs) - kwargs['element_class'] = Partition - kwargs['global_options'] = Partitions.global_options - return IntegerListsLex(n, **kwargs) - else: - raise ValueError("n must be an integer or be equal to one of "+ - "None, NN, NonNegativeIntegers()") + raise ValueError("n must be an integer or be equal to one of " + "None, NN, NonNegativeIntegers()") def __init__(self, is_infinite=False): """ @@ -6614,14 +6642,446 @@ def __setstate__(self, data): [[2, 1], [1, 1, 1]] """ n = data['n'] - self.__class__ = IntegerListsLex + self.__class__ = Partitions_with_constraints constraints = {'max_slope' : 0, - 'min_part' : 1, - 'element_class' : Partition, - 'global_options' : Partitions.global_options} + 'min_part' : 1} constraints.update(data['constraints']) self.__init__(n, **constraints) +class Partitions_with_constraints(IntegerListsLex): + """ + Partitions which satisfy a set of constraints. + + EXAMPLES:: + + sage: P = Partitions(6, inner=[1,1], max_slope=-1) + sage: list(P) + [[5, 1], [4, 2], [3, 2, 1]] + + TESTS:: + + sage: P = Partitions(6, min_part=2, max_slope=-1) + sage: TestSuite(P).run() + + Test that :trac:`15525` is fixed:: + + sage: loads(dumps(P)) == P + True + """ +# def __init__(self, n, **kwargs): +# """ +# Initialize ``self``. +# """ +# IntegerListsLex.__init__(self, n, **kwargs) + + Element = Partition + global_options = PartitionOptions + +###################### +# Regular Partitions # +###################### + +class RegularPartitions(Partitions): + r""" + Base class for `\ell`-regular partitions. + + Let `\ell` be a positive integer. A partition `\lambda` is + `\ell`-*regular* if `m_i < \ell` for all `i`, where `m_i` is the + multiplicity of `i` in `\lambda`. + + .. NOTE:: + + This is conjugate to the notion of `\ell`-*restricted* partitions, + where the difference between any two parts is at most `\ell`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``is_infinite`` -- boolean; if the subset of `\ell`-regular + partitions is infinite + """ + def __init__(self, ell, is_infinte=False): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: TestSuite(P).run() + """ + self._ell = ell + Partitions.__init__(self, is_infinte) + + def ell(self): + r""" + Return the value `\ell`. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: P.ell() + 2 + """ + return self._ell + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=3) + sage: [5] in P + True + sage: [] in P + True + sage: [3, 3, 2, 2] in P + True + sage: [3, 3, 3, 1] in P + False + sage: [4, 0, 0, 0, 0, 0] in P + True + sage: Partition([4,2,2,1]) in P + True + sage: Partition([4,2,2,2]) in P + False + sage: Partition([10,1]) in P + True + """ + if not Partitions.__contains__(self, x): + return False + if isinstance(x, Partition): + return max(x.to_exp(1)) < self._ell + return all(x.count(i) < self._ell for i in set(x) if i > 0) + + def _fast_iterator(self, n, max_part): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + if n == 0: + yield [] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_all(RegularPartitions): + r""" + The class of all `\ell`-regular partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell, True) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_all + sage: RegularPartitions_all(3) + 3-Regular Partitions + """ + return "{}-Regular Partitions".format(self._ell) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [2, 1, 1]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + +class RegularPartitions_truncated(RegularPartitions): + r""" + The class of `\ell`-regular partitions with max length `k`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``max_len`` -- integer; the maximum length + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, max_len): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: TestSuite(P).run() + """ + self._max_len = max_len + RegularPartitions.__init__(self, ell, True) + + def max_length(self): + """ + Return the maximum length of the partitions of ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: P.max_length() + 3 + """ + return self._max_len + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_length=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1, 1] in P + False + """ + return len(x) <= self._max_len and RegularPartitions.__contains__(self, x) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_truncated + sage: RegularPartitions_truncated(4, 3) + 4-Regular Partitions with max length 3 + """ + return "{}-Regular Partitions with max length {}".format(self._ell, self._max_len) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3, max_length=2) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [5]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + + def _fast_iterator(self, n, max_part, depth=0): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_length=2) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2]] + """ + if n == 0 or depth >= self._max_len: + yield [] + return + + # Special case + if depth + 1 == self._max_len: + if max_part >= n: + yield [n] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i, depth+1): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_bounded(RegularPartitions): + r""" + The class of `\ell`-regular `k`-bounded partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``k`` -- integer; the value `k` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, k): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_part=3) + sage: TestSuite(P).run() + """ + self.k = k + RegularPartitions.__init__(self, ell, False) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_part=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1] in P + False + """ + return len(x) == 0 or (x[0] <= self.k and RegularPartitions.__contains__(self, x)) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_bounded + sage: RegularPartitions_bounded(4, 3) + 4-Regular 3-Bounded Partitions + """ + return "{}-Regular {}-Bounded Partitions".format(self._ell, self.k) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_part=3) + sage: list(P) + [[3, 2, 1], [3, 2], [3, 1], [3], [2, 1], [2], [1], []] + """ + k = self.k + for n in reversed(range(k*(k+1)/2 * self._ell)): + for p in self._fast_iterator(n, k): + yield self.element_class(self, p) + +class RegularPartitions_n(RegularPartitions, Partitions_n): + r""" + The class of `\ell`-regular partitions of `n`. + + INPUT: + + - ``n`` -- the integer `n` to partition + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, n, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell) + Partitions_n.__init__(self, n) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_n + sage: RegularPartitions_n(3, 5) + 5-Regular Partitions of the integer 3 + """ + return "{}-Regular Partitions of the integer {}".format(self._ell, self.n) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(5, regular=3) + sage: [3, 1, 1] in P + True + sage: [3, 2, 1] in P + False + """ + return RegularPartitions.__contains__(self, x) and sum(x) == self.n + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: list(P) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + for p in self._fast_iterator(self.n, self.n): + yield self.element_class(self, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: P.cardinality() + 5 + sage: P = Partitions(5, regular=6) + sage: P.cardinality() + 7 + sage: P.cardinality() == Partitions(5).cardinality() + True + """ + if self._ell > self.n: + return Partitions_n.cardinality(self) + return ZZ.sum(1 for x in self) ###################### # Ordered Partitions # @@ -6830,7 +7290,7 @@ def _repr_(self): # Partitions Greatest EQ # ########################## -class PartitionsGreatestEQ(IntegerListsLex, UniqueRepresentation): +class PartitionsGreatestEQ(UniqueRepresentation, IntegerListsLex): """ The class of all (unordered) "restricted" partitions of the integer `n` having its greatest part equal to the integer `k`. @@ -7086,7 +7546,7 @@ def number_of_partitions(n, algorithm='default'): [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] sage: len(v) 7 - sage: number_of_partitions(5, algorithm='bober') + sage: number_of_partitions(5, algorithm='bober') 7 The input must be a nonnegative integer or a ``ValueError`` is raised. diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 32bde2f5261..06ec9991a60 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -256,7 +256,8 @@ class of modules for the algebras, which are generalisations of the Specht # http://www.gnu.org/licenses/ #***************************************************************************** -from cartesian_product import CartesianProduct +import itertools + from combinat import CombinatorialElement from integer_vector import IntegerVectors from partition import Partition, Partitions, Partitions_n, _Partitions @@ -538,8 +539,10 @@ def _repr_diagram(self): EXAMPLES:: - sage: PartitionTuple(([2,1],[3,2],[1,1,1]))._repr_diagram() - ' ** *** *\n * ** *\n *' + sage: print PartitionTuple(([2,1],[3,2],[1,1,1]))._repr_diagram() + ** *** * + * ** * + * """ return self.diagram() @@ -1485,7 +1488,7 @@ class PartitionTuples(UniqueRepresentation, Parent): sage: ( [] ) in PartitionTuples() True - Check that trac:`14145` has been fixed:: + Check that :trac:`14145` has been fixed:: sage: 1 in PartitionTuples() False @@ -1562,7 +1565,7 @@ def _element_constructor_(self, mu): """ # one way or another these two cases need to be treated separately - if mu==[] or mu==[[]]: + if mu == [] or mu == () or mu == [[]]: return Partition([]) # As partitions are 1-tuples of partitions we need to treat them separately @@ -1681,9 +1684,9 @@ def _an_element_(self): Return a generic element. EXAMPLES:: + sage: PartitionTuples().an_element() # indirect doctest ([1, 1, 1, 1], [2, 1, 1], [3, 1], [4]) - """ return PartitionTuple( ([1,1,1,1],[2,1,1],[3,1],[4]) ) @@ -1756,9 +1759,9 @@ def _an_element_(self): Return a generic element. EXAMPLES:: + sage: PartitionTuples().an_element() # indirect doctest ([1, 1, 1, 1], [2, 1, 1], [3, 1], [4]) - """ return self.element_class(self,([1,1,1,1],[2,1,1],[3,1],[4])) @@ -1826,28 +1829,28 @@ def __iter__(self): EXAMPLES:: - sage: parts=PartitionTuples(3) - sage: [parts[k] for k in range(20)] - [([], [], []), - ([1], [], []), - ([], [1], []), - ([], [], [1]), - ([2], [], []), - ([1, 1], [], []), - ([1], [1], []), - ([1], [], [1]), - ([], [2], []), - ([], [1, 1], []), - ([], [1], [1]), - ([], [], [2]), - ([], [], [1, 1]), - ([3], [], []), - ([2, 1], [], []), - ([1, 1, 1], [], []), - ([2], [1], []), - ([1, 1], [1], []), - ([2], [], [1]), - ([1, 1], [], [1])] + sage: parts=PartitionTuples(3) + sage: [parts[k] for k in range(20)] + [([], [], []), + ([1], [], []), + ([], [1], []), + ([], [], [1]), + ([2], [], []), + ([1, 1], [], []), + ([1], [1], []), + ([1], [], [1]), + ([], [2], []), + ([], [1, 1], []), + ([], [1], [1]), + ([], [], [2]), + ([], [], [1, 1]), + ([3], [], []), + ([2, 1], [], []), + ([1, 1, 1], [], []), + ([2], [1], []), + ([1, 1], [1], []), + ([2], [], [1]), + ([1, 1], [], [1])] """ for size in NN: for mu in PartitionTuples_level_size(self.level(),size): @@ -1858,9 +1861,9 @@ def _an_element_(self): Return a generic element. EXAMPLES:: + sage: PartitionTuples(level=4).an_element() # indirect doctest ([], [1], [2], [3]) - """ return self.element_class(self, tuple([l] for l in range(self.level()) )) @@ -1874,6 +1877,7 @@ def __init__(self, size): Initializes this class. EXAMPLES:: + sage: PartitionTuples(size=4) Partition tuples of size 4 sage: PartitionTuples(size=6) @@ -1957,9 +1961,9 @@ def _an_element_(self): Return a generic element. EXAMPLES:: + sage: PartitionTuples(size=4).an_element() # indirect doctest ([1], [1], [1], [1]) - """ return self.element_class(self, tuple([1] for l in range(self.size()) )) @@ -2048,7 +2052,7 @@ def __iter__(self): """ p = [Partitions(i) for i in range(self.size()+1)] for iv in IntegerVectors(self.size(),self.level()): - for cp in CartesianProduct(*[p[i] for i in iv]): + for cp in itertools.product(*[p[i] for i in iv]): yield self._element_constructor_(cp) @@ -2105,7 +2109,7 @@ def __setstate__(self, state): """ In order to maintain backwards compatibility and be able to unpickle a old pickle from PartitionTuples_nk we have to override the default - __setstate__. + ``__setstate__``. TESTS:: diff --git a/src/sage/combinat/partitions.pyx b/src/sage/combinat/partitions.pyx index b0f1c6bb297..65fdb144662 100644 --- a/src/sage/combinat/partitions.pyx +++ b/src/sage/combinat/partitions.pyx @@ -159,11 +159,6 @@ def ZS1_iterator(int n): if n == 0: yield [] return - #cdef int *x = malloc(sizeof(int) *n) - #x[0] = n - #cdef int i - #for i in range(1, n): - # x[i] = 1 x = [1]*n x[0] = n @@ -227,11 +222,6 @@ def ZS1_iterator_nk(int n, int k): if k == 1: yield [n] return - #cdef int *x = malloc(sizeof(int) *n) - #x[0] = n - #cdef int i - #for i in range(1, n): - # x[i] = 1 x = [1]*k x[0] = n diff --git a/src/sage/combinat/perfect_matching.py b/src/sage/combinat/perfect_matching.py index 40852a14e1f..74f0ebe0c59 100644 --- a/src/sage/combinat/perfect_matching.py +++ b/src/sage/combinat/perfect_matching.py @@ -808,6 +808,35 @@ def to_permutation(self): from sage.combinat.permutation import Permutation return Permutation(self.value) + def to_non_crossing_set_partition(self): + r""" + Returns the noncrossing set partition (on half as many elements) + corresponding to the perfect matching if the perfect matching is + noncrossing, and otherwise gives an error. + + OUTPUT: + + The realization of ``self`` as a noncrossing set partition. + + EXAMPLES:: + + sage: PerfectMatching([[1,3], [4,2]]).to_non_crossing_set_partition() + Traceback (most recent call last): + ... + ValueError: matching must be non-crossing + sage: PerfectMatching([[1,4], [3,2]]).to_non_crossing_set_partition() + {{1, 2}} + sage: PerfectMatching([]).to_non_crossing_set_partition() + {} + """ + from sage.combinat.set_partition import SetPartition + if not self.is_non_crossing(): + raise ValueError("matching must be non-crossing") + else: + perm = self.to_permutation() + perm2 = Permutation([(perm[2*i])/2 for i in range(len(perm)/2)]) + return SetPartition(perm2.cycle_tuples()) + class PerfectMatchings(UniqueRepresentation, Parent): r""" diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 9baf23fe398..b07661028de 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -491,10 +491,10 @@ def __classcall_private__(cls, l, check_input = True): EXAMPLES:: - sage: P = Permutation([2,1]); P - [2, 1] - sage: P.parent() - Standard permutations + sage: P = Permutation([2,1]); P + [2, 1] + sage: P.parent() + Standard permutations """ if isinstance(l, Permutation): return l @@ -4834,7 +4834,7 @@ def shifted_shuffle(self, other): ################################################################ # Base class for permutations -class Permutations(Parent, UniqueRepresentation): +class Permutations(UniqueRepresentation, Parent): r""" Permutations. diff --git a/src/sage/combinat/posets/__init__.py b/src/sage/combinat/posets/__init__.py index fb7d037e33c..61b51a2423d 100644 --- a/src/sage/combinat/posets/__init__.py +++ b/src/sage/combinat/posets/__init__.py @@ -15,14 +15,20 @@ - :ref:`sage.combinat.posets.incidence_algebras` +- :ref:`sage.combinat.posets.cartesian_product` + +- :ref:`sage.combinat.posets.moebius_algebra` + - :ref:`sage.combinat.tamari_lattices` - :ref:`sage.combinat.interval_posets` - :ref:`sage.combinat.shard_order` If you are looking for Poset-related :ref:`categories `, see -:class:`~sage.categories.posets.Posets` and -:class:`~sage.categories.lattice_posets.LatticePosets`. +:class:`~sage.categories.posets.Posets`, +:class:`~sage.categories.finite_posets.FinitePosets`, +:class:`~sage.categories.lattice_posets.LatticePosets` and +:class:`~sage.categories.finite_lattice_posets.FiniteLatticePosets`. """ diff --git a/src/sage/combinat/posets/cartesian_product.py b/src/sage/combinat/posets/cartesian_product.py new file mode 100644 index 00000000000..10bb66d5fe6 --- /dev/null +++ b/src/sage/combinat/posets/cartesian_product.py @@ -0,0 +1,505 @@ +""" +Cartesian products of Posets + +AUTHORS: + +- Daniel Krenn (2015) + +""" +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.sets.cartesian_product import CartesianProduct + + +class CartesianProductPoset(CartesianProduct): + r""" + A class implementing cartesian products of posets (and elements + thereof). Compared to :class:`CartesianProduct` you are able to + specify an order for comparison of the elements. + + INPUT: + + - ``sets`` -- a tuple of parents. + + - ``category`` -- a subcategory of + ``Sets().CartesianProducts() & Posets()``. + + - ``order`` -- a string or function specifying an order less or equal. + It can be one of the following: + + - ``'native'`` -- elements are ordered by their native ordering, + i.e., the order the wrapped elements (tuples) provide. + + - ``'lex'`` -- elements are ordered lexicographically. + + - ``'product'`` -- an element is less or equal to another + element, if less or equal is true for all its components + (cartesian projections). + + - A function which performs the comparison `\leq`. It takes two + input arguments and outputs a boolean. + + Other keyword arguments (``kwargs``) are passed to the constructor + of :class:`CartesianProduct`. + + EXAMPLES:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: Cl = cartesian_product((P, P), order='lex') + sage: Cl((1, 1)) <= Cl((2, 0)) + True + sage: Cp = cartesian_product((P, P), order='product') + sage: Cp((1, 1)) <= Cp((2, 0)) + False + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: Cs = cartesian_product((P, P), order=le_sum) + sage: Cs((1, 1)) <= Cs((2, 0)) + True + + TESTS:: + + sage: Cl.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cl).run() + sage: Cp.category() + Join of Category of finite posets and + Category of Cartesian products of finite enumerated sets + sage: TestSuite(Cp).run() + + .. SEEALSO: + + :class:`CartesianProduct` + """ + + def __init__(self, sets, category, order=None, **kwargs): + r""" + See :class:`CartesianProductPoset` for details. + + TESTS:: + + sage: P = Poset((srange(3), lambda left, right: left <= right)) + sage: C = cartesian_product((P, P), order='notexisting') + Traceback (most recent call last): + ... + ValueError: No order 'notexisting' known. + sage: C = cartesian_product((P, P), category=(Groups(),)) + sage: C.category() + Join of Category of groups and Category of posets + """ + if order is None: + self._le_ = self.le_product + elif isinstance(order, str): + try: + self._le_ = getattr(self, 'le_' + order) + except AttributeError: + raise ValueError("No order '%s' known." % (order,)) + else: + self._le_ = order + + from sage.categories.category import Category + from sage.categories.posets import Posets + if not isinstance(category, tuple): + category = (category,) + category = Category.join(category + (Posets(),)) + super(CartesianProductPoset, self).__init__( + sets, category, **kwargs) + + + def le(self, left, right): + r""" + Test whether ``left`` is less than or equal to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the order defined on creation of this + cartesian product. See :class:`CartesianProductPoset`. + + EXAMPLES:: + + sage: P = Posets.ChainPoset(10) + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((P, P), order=le_sum) + sage: C.le(C((1, 6)), C((6, 1))) + True + sage: C.le(C((6, 1)), C((1, 6))) + False + sage: C.le(C((1, 6)), C((6, 6))) + True + sage: C.le(C((6, 6)), C((1, 6))) + False + """ + return self._le_(left, right) + + + def le_lex(self, left, right): + r""" + Test whether ``left`` is lexicographically smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='lex') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + for l, r, S in \ + zip(left.value, right.value, self.cartesian_factors()): + if l == r: + continue + if S.le(l, r): + return True + if S.le(r, l): + return False + return True # equal + + + def le_product(self, left, right): + r""" + Test whether ``left`` is component-wise smaller or equal + to ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + The comparison is ``True`` if the result of the + comparision in each component is ``True``. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='product') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = False + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return all( + S.le(l, r) + for l, r, S in + zip(left.value, right.value, self.cartesian_factors())) + + + def le_native(self, left, right): + r""" + Test whether ``left`` is smaller or equal to ``right`` in the order + provided by the elements themselves. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: P = Poset((srange(2), lambda left, right: left <= right)) + sage: Q = cartesian_product((P, P), order='native') + sage: T = [Q((0, 0)), Q((1, 1)), Q((0, 1)), Q((1, 0))] + sage: for a in T: + ....: for b in T: + ....: assert(Q.le(a, b) == (a <= b)) + ....: print '%s <= %s = %s' % (a, b, a <= b) + (0, 0) <= (0, 0) = True + (0, 0) <= (1, 1) = True + (0, 0) <= (0, 1) = True + (0, 0) <= (1, 0) = True + (1, 1) <= (0, 0) = False + (1, 1) <= (1, 1) = True + (1, 1) <= (0, 1) = False + (1, 1) <= (1, 0) = False + (0, 1) <= (0, 0) = False + (0, 1) <= (1, 1) = True + (0, 1) <= (0, 1) = True + (0, 1) <= (1, 0) = True + (1, 0) <= (0, 0) = False + (1, 0) <= (1, 1) = True + (1, 0) <= (0, 1) = False + (1, 0) <= (1, 0) = True + """ + return left.value <= right.value + + + class Element(CartesianProduct.Element): + + def _le_(self, other): + r""" + Return if this element is less or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method calls :meth:`CartesianProductPoset.le`. Override + it in inherited class to change this. + + It can be assumed that this element and ``other`` have + the same parent. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) # indirect doctest + True + sage: C((1/3, 2)) <= C((2, 2)) # indirect doctest + True + """ + return self.parent().le(self, other) + + + def __le__(self, other): + r""" + Return if this element is less than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: from sage.combinat.posets.cartesian_product import CartesianProductPoset + sage: QQ.CartesianProduct = CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) <= C((2, 1/3)) + True + sage: C((1/3, 2)) <= C((2, 2)) + True + + The following example tests that the coercion gets involved in + comparisons; it can be simplified once #18182 is in merged. + :: + + sage: class MyCP(CartesianProductPoset): + ....: def _coerce_map_from_(self, S): + ....: if isinstance(S, self.__class__): + ....: S_factors = S.cartesian_factors() + ....: R_factors = self.cartesian_factors() + ....: if len(S_factors) == len(R_factors): + ....: if all(r.has_coerce_map_from(s) + ....: for r,s in zip(R_factors, S_factors)): + ....: return True + sage: QQ.CartesianProduct = MyCP + sage: A = cartesian_product((QQ, ZZ), order=le_sum) + sage: B = cartesian_product((QQ, QQ), order=le_sum) + sage: A((1/2, 4)) <= B((1/2, 5)) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def __ge__(self, other): + r""" + Return if this element is greater than or equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) >= C((2, 1/3)) + False + sage: C((1/3, 2)) >= C((2, 2)) + False + """ + return other.__le__(self) + + + def __lt__(self, other): + r""" + Return if this element is less than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) < C((2, 1/3)) + True + sage: C((1/3, 2)) < C((2, 2)) + True + """ + return not self == other and self.__le__(other) + + + def __gt__(self, other): + r""" + Return if this element is greater than ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method uses the coercion framework to find a + suitable common parent. + + This method can be deleted once :trac:`10130` is fixed and + provides these methods automatically. + + TESTS:: + + sage: QQ.CartesianProduct = sage.combinat.posets.cartesian_product.CartesianProductPoset # needed until #19269 is fixed + sage: def le_sum(left, right): + ....: return (sum(left) < sum(right) or + ....: sum(left) == sum(right) and left[0] <= right[0]) + sage: C = cartesian_product((QQ, QQ), order=le_sum) + sage: C((1/3, 2)) > C((2, 1/3)) + False + sage: C((1/3, 2)) > C((2, 2)) + False + """ + return not self == other and other.__le__(self) diff --git a/src/sage/combinat/posets/elements.py b/src/sage/combinat/posets/elements.py index b27afd59980..0cf391ef992 100644 --- a/src/sage/combinat/posets/elements.py +++ b/src/sage/combinat/posets/elements.py @@ -51,6 +51,17 @@ def __init__(self, poset, element, vertex): self.element = element self.vertex = vertex + def __hash__(self): + r""" + TESTS:: + + sage: P = Poset([[1,2],[4],[3],[4],[]], facade = False) + sage: e = P(0) + sage: hash(e) + 0 + """ + return hash(self.element) + def _repr_(self): """ TESTS:: diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 2858c48519e..b6baeb31ccc 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Hasse diagrams of posets @@ -52,6 +53,7 @@ class HasseDiagram(DiGraph): def _repr_(self): r""" TESTS:: + sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H._repr_() @@ -135,7 +137,7 @@ def cover_relations(self): sage: H.cover_relations() [(0, 2), (0, 3), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)] """ - return [c for c in self.cover_relations_iterator()] + return list(self.cover_relations_iterator()) def is_lequal(self, i, j): """ @@ -374,6 +376,8 @@ def is_chain(self): sage: p.is_chain() False """ + if self.cardinality() == 0: + return True return (self.num_edges()+1 == self.num_verts() and # Hasse Diagram is a tree all(d<=1 for d in self.out_degree()) and # max outdegree is <= 1 all(d<=1 for d in self.in_degree())) # max indegree is <= 1 @@ -424,8 +428,8 @@ def interval(self, x, y): sage: I == set(H.interval(2,7)) True """ - return [z for z in range(self.order())[x:y+1] if - self.is_lequal(x,z) and self.is_lequal(z,y)] + return [z for z in range(x, y+1) if + self.is_lequal(x, z) and self.is_lequal(z, y)] closed_interval = interval @@ -705,7 +709,7 @@ def cardinality(self): def mobius_function(self,i,j): # dumb algorithm r""" - Returns the value of the M\"obius function of the poset + Returns the value of the Möbius function of the poset on the elements ``i`` and ``j``. EXAMPLES:: @@ -739,10 +743,10 @@ def mobius_function(self,i,j): # dumb algorithm def mobius_function_matrix(self): r""" - Returns the matrix of the Mobius function of this poset + Returns the matrix of the Möbius function of this poset This returns the sparse matrix over `\ZZ` whose ``(x, y)`` entry - is the value of the M\"obius function of ``self`` evaluated on + is the value of the Möbius function of ``self`` evaluated on ``x`` and ``y``, and redefines :meth:`mobius_function` to use it. @@ -783,7 +787,7 @@ def mobius_function_matrix(self): # Redefine self.mobius_function def _mobius_function_from_matrix(self, i,j): r""" - Returns the value of the M\"obius function of the poset + Returns the value of the Möbius function of the poset on the elements ``i`` and ``j``. EXAMPLES:: @@ -825,9 +829,9 @@ def coxeter_transformation(self): """ return - self.lequal_matrix()*self.mobius_function_matrix().transpose() - def order_filter(self,elements): + def order_filter(self, elements): """ - Returns the order filter generated by a list of elements. + Return the order filter generated by a list of elements. `I` is an order filter if, for any `x` in `I` and `y` such that `y \ge x`, then `y` is in `I`. @@ -838,11 +842,7 @@ def order_filter(self,elements): sage: H.order_filter([3,8]) [3, 7, 8, 9, 10, 11, 12, 13, 14, 15] """ - of = [] - for i in elements: - for j in self.breadth_first_search(i): - of.append(j) - return uniq(of) + return sorted(list(self.depth_first_search(elements))) def principal_order_filter(self, i): """ @@ -856,9 +856,9 @@ def principal_order_filter(self, i): """ return self.order_filter([i]) - def order_ideal(self,elements): + def order_ideal(self, elements): """ - Returns the order ideal generated by a list of elements. + Return the order ideal generated by a list of elements. `I` is an order ideal if, for any `x` in `I` and `y` such that `y \le x`, then `y` is in `I`. @@ -869,12 +869,8 @@ def order_ideal(self,elements): sage: H.order_ideal([7,10]) [0, 1, 2, 3, 4, 5, 6, 7, 8, 10] """ - H = copy(self).reverse() - oi = [] - for i in elements: - for j in H.breadth_first_search(i): - oi.append(j) - return uniq(oi) + return sorted(list( + self.depth_first_search(elements, neighbors=self.neighbors_in))) def principal_order_ideal(self, i): """ @@ -1295,33 +1291,34 @@ def is_distributive_lattice(self): # still a dumb algorithm... def is_complemented_lattice(self): r""" - Returns ``True`` if ``self`` is the Hasse diagram of a + Return ``True`` if ``self`` is the Hasse diagram of a complemented lattice, and ``False`` otherwise. EXAMPLES:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram - sage: H = HasseDiagram({0:[1,2,3],1:[4],2:[4],3:[4]}) + sage: H = HasseDiagram({0:[1, 2, 3], 1:[4], 2:[4], 3:[4]}) sage: H.is_complemented_lattice() True - sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[4]}) + sage: H = HasseDiagram({0:[1, 2], 1:[3], 2:[3], 3:[4]}) sage: H.is_complemented_lattice() False """ + from itertools import izip try: - jn = self.join_matrix() mt = self.meet_matrix() + jn = self.join_matrix() except ValueError: return False - n = self.cardinality() - c = [-1 for x in range(n)] - for x in range(n): - for y in range(x,n): - if jn[x][y]==n-1 and mt[x][y]==0: - c[x]=y - c[y]=x - return all([c[x]!=-1 for x in range(n)]) + n = self.cardinality() - 1 + for row1, row2 in izip(mt, jn): + for c1, c2 in izip(row1, row2): + if c1 == 0 and c2 == n: + break + else: + return False + return True def complements(self): r""" @@ -1539,6 +1536,129 @@ def chains(self, element_class=list, exclude=None): self.are_comparable, element_class = element_class) + def maximal_sublattices(self): + """ + Return maximal sublattices of the lattice. + + EXAMPLES:: + + sage: L = Posets.PentagonPoset() + sage: ms = L._hasse_diagram.maximal_sublattices() + sage: sorted(ms, key=sorted) + [{0, 1, 2, 4}, {0, 1, 3, 4}, {0, 2, 3, 4}] + """ + jn = self.join_matrix() + mt = self.meet_matrix() + + def sublattice(elms, e): + """ + Helper function to get sublattice generated by list + of elements. + """ + gens_remaining = set([e]) + current_set = set(elms) + + while gens_remaining: + g = gens_remaining.pop() + if g in current_set: + continue + for x in current_set: + gens_remaining.add(jn[x, g]) + gens_remaining.add(mt[x, g]) + current_set.add(g) + + return current_set + + N = self.cardinality() + elms = [0] + sublats = [set([0])] + result = [] + skip = -1 + + while True: + # First try to append an element + found_element_to_append = False + e = elms[-1] + while e != skip: + e += 1 + if e == N: + maybe_found = sublats[-1] + if not any(maybe_found.issubset(x) for x in result): + result.append(sublats[-1]) + break + if e in sublats[-1]: + continue + # Let's try to add 'e' and see what happens. + sl = sublattice(sublats[-1], e) + if len(sl) < N: + # Skip this, if it generated a back-reference. + new_elms = sl.difference(sublats[-1]) + if not any(x < e for x in new_elms): + found_element_to_append = True + break + # Now sl is whole lattice, so we continue and try + # appending another element. + + if found_element_to_append: + elms.append(e) + sublats.append(sl) + continue + + # Can not append. Try to increment last element. + e = elms.pop() + sublats.pop() + + last_element_increment = True + while True: + e += 1 + if e == N: + last_element_increment = False + break + if e in sublats[-1]: + continue + sl = sublattice(sublats[-1], e) + if len(sl) == N: + continue + + new_elms = sl.difference(set(sublats[-1])) + if any(x < e for x in new_elms): + continue + + elms.append(e) + sublats.append(sl) + break + + if not last_element_increment: + # Can not append nor increment. "Backtracking". + skip = elms[-1] + if skip == 0: + break + + # Special case to handle at last. + if len(self.neighbors_out(0)) == 1: + result.append(set(range(1, N))) + + return result + + def frattini_sublattice(self): + """ + Return the list of elements of the Frattini sublattice of the lattice. + + EXAMPLES:: + + sage: H = Posets.PentagonPoset()._hasse_diagram + sage: H.frattini_sublattice() + [0, 4] + """ + # Just a direct computation, no optimization at all. + n = self.cardinality() + if n == 0 or n == 2: return [] + if n == 1: return [0] + max_sublats = self.maximal_sublattices() + return [e for e in range(self.cardinality()) if + all(e in ms for ms in max_sublats)] + from sage.misc.rest_index_of_methods import gen_rest_table_index import sys __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(HasseDiagram)) + diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 3546828bad4..0b985eeb448 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -24,6 +24,8 @@ :delim: | :meth:`~FiniteLatticePoset.complements` | Return the list of complements of an element, or the dictionary of complements for all elements. + :meth:`~FiniteLatticePoset.maximal_sublattices` | Return maximal sublattices of the lattice. + :meth:`~FiniteLatticePoset.frattini_sublattice` | Return the intersection of maximal sublattices. :meth:`~FiniteLatticePoset.is_atomic` | Return ``True`` if the lattice is atomic. :meth:`~FiniteLatticePoset.is_complemented` | Return ``True`` if the lattice is complemented. :meth:`~FiniteLatticePoset.is_distributive` | Return ``True`` if the lattice is distributive. @@ -31,6 +33,7 @@ :meth:`~FiniteLatticePoset.is_modular` | Return ``True`` if the lattice is lower modular. :meth:`~FiniteLatticePoset.is_modular_element` | Return ``True`` if given element is modular in the lattice. :meth:`~FiniteLatticePoset.is_upper_semimodular` | Return ``True`` if the lattice is upper semimodular. + :meth:`~FiniteLatticePoset.is_planar` | Return ``True`` if the lattice is *upward* planar, and ``False`` otherwise. :meth:`~FiniteLatticePoset.is_supersolvable` | Return ``True`` if the lattice is supersolvable. :meth:`~FiniteJoinSemilattice.join` | Return the join of given elements in the join semi-lattice. :meth:`~FiniteJoinSemilattice.join_matrix` | Return the matrix of joins of all elements of the join semi-lattice. @@ -52,6 +55,7 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** + from sage.categories.finite_lattice_posets import FiniteLatticePosets from sage.combinat.posets.posets import Poset, FinitePoset from sage.combinat.posets.elements import (LatticePosetElement, @@ -526,6 +530,105 @@ def is_complemented(self): """ return self._hasse_diagram.is_complemented_lattice() + def breadth(self, certificate=False): + r""" + Return the breadth of the lattice. + + The breadth of a lattice is the largest integer `n` such that + any join of elements `x_1, x_2, \ldots, x_{n+1}` is join of a + proper subset of `x_i`. + + INPUT: + + - ``certificate`` -- (boolean; default: ``False``) -- whether to + return an integer (the breadth) or a certificate, i.e. a biggest + set whose join differs from the join of any subset. + + EXAMPLES:: + + sage: D10 = Posets.DiamondPoset(10) + sage: D10.breadth() + 2 + + sage: B3 = Posets.BooleanLattice(3) + sage: B3.breadth() + 3 + sage: B3.breadth(certificate=True) + [1, 2, 4] + + Smallest example of a lattice with breadth 4:: + + sage: L = LatticePoset(DiGraph('O]???w?K_@S?E_??Q?@_?D??I??W?B??@??C??O?@???')) + sage: L.breadth() + 4 + + ALGORITHM: + + For a lattice to have breadth at least `n`, it must have an + `n`-element antichain `A` with join `j`. Element `j` must + cover at least `n` elements. There must also be `n-2` levels + of elements between `A` and `j`. So we start by searching + elements that could be our `j` and then just check possible + antichains `A`. + + TESTS:: + + sage: Posets.ChainPoset(0).breadth() + 0 + sage: Posets.ChainPoset(1).breadth() + 1 + """ + # A place for optimization: Adding a doubly irreducible element to + # a lattice does not change the breadth, except from 1 to 2. + # Hence we could start by removing double irreducibles. + + from sage.combinat.subsets_pairwise import PairwiseCompatibleSubsets + + # First check if breadth is zero (empty lattice) or one (a chain). + n = self.cardinality() + if n == 0: + return [] if certificate else 0 + if self.is_chain(): + return [self.bottom()] if certificate else 1 + # Breadth is at least two. + + # Work directly with the Hasse diagram + H = self._hasse_diagram + + # Helper function: Join of elements in the list L. + jn = H._join + def join(L): + j = 0 + for i in L: + j = jn[i, j] + return j + + indegs = [H.in_degree(i) for i in range(n)] + max_breadth = max(indegs) + + for B in range(max_breadth, 1, -1): + for j in H: + if indegs[j] < B: continue + + # Get elements more than B levels below it. + too_close = set(H.breadth_first_search(j, + neighbors=H.neighbors_in, + distance=B-2)) + elems = [e for e in H.order_ideal([j]) if e not in too_close] + + achains = PairwiseCompatibleSubsets(elems, + lambda x,y: H.are_incomparable(x,y)) + achains_n = achains.elements_of_depth_iterator(B) + + for A in achains_n: + if join(A) == j: + if all(join(A[:i]+A[i+1:]) != j for i in range(B)): + if certificate: + return [self._vertex_to_element(e) for e in A] + else: + return B + assert False, "BUG: breadth() in lattices.py have an error." + def complements(self, element=None): r""" Return the list of complements of an element in the lattice, @@ -637,6 +740,79 @@ def is_atomic(self): return False return True + def is_planar(self): + r""" + Return ``True`` if the lattice is *upward* planar, and ``False`` + otherwise. + + A lattice is upward planar if its Hasse diagram has a planar drawing in + the `\mathbb{R}^2` plane, in such a way that `x` is strictly below `y` + (on the vertical axis) whenever `x, +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.bindable_class import BindableClass +from sage.misc.lazy_attribute import lazy_attribute +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.algebras import Algebras +from sage.categories.realizations import Realizations, Category_realization_of_parent +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.combinat.posets.lattices import LatticePoset +from sage.combinat.free_module import CombinatorialFreeModule +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.all import ZZ + +class BasisAbstract(CombinatorialFreeModule, BindableClass): + """ + Abstract base class for a basis. + """ + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E[5] + E[5] + sage: C = L.quantum_moebius_algebra().C() + sage: C[5] + C[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + +class MoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The möbius algebra of a lattice. + + Let `L` be a lattice. The *Möbius algebra* `M_L` was originally + constructed by Solomon and has a natural basis + `\{ E_x \mid x \in L \}` with multiplication given by + `E_x \cdot E_y = E_{x \vee y}`. Moreover this has a basis given by + orthogonal idempotents `\{ I_x \mid x \in L \}` (so + `I_x I_y = \delta_{xy} I_x` where `\delta` is the Kronecker delta) + related to the natural basis by + + .. MATH:: + + I_x = \sum_{y \leq x} \mu_L(y, x) E_x, + + where `\mu_L` is the Möbius function of `L`. + + REFERENCES: + + .. [Greene73] Curtis Greene. + *On the Möbius algebra of a partially ordered set*. + Advances in Mathematics, **10**, 1973. + :doi:`10.1016/0001-8708(73)90106-0`. + + .. [Etienne98] Gwihen Etienne. + *On the Möbius algebra of geometric lattices*. + European Journal of Combinatorics, **19**, 1998. + :doi:`10.1006/eujc.1998.0227`. + """ + def __init__(self, R, L): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M).run() + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + cat = Algebras(R).Commutative().WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.moebius_algebra(QQ) + Moebius algebra of Finite lattice containing 16 elements over Rational Field + """ + return "Moebius algebra of {} over {}".format(self._lattice, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.a_realization() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `E_x E_y = E_{x \vee y}`. + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.E()).run() + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + @cached_method + def _to_idempotent_basis(self, x): + """ + Convert the element indexed by ``x`` to the idempotent basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: E = M.E() + sage: all(E(E._to_idempotent_basis(x)) == E.monomial(x) + ....: for x in E.basis().keys()) + True + """ + M = self.realization_of() + I = M.idempotent() + return I.sum_of_monomials(M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + E[10] + """ + return self.monomial(self.realization_of()._lattice.join(x, y)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.one() + E[0] + """ + elts = self.realization_of()._lattice.minimal_elements() + return self.sum_of_monomials(elts) + + natural = E + + class I(BasisAbstract): + """ + The (orthogonal) idempotent basis of a Möbius algebra. + + Let `I_x` and `I_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `I_x I_y = \delta_{xy} I_x` where + `\delta_{xy}` is the Kronecker delta. + """ + def __init__(self, M, prefix='I'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.I()).run() + """ + self._basis_name = "idempotent" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + E.module_morphism(E._to_idempotent_basis, + codomain=self, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: I = M.I() + sage: all(I(I._to_natural_basis(x)) == I.monomial(x) + ....: for x in I.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + mobius = M._lattice.mobius_function + return N.sum_of_terms((y, mobius(y,x)) for y in M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.product_on_basis(5, 14) + 0 + sage: I.product_on_basis(2, 2) + I[2] + """ + if x == y: + return self.monomial(x) + return self.zero() + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.one() + I[0] + I[1] + I[2] + I[3] + I[4] + I[5] + I[6] + I[7] + I[8] + + I[9] + I[10] + I[11] + I[12] + I[13] + I[14] + I[15] + """ + return self.sum_of_monomials(self.realization_of()._lattice) + + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I[5] + I[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + + idempotent = I + +class QuantumMoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The quantum Möbius algebra of a lattice. + + Let `L` be a lattice, and we define the *quantum Möbius algebra* `M_L(q)` + as the algebra with basis `\{ E_x \mid x \in L \}` with + multiplication given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). At `q = 1`, this + reduces to the multiplication formula originally given by Solomon. + """ + def __init__(self, L, q=None): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M).run() # long time + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + if q is None: + q = LaurentPolynomialRing(ZZ, 'q').gen() + self._q = q + R = q.parent() + cat = Algebras(R).WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.Commutative().FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.quantum_moebius_algebra() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q over Integer Ring + """ + return "Quantum Moebius algebra of {} with q={} over {}".format( + self._lattice, self._q, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.a_realization() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q + over Integer Ring in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a quantum Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.E()).run() # long time + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + q^2*E[10] + (q-q^2)*E[11] + (q-q^2)*E[14] + (1-2*q+q^2)*E[15] + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + j = L.join(x,y) + return self.sum_of_terms(( z, mobius(a,z) * q**(R - rank(a)) ) + for z in L.order_filter([j]) + for a in L.closed_interval(j, z)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: all(E.one() * b == b for b in E.basis()) + True + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + return self.sum_of_terms((x, mobius(y,x) * q**(rank(y) - R)) + for x in L for y in L.order_ideal([x])) + + natural = E + + class C(BasisAbstract): + r""" + The characteristic basis of a quantum Möbius algebra. + + The characteristic basis `\{ C_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + C_x = \sum_{a \geq x} P(F^x; q) E_a, + + where `F^x = \{ y \in L \mid y \geq x \}` is the principal order + filter of `x` and `P(F^x; q)` is the characteristic polynomial + of the (sub)poset `F^x`. + """ + def __init__(self, M, prefix='C'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(3) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.C()).run() # long time + """ + self._basis_name = "characteristic" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: C = M.C() + sage: all(C(C._to_natural_basis(x)) == C.monomial(x) + ....: for x in C.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + q = M._q + R = M.base_ring() + L = M._lattice + poly = lambda x,y: L.subposet(L.closed_interval(x, y)).characteristic_polynomial() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + # ...at which point, we can do poly(x,y)(q=q) + return N.sum_of_terms((y, subs(poly(x,y), q)) + for y in L.order_filter([x])) + + characteristic_basis = C + + class KL(BasisAbstract): + """ + The Kazhdan-Lusztig basis of a quantum Möbius algebra. + + The Kazhdan-Lusztig basis `\{ B_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + B_x = \sum_{y \geq x} P_{x,y}(q) E_a, + + where `P_{x,y}(q)` is the Kazhdan-Lusztig polynomial of `L`, + following the definition given in [EPW14]_. + + EXAMPLES: + + We construct some examples of Proposition 4.5 of [EPW14]_:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: KL[4] * KL[5] + (q^2+q^3)*KL[5] + (q+2*q^2+q^3)*KL[7] + (q+2*q^2+q^3)*KL[13] + + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[15] + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[10] + (q+3*q^2+3*q^3+q^4)*KL[14] + (1+4*q+6*q^2+4*q^3+q^4)*KL[15] + """ + def __init__(self, M, prefix='KL'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.KL()).run() # long time + """ + self._basis_name = "Kazhdan-Lusztig" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: all(KL(KL._to_natural_basis(x)) == KL.monomial(x) # long time + ....: for x in KL.basis().keys()) + True + """ + M = self.realization_of() + L = M._lattice + E = M.E() + q = M._q + R = M.base_ring() + rank = L.rank_function() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + return E.sum_of_terms((y, q**(rank(y) - rank(x)) * + subs(L.kazhdan_lusztig_polynomial(x, y), q**-2)) + for y in L.order_filter([x])) + + kazhdan_lusztig = KL + +class MoebiusAlgebraBases(Category_realization_of_parent): + r""" + The category of bases of a Möbius algebra. + + INPUT: + + - ``base`` -- a Möbius algebra + + TESTS:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: M.E() in bases + True + """ + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: MoebiusAlgebraBases(M) + Category of bases of Moebius algebra of Finite lattice + containing 16 elements over Rational Field + """ + return "Category of bases of {}".format(self.base()) + + def super_categories(self): + r""" + The super categories of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: bases.super_categories() + [Category of finite dimensional commutative algebras with basis over Rational Field, + Category of realizations of Moebius algebra of Finite lattice + containing 16 elements over Rational Field] + """ + return [self.base()._category, Realizations(self.base())] + + class ParentMethods: + def _repr_(self): + """ + Text representation of this basis of a Möbius algebra. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: M.E() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + sage: M.I() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the idempotent basis + """ + return "{} in the {} basis".format(self.realization_of(), self._basis_name) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: C.product_on_basis(5, 14) + q^3*C[15] + sage: C.product_on_basis(2, 8) + q^4*C[10] + """ + R = self.realization_of().a_realization() + return self(R(self.monomial(x)) * R(self.monomial(y))) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: all(C.one() * b == b for b in C.basis()) + True + """ + R = self.realization_of().a_realization() + return self(R.one()) + + class ElementMethods: + pass + diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 7224f4c41d7..40ffc5f98b7 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -12,7 +12,7 @@ :class:`FinitePoset` | A class for finite posets :class:`FinitePosets_n` | A class for finite posets up to isomorphism (i.e. unlabeled posets) :meth:`Poset` | Construct a finite poset from various forms of input data. - :meth:`is_poset` | Tests whether a directed graph is acyclic and transitively reduced. + :meth:`is_poset` | Return ``True`` if a directed graph is acyclic and transitively reduced. List of Poset methods --------------------- @@ -59,21 +59,22 @@ :delim: | :meth:`~FinitePoset.cardinality` | Return the number of elements in the poset. - :meth:`~FinitePoset.height` | Return the height (number of elements in the longest chain) of the poset. - :meth:`~FinitePoset.width` | Return the width of the poset (the size of its longest antichain). - :meth:`~FinitePoset.dimension` | Return the dimension of the poset. + :meth:`~FinitePoset.height` | Return the number of elements in a longest chain of the poset. + :meth:`~FinitePoset.width` | Return the number of elements in a longest antichain of the poset. :meth:`~FinitePoset.relations_number` | Return the number of relations in the poset. + :meth:`~FinitePoset.dimension` | Return the dimension of the poset. :meth:`~FinitePoset.has_bottom` | Return ``True`` if the poset has a unique minimal element. :meth:`~FinitePoset.has_top` | Return ``True`` if the poset has a unique maximal element. - :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset contains a unique maximal element and a unique minimal element, and False otherwise. - :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered, and False otherwise. - :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected, and ``False`` otherwise. - :meth:`~FinitePoset.is_graded` | Return whether this poset is graded. - :meth:`~FinitePoset.is_ranked` | Return whether this poset is ranked. - :meth:`~FinitePoset.is_incomparable_chain_free` | Return whether the poset is `(m+n)`-free. - :meth:`~FinitePoset.is_slender` | Return whether the poset ``self`` is slender or not. - :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation, and False otherwise. - :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if self has a meet operation, and False otherwise. + :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset has both unique minimal and unique maximal element. + :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered. + :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected. + :meth:`~FinitePoset.is_graded` | Return ``True`` if all maximal chains of the poset has same length. + :meth:`~FinitePoset.is_ranked` | Return ``True`` if the poset has a rank function. + :meth:`~FinitePoset.is_rank_symmetric` | Return ``True`` if the poset is rank symmetric. + :meth:`~FinitePoset.is_incomparable_chain_free` | Return ``True`` if the poset is (m+n)-free. + :meth:`~FinitePoset.is_slender` | Return ``True`` if the poset is slender. + :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation. + :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if the poset has a meet operation. **Minimal and maximal elements** @@ -113,11 +114,11 @@ :widths: 30, 70 :delim: | - :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if given iterable is a chain of the poset. - :meth:`~FinitePoset.chains` | Return all the chains of ``self``. + :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if the given list is a chain of the poset. + :meth:`~FinitePoset.chains` | Return the chains of the poset. :meth:`~FinitePoset.antichains` | Return the antichains of the poset. - :meth:`~FinitePoset.maximal_chains` | Return all maximal chains of the poset. - :meth:`~FinitePoset.maximal_antichains` | Return all maximal antichains of the poset. + :meth:`~FinitePoset.maximal_chains` | Return the maximal chains of the poset. + :meth:`~FinitePoset.maximal_antichains` | Return the maximal antichains of the poset. :meth:`~FinitePoset.antichains_iterator` | Return an iterator over the antichains of the poset. **Drawing** @@ -139,6 +140,7 @@ :delim: | :meth:`~FinitePoset.is_isomorphic` | Return ``True`` if both posets are isomorphic. + :meth:`~FinitePoset.is_induced_subposet` | Return ``True`` if given poset is an induced subposet of this poset. **Polynomials** @@ -189,6 +191,7 @@ :meth:`~FinitePoset.is_linear_extension` | Return whether ``l`` is a linear extension of ``self``. :meth:`~FinitePoset.isomorphic_subposets_iterator` | Return an iterator over the subposets isomorphic to another poset. :meth:`~FinitePoset.isomorphic_subposets` | Return all subposets isomorphic to another poset. + :meth:`~FinitePoset.kazhdan_lusztig_polynomial` | Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. :meth:`~FinitePoset.lequal_matrix` | Computes the matrix whose ``(i,j)`` entry is 1 if ``self.linear_extension()[i] < self.linear_extension()[j]`` and 0 otherwise. :meth:`~FinitePoset.level_sets` | Return a list l such that l[i+1] is the set of minimal elements of the poset obtained by removing the elements in l[0], l[1], ..., l[i]. :meth:`~FinitePoset.linear_extension` | Return a linear extension of this poset. @@ -228,6 +231,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc_c import prod +from sage.functions.other import floor from sage.categories.category import Category from sage.categories.sets_cat import Sets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -239,6 +243,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.graphs.digraph import DiGraph from sage.graphs.digraph_generators import digraphs from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -1235,9 +1240,9 @@ def hasse_diagram(self, wrapped = True): sage: P = Poset((divisors(15), attrcall("divides")), facade = False) sage: H = P.hasse_diagram() sage: H.vertices() - [1, 5, 3, 15] + [1, 3, 5, 15] sage: H.edges() - [(1, 3, None), (1, 5, None), (5, 15, None), (3, 15, None)] + [(1, 3, None), (1, 5, None), (3, 15, None), (5, 15, None)] sage: H.set_latex_options(format="dot2tex") # optional - dot2tex sage: view(H, tight_page=True) # optional - dot2tex, not tested (opens external window) """ @@ -1915,7 +1920,7 @@ def relations_number(self): Return the number of relations in the poset. A relation is a pair of elements `x` and `y` such that `x\leq y` - in ``self``. + in the poset. Relations are also often called intervals. The number of intervals is the dimension of the incidence algebra. @@ -1926,13 +1931,18 @@ def relations_number(self): EXAMPLES:: + sage: P = Posets.PentagonPoset() + sage: P.relations_number() + 13 + sage: from sage.combinat.tamari_lattices import TamariLattice sage: TamariLattice(4).relations_number() 68 - sage: P = posets.BooleanLattice(3) - sage: P.relations_number() - 27 + TESTS:: + + sage: Poset().relations_number() + 0 """ return sum(1 for x in self.relations_iterator()) @@ -1941,53 +1951,60 @@ def relations_number(self): intervals_number = relations_number intervals_iterator = relations_iterator - def is_incomparable_chain_free(self, m, n = None): + def is_incomparable_chain_free(self, m, n=None): r""" - Return ``True`` if the poset is `(m+n)`-free (that is, there is no pair - of incomparable chains of lengths `m` and `n`), and ``False`` if not. + Return ``True`` if the poset is `(m+n)`-free, and ``False`` otherwise. + + A poset is `(m+n)`-free if there is no incomparable chains of + lengths `m` and `n`. Three cases have special name: - If ``m`` is a tuple of pairs of chain lengths, returns ``True`` if the poset - does not contain a pair of incomparable chains whose lengths comprise - one of the chain pairs, and ``False`` if not. - A poset is `(m+n)`-free if it contains no induced subposet that is - isomorphic to the poset consisting of two disjoint chains of lengths - `m` and `n`. See, for example, Exercise 15 in Chapter 3 of - [EnumComb1]_. + - ''interval order'' is `(2+2)`-free + - ''semiorder'' (or ''unit interval order'') is `(1+3)`-free and + `(2+2)`-free + - ''weak order'' is `(1+2)`-free. INPUT: - - ``m`` - tuple of pairs of nonnegative integers - - ``m``, ``n`` - nonnegative integers + - ``m``, ``n`` - positive integers + + It is also possible to give a list of integer pairs as argument. + See below for an example. EXAMPLES:: - sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: P.is_incomparable_chain_free(1, 1) - False - sage: P.is_incomparable_chain_free(2, 1) + sage: B3 = Posets.BooleanLattice(3) + sage: B3.is_incomparable_chain_free(1, 3) True + sage: B3.is_incomparable_chain_free(2, 2) + False - :: - - sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) - sage: P.is_incomparable_chain_free(((3, 1), (2, 2))) + sage: IP6 = Posets.IntegerPartitions(6) + sage: IP6.is_incomparable_chain_free(1, 3) + False + sage: IP6.is_incomparable_chain_free(2, 2) True - :: + A list of pairs as an argument:: - sage: P = Poset((("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"), (("d", "a"), ("e", "a"), ("f", "a"), ("g", "a"), ("h", "b"), ("f", "b"), ("h", "c"), ("g", "c"), ("h", "d"), ("i", "d"), ("h", "e"), ("i", "e"), ("j", "f"), ("i", "f"), ("j", "g"), ("i", "g"), ("j", "h")))) - sage: P.is_incomparable_chain_free(3, 1) - True - sage: P.is_incomparable_chain_free(2, 2) + sage: B3.is_incomparable_chain_free([[1, 3], [2, 2]]) False - :: + We show how to get an incomparable chain pair:: - sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time - [1, 1, 2, 5, 14, 42] + sage: P = Posets.PentagonPoset() + sage: chains_1_2 = Poset({0:[], 1:[2]}) + sage: incomps = P.isomorphic_subposets(chains_1_2)[0] + sage: sorted(incomps.list()), incomps.cover_relations() + ([1, 2, 3], [[2, 3]]) TESTS:: + sage: Poset().is_incomparable_chain_free(1,1) # Test empty poset + True + + sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time + [1, 1, 2, 5, 14, 42] + sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: Q.is_incomparable_chain_free(2, 20/10) True @@ -1998,7 +2015,7 @@ def is_incomparable_chain_free(self, m, n = None): sage: Q.is_incomparable_chain_free(2, -1) Traceback (most recent call last): ... - ValueError: 2 and -1 must be nonnegative integers. + ValueError: 2 and -1 must be positive integers. sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) sage: P.is_incomparable_chain_free((3, 1)) Traceback (most recent call last): @@ -2045,8 +2062,8 @@ def is_incomparable_chain_free(self, m, n = None): m, n = Integer(m), Integer(n) except TypeError: raise TypeError('%s and %s must be integers.' % (m, n)) - if m < 0 or n < 0: - raise ValueError("%s and %s must be nonnegative integers." % (m, n)) + if m < 1 or n < 1: + raise ValueError("%s and %s must be positive integers." % (m, n)) twochains = digraphs.TransitiveTournament(m) + digraphs.TransitiveTournament(n) return self._hasse_diagram.transitive_closure().subgraph_search(twochains, induced = True) is None @@ -2235,7 +2252,7 @@ def bottom(self): sage: Q.bottom() 0 - .. SEEALSO:: :meth:`has_bottom`, :meth:`top`. + .. SEEALSO:: :meth:`has_bottom`, :meth:`top` """ hasse_bot = self._hasse_diagram.bottom() if hasse_bot is None: @@ -2250,14 +2267,19 @@ def has_bottom(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: P.has_bottom() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[1], 1:[]}) sage: Q.has_bottom() True - .. SEEALSO:: :meth:`bottom`, :meth:`has_top`. + .. SEEALSO:: :meth:`bottom`, :meth:`has_top`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_bottom() @@ -2274,7 +2296,7 @@ def top(self): sage: Q.top() 1 - .. SEEALSO:: :meth:`has_top`, :meth:`bottom`. + .. SEEALSO:: :meth:`has_top`, :meth:`bottom` TESTS:: @@ -2297,20 +2319,25 @@ def has_top(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.has_top() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: Q.has_top() True - .. SEEALSO:: :meth:`top`, :meth:`has_bottom`. + .. SEEALSO:: :meth:`top`, :meth:`has_bottom`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_top() def height(self): """ - Return the height (number of elements in the longest chain) of the poset. + Return the height (number of elements in a longest chain) of the poset. EXAMPLES:: @@ -2319,7 +2346,10 @@ def height(self): 3 sage: Posets.PentagonPoset().height() 4 - sage: Poset({}).height() + + TESTS:: + + sage: Poset().height() 0 """ return self.rank()+1 @@ -2359,127 +2389,143 @@ def has_isomorphic_subposet(self, other): def is_bounded(self): """ - Return ``True`` if the poset ``self`` is bounded, and ``False`` - otherwise. + Return ``True`` if the poset is bounded, and ``False`` otherwise. - We call a poset bounded if it contains a unique maximal element + A poset is bounded if it contains both a unique maximal element and a unique minimal element. EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.is_bounded() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Posets.DiamondPoset(5) sage: Q.is_bounded() True + + .. SEEALSO:: :meth:`has_bottom`, :meth:`has_top` + + TESTS:: + + sage: Poset().is_bounded() # Test empty poset + False + sage: Poset({1:[]}).is_bounded() # Here top == bottom + True + sage: ( len([P for P in Posets(5) if P.is_bounded()]) == + ....: Posets(3).cardinality() ) + True """ return self._hasse_diagram.is_bounded() def is_chain(self): """ - Returns True if the poset is totally ordered, and False otherwise. + Return ``True`` if the poset is totally ordered ("chain"), and + ``False`` otherwise. EXAMPLES:: - sage: L = Poset({0:[1],1:[2],2:[3],3:[4]}) - sage: L.is_chain() + sage: I = Poset({0:[1], 1:[2], 2:[3], 3:[4]}) + sage: I.is_chain() True - sage: V = Poset({0:[1,2]}) + + sage: II = Poset({0:[1], 2:[3]}) + sage: II.is_chain() + False + + sage: V = Poset({0:[1, 2]}) sage: V.is_chain() False + + TESTS:: + + sage: [len([P for P in Posets(n) if P.is_chain()]) for n in range(5)] + [1, 1, 1, 1, 1] """ return self._hasse_diagram.is_chain() - def is_chain_of_poset(self, o, ordered=False): + def is_chain_of_poset(self, elms, ordered=False): """ - Return whether an iterable ``o`` is a chain of ``self``, - including a check for ``o`` being ordered from smallest - to largest element if the keyword ``ordered`` is set to - ``True``. + Return ``True`` if `elms` is a chain of the poset, and ``False`` otherwise. INPUT: - - ``o`` -- an iterable (e. g., list, set, or tuple) - containing some elements of ``self`` - - - ``ordered`` -- a Boolean (default: ``False``) which - decides whether the notion of a chain includes being - ordered - - OUTPUT: - - If ``ordered`` is set to ``False``, the truth value of - the following assertion is returned: The subset of ``self`` - formed by the elements of ``o`` is a chain in ``self``. + - ``elms`` -- a list or other iterable containing some elements + of the poset - If ``ordered`` is set to ``True``, the truth value of - the following assertion is returned: Every element of the - list ``o`` is (strictly!) smaller than its successor in - ``self``. (This makes no sense if ``ordered`` is a set.) + - ``ordered`` -- a Boolean. If ``True``, then return ``True`` + only if elements in `elms` are strictly increasing in the + poset; this makes no sense if `elms` is a set. If ``False`` + (the default), then elements can be repeated and be in any + order. EXAMPLES:: sage: P = Poset((divisors(12), attrcall("divides"))) sage: sorted(P.list()) [1, 2, 3, 4, 6, 12] - sage: P.is_chain_of_poset([2, 4]) - True - sage: P.is_chain_of_poset([12, 6]) + sage: P.is_chain_of_poset([12, 3]) True - sage: P.is_chain_of_poset([12, 6], ordered=True) + sage: P.is_chain_of_poset({3, 4, 12}) False - sage: P.is_chain_of_poset([6, 12], ordered=True) - True - sage: P.is_chain_of_poset(()) - True - sage: P.is_chain_of_poset((), ordered=True) - True - sage: P.is_chain_of_poset((3, 4, 12)) + sage: P.is_chain_of_poset([12, 3], ordered=True) False - sage: P.is_chain_of_poset((3, 6, 12, 1)) - True - sage: P.is_chain_of_poset((3, 6, 12, 1), ordered=True) - False - sage: P.is_chain_of_poset((3, 6, 12), ordered=True) - True sage: P.is_chain_of_poset((1, 1, 3)) True sage: P.is_chain_of_poset((1, 1, 3), ordered=True) False sage: P.is_chain_of_poset((1, 3), ordered=True) True - sage: P.is_chain_of_poset((6, 1, 1, 3)) + + TESTS:: + + sage: P = Posets.BooleanLattice(4) + sage: P.is_chain_of_poset([]) True - sage: P.is_chain_of_poset((2, 1, 1, 3)) + sage: P.is_chain_of_poset((1,3,7,15,14)) False + sage: P.is_chain_of_poset({10}) + True + sage: P.is_chain_of_poset([32]) + Traceback (most recent call last): + ... + ValueError: element (=32) not in poset """ if ordered: - sorted_o = o + sorted_o = elms return all(self.lt(a, b) for a, b in zip(sorted_o, sorted_o[1:])) else: # _element_to_vertex can be assumed to be a linear extension # of the poset according to the documentation of class # HasseDiagram. - sorted_o = sorted(o, key=self._element_to_vertex) + sorted_o = sorted(elms, key=self._element_to_vertex) return all(self.le(a, b) for a, b in zip(sorted_o, sorted_o[1:])) def is_connected(self): """ Return ``True`` if the poset is connected, and ``False`` otherwise. - Poset is not connected if it can be divided to disjoint parts + A poset is connected if it's Hasse diagram is connected. + + If a poset is not connected, then it can be divided to parts `S_1` and `S_2` so that every element of `S_1` is incomparable to every element of `S_2`. EXAMPLES:: - sage: P=Poset({1:[2,3], 3:[4,5]}) + sage: P = Poset({1:[2, 3], 3:[4, 5]}) sage: P.is_connected() True - sage: P=Poset({1:[2,3], 3:[4,5], 6:[7,8]}) + + sage: P = Poset({1:[2, 3], 3:[4, 5], 6:[7, 8]}) sage: P.is_connected() False + + .. SEEALSO:: :meth:`connected_components` + + TESTS:: + + sage: Poset().is_connected() # Test empty poset + True """ return self._hasse_diagram.is_connected() @@ -2548,8 +2594,11 @@ def dimension(self, certificate=False): r""" Return the dimension of the Poset. - The (Dushnik-Miller) dimension of a Poset defined on a set `X` of points - is the smallest integer `n` such that there exists `P_1,...,P_n` linear + The (Dushnik-Miller) dimension of a poset is the minimal + number of total orders so that the poset can be defined as + "intersection" of all of them. Mathematically said, dimension + of a poset defined on a set `X` of points is the smallest + integer `n` such that there exists `P_1,...,P_n` linear extensions of `P` satisfying the following property: .. MATH:: @@ -2566,11 +2615,11 @@ def dimension(self, certificate=False): .. NOTE:: - The speed of this function greatly improves when more efficient MILP - solvers (e.g. Gurobi, CPLEX) are installed. See + The speed of this function greatly improves when more efficient + MILP solvers (e.g. Gurobi, CPLEX) are installed. See :class:`MixedIntegerLinearProgram` for more information. - **Algorithm:** + ALGORITHM: As explained [FT00]_, the dimension of a poset is equal to the (weak) chromatic number of a hypergraph. More precisely: @@ -2593,19 +2642,17 @@ def dimension(self, certificate=False): EXAMPLES: - According to Wikipedia, the poset (of height 2) of a graph is `\leq 2` - if and only if the graph is a path:: + We create a poset, compute a set of linear extensions and check + that we get back the poset from them:: - sage: G = graphs.PathGraph(6) - sage: P = Poset(DiGraph({(u,v):[u,v] for u,v,_ in G.edges()})) + sage: P = Poset([[1,4], [3], [4,5,3], [6], [], [6], []]) sage: P.dimension() - 2 - - The actual linear extensions can be obtained with ``certificate=True``:: - - sage: P.dimension(certificates=True) # not tested -- architecture-dependent - [[(0, 1), 0, (1, 2), 1, (2, 3), 2, (3, 4), 3, (4, 5), 4, 5], - [(4, 5), 5, (3, 4), 4, (2, 3), 3, (1, 2), 2, (0, 1), 1, 0]] + 3 + sage: L = P.dimension(certificate=True) + sage: L # random -- architecture-dependent + [[0, 2, 4, 5, 1, 3, 6], [2, 5, 0, 1, 3, 4, 6], [0, 1, 2, 3, 5, 6, 4]] + sage: Poset( (L[0], lambda x, y: all(l.index(x) < l.index(y) for l in L)) ) == P + True According to Schnyder's theorem, the poset (of height 2) of a graph has dimension `\leq 3` if and only if the graph is planar:: @@ -2626,7 +2673,7 @@ def dimension(self, certificate=False): sage: Poset().dimension() 0 - sage: Poset().dimension(certificate=1) + sage: Poset().dimension(certificate=True) [] References: @@ -2811,80 +2858,72 @@ def rank(self, element=None): def is_ranked(self): r""" - Returns whether this poset is ranked. + Return ``True`` if the poset is ranked, and ``False`` otherwise. - A poset is *ranked* if it admits a rank function. For more information - about the rank function, see :meth:`~sage.combinat.posets.hasse_diagram.HasseDiagram.rank_function`. + A poset is ranked if there is a function `r` from poset elements + to integers so that `r(x)=r(y)+1` when `x` covers `y`. - .. SEEALSO:: :meth:`is_graded`. + Informally said a ranked poset can be "levelized": every element is + on a "level", and every cover relation goes only one level up. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Poset( ([1, 2, 3, 4], [[1, 2], [2, 4], [3, 4]] )) sage: P.is_ranked() True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_ranked() - False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset([[1, 5], [2, 6], [3], [4],[], [6, 3], [4]]) sage: P.is_ranked() + False + + .. SEEALSO:: :meth:`rank_function`, :meth:`rank`, :meth:`is_graded` + + TESTS:: + + sage: Poset().is_ranked() # Test empty poset True """ return bool(self.rank_function()) def is_graded(self): r""" - Returns whether this poset is graded. + Return ``True`` if the poset is graded, and ``False`` otherwise. - A poset is *graded* if all its maximal chains have the same length. - There are various competing definitions for graded posets (see - :wikipedia:`Graded_poset`). This definition is from section 3.1 of - Richard Stanley's *Enumerative Combinatorics, Vol. 1* [EnumComb1]_. + A poset is graded if all its maximal chains have the same length. - Note that every graded poset is ranked, but the converse is not - true. + There are various competing definitions for graded + posets (see :wikipedia:`Graded_poset`). This definition is from + section 3.1 of Richard Stanley's *Enumerative Combinatorics, + Vol. 1* [EnumComb1]_. - .. SEEALSO:: :meth:`is_ranked` + Every graded poset is ranked. The converse is true + for bounded posets, including lattices. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Posets.PentagonPoset() # Not even ranked sage: P.is_graded() - True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_graded() False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset({1:[2, 3], 3:[4]}) # Ranked, but not graded sage: P.is_graded() False - sage: P = Poset({1: [2, 3], 4: [5]}) + + sage: P = Poset({1:[3, 4], 2:[3, 4], 5:[6]}) sage: P.is_graded() True - sage: P = Poset({1: [2, 3], 3: [4]}) - sage: P.is_graded() - False - sage: P = Poset({1: [2, 3], 4: []}) + + sage: P = Poset([[1], [2], [], [4], []]) sage: P.is_graded() False - sage: P = Posets.BooleanLattice(4) - sage: P.is_graded() - True - sage: P = RootSystem(['D',4]).root_poset() - sage: P.is_graded() - True - sage: P = Poset({}) - sage: P.is_graded() - True - TESTS: + .. SEEALSO:: :meth:`is_ranked`, :meth:`level_sets` - Here we test that the empty poset is graded:: + TESTS:: - sage: Poset([[],[]]).is_graded() + sage: Poset().is_graded() # Test empty poset True """ - # The code below is not really optimized, but beats looking at - # every maximal chain... if len(self) <= 2: return True # Let's work with the Hasse diagram in order to avoid some @@ -2985,7 +3024,7 @@ def lower_covers(self, x): def cardinality(self): """ - Returns the number of elements in the poset. + Return the number of elements in the poset. EXAMPLES:: @@ -3153,21 +3192,29 @@ def meet_matrix(self): def is_meet_semilattice(self): r""" - Returns True if self has a meet operation, and False otherwise. + Return ``True`` if the poset has a meet operation, and + ``False`` otherwise. - EXAMPLES:: + A meet is the greatest lower bound for given elements, if it exists. - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]], facade = False) - sage: P.is_meet_semilattice() - True + EXAMPLES:: - sage: P = Poset([[1,2],[3],[3],[]]) + sage: P = Poset({1:[2, 3, 4], 2:[5, 6], 3:[6], 4:[6, 7]}) sage: P.is_meet_semilattice() True - sage: P = Poset({0:[2,3],1:[2,3]}) - sage: P.is_meet_semilattice() + sage: Q = P.dual() + sage: Q.is_meet_semilattice() False + + .. SEEALSO:: :meth:`is_join_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_meet_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_meet_semilattice()]) + 5 """ return self._hasse_diagram.is_meet_semilattice() @@ -3184,22 +3231,32 @@ def join_matrix(self): def is_join_semilattice(self): """ - Returns True if the poset has a join operation, and False + Return ``True`` if the poset has a join operation, and ``False`` otherwise. + A join is the least upper bound for given elements, if it exists. + EXAMPLES:: - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) + sage: P = Poset([[1,3,2], [4], [4,5,6], [6], [7], [7], [7], []]) sage: P.is_join_semilattice() True - sage: P = Poset([[1,2],[3],[3],[]]) - sage: P.is_join_semilattice() - True + Elements 3 and 4 have no common upper bound at all; elements + 1 and 2 have upper bounds 3 and 4, but no least upper bound:: - sage: P = Poset({0:[2,3],1:[2,3]}) + sage: P = Poset({1:[3, 4], 2:[3, 4]}) sage: P.is_join_semilattice() False + + .. SEEALSO:: :meth:`is_meet_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_join_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_join_semilattice()]) + 5 """ return self._hasse_diagram.is_join_semilattice() @@ -3299,17 +3356,22 @@ def isomorphic_subposets(self, other): def antichains(self, element_constructor = __builtin__.list): """ - Returns the antichains of the poset. + Return the antichains of the poset. + + An *antichain* of a poset is a set of elements of the + poset that are pairwise incomparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: list) + argument (default: ``list``) - OUTPUT: an enumerated set + OUTPUT: - An *antichain* of a poset is a collection of elements of the - poset that are pairwise incomparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all antichains of the poset, each of which is given as an + ``element_constructor.`` EXAMPLES:: @@ -3321,10 +3383,12 @@ def antichains(self, element_constructor = __builtin__.list): 8 sage: A[3] [1, 2] - sage: list(Posets.AntichainPoset(3).antichains()) - [[], [2], [2, 1], [2, 1, 0], [2, 0], [1], [1, 0], [0]] - sage: list(Posets.ChainPoset(3).antichains()) - [[], [0], [1], [2]] + + To get the antichains as, say, sets, one may use the + ``element_constructor`` option:: + + sage: list(Posets.ChainPoset(3).antichains(element_constructor=set)) + [set(), {0}, {1}, {2}] To get the antichains of a given size one can currently use:: @@ -3335,12 +3399,6 @@ def antichains(self, element_constructor = __builtin__.list): sage: A.subset(size = 2) # todo: not implemented - To get the antichains as, say, sets, one may use the - ``element_constructor`` option:: - - sage: list(Posets.ChainPoset(3).antichains(element_constructor = set)) - [set(), {0}, {1}, {2}] - .. NOTE:: Internally, this uses @@ -3356,7 +3414,7 @@ def antichains(self, element_constructor = __builtin__.list): On the other hand, this returns a full featured enumerated set, with containment testing, etc. - .. seealso:: :meth:`maximal_antichains` + .. seealso:: :meth:`maximal_antichains`, :meth:`chains` """ vertex_to_element = self._vertex_to_element @@ -3368,12 +3426,14 @@ def f(antichain): def antichains_iterator(self): """ - Returns an iterator over the antichains of the poset. + Return an iterator over the antichains of the poset. EXAMPLES:: - sage: Posets.PentagonPoset().antichains_iterator() + sage: it = Posets.PentagonPoset().antichains_iterator(); it + sage: it.next(), it.next() + ([], [4]) .. SEEALSO:: :meth:`antichains` """ @@ -3385,19 +3445,20 @@ def width(self): r""" Return the width of the poset (the size of its longest antichain). - It is computed through a matching in a bipartite graph. See - :wikipedia:`Dilworth's_theorem` for more information. + It is computed through a matching in a bipartite graph; see + :wikipedia:`Dilworth's_theorem` for more information. The width is + also called Dilworth number. - .. SEEALSO:: + EXAMPLES:: - :meth:`dilworth_decomposition` -- return a partition of the poset - into the smallest number of chains. + sage: P = posets.BooleanLattice(4) + sage: P.width() + 6 - EXAMPLE:: + TESTS:: - sage: p = posets.BooleanLattice(4) - sage: p.width() - 6 + sage: Poset().width() + 0 """ # See the doc of dilworth_decomposition for an explanation of what is # going on. @@ -3434,9 +3495,9 @@ def dilworth_decomposition(self): union of chains. According to Dilworth's theorem, the number of chains is equal to - `\alpha` (the posets' width). + `\alpha` (the posets' width). - EXAMPLE:: + EXAMPLES:: sage: p = posets.BooleanLattice(4) sage: p.width() @@ -3479,51 +3540,47 @@ def dilworth_decomposition(self): def chains(self, element_constructor=__builtin__.list, exclude=None): """ - Return all the chains of ``self``. + Return the chains of the poset. + + A *chain* of a poset is a set of elements of the poset + that are pairwise comparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: ``list``) + argument (default: ``list``) - ``exclude`` -- elements of the poset to be excluded (default: ``None``) OUTPUT: - The enumerated set of all chains of ``self``, each of which - is given as an ``element_constructor``. - - A *chain* of a poset is a set of elements of the poset - that are pairwise comparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all chains of the poset, each of which is given as an + ``element_constructor``. EXAMPLES:: - sage: A = Posets.PentagonPoset().chains(); A + sage: C = Posets.PentagonPoset().chains(); C Set of chains of Finite lattice containing 5 elements - sage: list(A) + sage: list(C) [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4], [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]] - To get the chains of a given size one can currently use:: - - sage: list(A.elements_of_depth_iterator(2)) - [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] - - For bounded posets, one can exclude the bounds as follows:: - - sage: P = Posets.DiamondPoset(5) - sage: list(P.chains(exclude=[0, 4])) - [[], [1], [2], [3]] - - Another example of exclusion of vertices:: + Exclusion of vertices, tuple (instead of list) as constructor:: sage: P = Poset({1: [2, 3], 2: [4], 3: [4, 5]}) sage: list(P.chains(element_constructor=tuple, exclude=[3])) [(), (1,), (1, 2), (1, 2, 4), (1, 4), (1, 5), (2,), (2, 4), (4,), (5,)] + To get the chains of a given size one can currently use:: + + sage: list(C.elements_of_depth_iterator(2)) + [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] + Eventually the following syntax will be accepted:: - sage: A.subset(size = 2) # todo: not implemented + sage: C.subset(size = 2) # todo: not implemented .. SEEALSO:: :meth:`maximal_chains`, :meth:`antichains` """ @@ -3540,23 +3597,28 @@ def f(chain): def connected_components(self): """ - Return the connected components of ``self`` as subposets. + Return the connected components of the poset as subposets. EXAMPLES:: sage: P = Poset({1:[2,3], 3:[4,5]}) sage: CC = P.connected_components() - sage: CC is P + sage: CC[0] is P True sage: P = Poset({1:[2,3], 3:[4,5], 6:[7,8]}) sage: sorted(P.connected_components(), key=len) [Finite poset containing 3 elements, Finite poset containing 5 elements] + + TESTS:: + + sage: Poset().connected_components() # Test empty poset + [] """ comps = self._hasse_diagram.connected_components() if len(comps) == 1: - return self + return [self] return [self.subposet(self._vertex_to_element(x) for x in cc) for cc in comps] @@ -3861,10 +3923,10 @@ def relabel(self, relabeling): INPUT: - - ``relabeling`` -- a function or dictionnary + - ``relabeling`` -- a function or dictionary - This function should map each (non-wrapped) element of - ``self`` to some distinct object. + This function should map each (non-wrapped) element of + ``self`` to some distinct object. EXAMPLES:: @@ -4284,8 +4346,9 @@ def random_order_ideal(self, direction='down'): if direction == 'antichain': return [self._vertex_to_element(i) for i,x in enumerate(state) if x == 0 and all(state[j] == 1 for j in hd.upper_covers_iterator(i))] - else: # direction is assumed to be 'down' - return [self._vertex_to_element(i) for i,x in enumerate(state) if x == 0] + if direction != 'down': + raise ValueError("direction must be 'up', 'down' or 'antichain'") + return [self._vertex_to_element(i) for i,x in enumerate(state) if x == 0] def order_filter(self,elements): """ @@ -4440,9 +4503,57 @@ def incomparability_graph(self): G.rename('Incomparability graph on %s vertices' % self.cardinality()) return G + def linear_extensions_graph(self): + r""" + Return the linear extensions graph of the poset. + + Vertices of the graph are linear extensions of the poset. + Two vertices are connected by an edge if the linear extensions + differ by only one adjacent transposition. + + EXAMPLES:: + + sage: P = Poset({1:[3,4],2:[4]}) + sage: G = P.linear_extensions_graph(); G + Graph on 5 vertices + sage: G.degree_sequence() + [3, 2, 2, 2, 1] + + sage: chevron = Poset({1:[2,6], 2:[3], 4:[3,5], 6:[5]}) + sage: G = chevron.linear_extensions_graph(); G + Graph on 22 vertices + sage: G.size() + 36 + + TESTS:: + + sage: Poset().linear_extensions_graph() + Graph on 1 vertex + + sage: A4 = Posets.AntichainPoset(4) + sage: G = A4.linear_extensions_graph() + sage: G.is_regular() + True + """ + from sage.graphs.graph import Graph + # Direct implementation, no optimizations + L = self.linear_extensions() + G = Graph() + G.add_vertices(L) + for i in range(len(L)): + for j in range(i): + tmp = map(lambda x,y: x != y, L[i], L[j]) + if tmp.count(True) == 2 and tmp[tmp.index(True)+1]: + G.add_edge(L[i], L[j]) + return G + def maximal_antichains(self): """ - Return all maximal antichains of the poset. + Return the maximal antichains of the poset. + + An antichain `a` of poset `P` is *maximal* if there is + no element `e \in P \setminus a` such that `a \cup \{e\}` + is an antichain. EXAMPLES:: @@ -4453,7 +4564,7 @@ def maximal_antichains(self): sage: Posets.PentagonPoset().maximal_antichains() [[0], [1, 2], [1, 3], [4]] - .. seealso:: :meth:`maximal_chains`, :meth:`antichains` + .. seealso:: :meth:`antichains`, :meth:`maximal_chains` """ # Maximal antichains are maximum cliques on incomparability graph. return self.incomparability_graph().cliques_maximal() @@ -5227,6 +5338,42 @@ def evacuation(self): """ return self.linear_extension().evacuation().to_poset() + def is_rank_symmetric(self): + """ + Return ``True`` if the poset is rank symmetric, and ``False`` + otherwise. + + The poset is expected to be graded and connected. + + A poset of rank `h` (maximal chains have `h+1` elements) is rank + symmetric if the number of elements are equal in ranks `i` and + `h-i` for every `i` in `0, 1, \ldots, h`. + + EXAMPLES:: + + sage: P = Poset({1:[3, 4, 5], 2:[3, 4, 5], 3:[6], 4:[7], 5:[7]}) + sage: P.is_rank_symmetric() + True + sage: P = Poset({1:[2], 2:[3, 4], 3:[5], 4:[5]}) + sage: P.is_rank_symmetric() + False + + TESTS:: + + sage: Poset().is_rank_symmetric() # Test empty poset + True + """ + if not self.is_connected(): + raise TypeError('the poset is not connected') + if not self.is_graded(): + raise TypeError('the poset is not graded') + levels = self._hasse_diagram.level_sets() + h = len(levels) + for i in range(h/2): + if len(levels[i]) != len(levels[h-1-i]): + return False + return True + def is_slender(self): r""" Return ``True`` if the poset is slender, and ``False`` otherwise. @@ -5246,21 +5393,26 @@ def is_slender(self): EXAMPLES:: - sage: P = Poset(([1,2,3,4],[[1,2],[1,3],[2,4],[3,4]]), facade = True) + sage: P = Poset(([1, 2, 3, 4], [[1, 2], [1, 3], [2, 4], [3, 4]])) sage: P.is_slender() True - sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]]), facade = True) + sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]])) sage: P.is_slender() False - sage: W = WeylGroup(['A',2]) + sage: W = WeylGroup(['A', 2]) sage: G = W.bruhat_poset() sage: G.is_slender() True - sage: W = WeylGroup(['A',3]) + sage: W = WeylGroup(['A', 3]) sage: G = W.bruhat_poset() sage: G.is_slender() True + + TESTS:: + + sage: Poset().is_slender() # Test empty poset + True """ for x in self: d = {} @@ -5601,6 +5753,195 @@ def incidence_algebra(self, R, prefix='I'): from sage.combinat.posets.incidence_algebras import IncidenceAlgebra return IncidenceAlgebra(R, self, prefix) + @cached_method(key=lambda self,x,y,l: (x,y)) + def _kl_poly(self, x=None, y=None, canonical_labels=None): + r""" + Cached Kazhdan-Lusztig polynomial of ``self`` for generic `q`. + + .. SEEALSO:: + + :meth:`kazhdan_lusztig_polynomial` + + EXAMPLES:: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L._kl_poly() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L._kl_poly(x, y) + -q + 1 + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + R = PolynomialRing(ZZ, 'q') + q = R.gen(0) + + # Handle some special cases + if self.cardinality() == 0: + return q.parent().zero() + if not self.rank(): + return q.parent().one() + + if canonical_labels is None: + canonical_labels = x is None and y is None + + if x is not None or y is not None: + if x == y: + return q.parent().one() + if x is None: + x = self.minimal_elements()[0] + if y is None: + y = self.maximal_elements()[0] + if not self.le(x, y): + return q.parent().zero() + P = self.subposet(self.interval(x, y)) + return P.kazhdan_lusztig_polynomial(q=q, canonical_labels=canonical_labels) + + min_elt = self.minimal_elements()[0] + if canonical_labels: + sublat = lambda P: self.subposet(P).canonical_label() + else: + sublat = lambda P: self.subposet(P) + poly = -sum(sublat(self.order_ideal([x])).characteristic_polynomial() + * sublat(self.order_filter([x])).kazhdan_lusztig_polynomial() + for x in self if x != min_elt) + tr = floor(self.rank()/2) + 1 + ret = poly.truncate(tr) + return ret(q=q) + + def kazhdan_lusztig_polynomial(self, x=None, y=None, q=None, canonical_labels=None): + r""" + Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. + + We follow the definition given in [EPW14]_. Let `G` denote a + graded poset with unique minimal and maximal elements and `\chi_G` + denote the characteristic polynomial of `G`. Let `I_x` and `F^x` + denote the principal order ideal and filter of `x` respectively. + Define the *Kazhdan-Lusztig polynomial* of `G` as the unique + polynomial `P_G(q)` satisfying the following: + + 1. If `\operatorname{rank} G = 0`, then `P_G(q) = 1`. + 2. If `\operatorname{rank} G > 0`, then `\deg P_G(q) < + \frac{1}{2} \operatorname{rank} G`. + 3. We have + + .. MATH:: + + q^{\operatorname{rank} G} P_G(q^{-1}) + = \sum_{x \in G} \chi_{I_x}(q) P_{F^x}(q). + + We then extend this to `P_{x,y}(q)` by considering the subposet + corresponding to the (closed) interval `[x, y]`. We also + define `P_{\emptyset}(q) = 0` (so if `x \not\leq y`, + then `P_{x,y}(q) = 0`). + + INPUT: + + - ``q`` -- (default: `q \in \ZZ[q]`) the indeterminate `q` + - ``x`` -- (default: the minimal element) the element `x` + - ``y`` -- (default: the maximal element) the element `y` + - ``canonical_labels`` -- (optional) for subposets, use the + canonical labeling (this can limit recursive calls for posets + with large amounts of symmetry, but producing the labeling + takes time); if not specified, this is ``True`` if ``x`` + and ``y`` are both not specified and ``False`` otherwise + + EXAMPLES:: + + sage: L = posets.BooleanLattice(3) + sage: L.kazhdan_lusztig_polynomial() + 1 + + :: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L.kazhdan_lusztig_polynomial() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L.kazhdan_lusztig_polynomial(x, y) + -q + 1 + sage: L.kazhdan_lusztig_polynomial(x, y, var('t')) + -t + 1 + + REFERENCES: + + .. [EPW14] Ben Elias, Nicholas Proudfoot, and Max Wakefield. + *The Kazhdan-Lusztig polynomial of a matroid*. 2014. + :arxiv:`1412.7408`. + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + if not self.is_ranked(): + raise ValueError("poset is not ranked") + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + poly = self._kl_poly(x, y, canonical_labels) + return poly(q=q) + + def is_induced_subposet(self, other): + r""" + Return ``True`` if the poset is an induced subposet of ``other``, and + ``False`` otherwise. + + A poset `P` is an induced subposet of `Q` if every element + of `P` is an element of `Q`, and `x \le_P y` iff `x \le_Q y`. + Note that "induced" here has somewhat different meaning compared + to that of graphs. + + INPUT: + + - ``other``, a poset. + + .. NOTE:: + + This method does not check whether the poset is a + *isomorphic* (i.e., up to relabeling) subposet of ``other``, + but only if ``other`` directly contains the poset as an + induced subposet. For isomorphic subposets see + :meth:`has_isomorphic_subposet`. + + EXAMPLES:: + + sage: P = Poset({1:[2, 3]}) + sage: Q = Poset({1:[2, 4], 2:[3]}) + sage: P.is_induced_subposet(Q) + False + sage: R = Poset({0:[1], 1:[3, 4], 3:[5], 4:[2]}) + sage: P.is_induced_subposet(R) + True + + TESTS:: + + sage: P = Poset({2:[1]}) + sage: Poset().is_induced_subposet(P) + True + sage: Poset().is_induced_subposet(Poset()) + True + sage: P.is_induced_subposet(Poset()) + False + + Bad input:: + + sage: Poset().is_induced_subposet('junk') + Traceback (most recent call last): + ... + AttributeError: 'str' object has no attribute 'subposet' + """ + if (not self._is_facade or + (isinstance(other, FinitePoset) and not other._is_facade)): + raise TypeError('the function is not defined on non-facade posets') + # TODO: When we have decided if + # Poset({'x':[42]}) == LatticePoset({'x':[42]}) + # or not, either remove this note or remove .hasse_diagram() below. + return (set(self).issubset(set(other)) and + other.subposet(self).hasse_diagram() == self.hasse_diagram()) + FinitePoset._dual_class = FinitePoset ##### Posets ##### @@ -5672,6 +6013,7 @@ def __iter__(self): transitively-reduced, acyclic digraphs. EXAMPLES:: + sage: P = Posets(2) sage: list(P) [Finite poset containing 2 elements, Finite poset containing 2 elements] @@ -5719,13 +6061,13 @@ def cardinality(self, from_iterator=False): def is_poset(dig): r""" - Tests whether a directed graph is acyclic and transitively - reduced. + Return ``True`` if a directed graph is acyclic and transitively + reduced, and ``False`` otherwise. EXAMPLES:: sage: from sage.combinat.posets.posets import is_poset - sage: dig = DiGraph({0:[2,3], 1:[3,4,5], 2:[5], 3:[5], 4:[5]}) + sage: dig = DiGraph({0:[2, 3], 1:[3, 4, 5], 2:[5], 3:[5], 4:[5]}) sage: is_poset(dig) False sage: is_poset(dig.transitive_reduction()) diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index f939e8ba945..64d9cd70fac 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -27,7 +27,7 @@ Constructions and Species:: - sage: for (p, s) in CartesianProduct(P, S): print p, s # not tested + sage: for (p, s) in cartesian_product([P,S]): print p, s # not tested sage: DisjointUnionEnumeratedSets(Family(lambda n: IntegerVectors(n, 3), NonNegativeIntegers)) # not tested Words:: diff --git a/src/sage/combinat/ribbon_tableau.py b/src/sage/combinat/ribbon_tableau.py index 59464d6a8b7..8610f09689e 100644 --- a/src/sage/combinat/ribbon_tableau.py +++ b/src/sage/combinat/ribbon_tableau.py @@ -147,7 +147,7 @@ def to_word(self): # Ribbon Tableaux # ##################### -class RibbonTableaux(Parent, UniqueRepresentation): +class RibbonTableaux(UniqueRepresentation, Parent): r""" Ribbon tableaux. @@ -952,7 +952,7 @@ def _inversion_pairs_from_position(self, k, ij): return res -class MultiSkewTableaux(Parent, UniqueRepresentation): +class MultiSkewTableaux(UniqueRepresentation, Parent): r""" Multiskew tableaux. """ diff --git a/src/sage/combinat/rigged_configurations/bij_abstract_class.py b/src/sage/combinat/rigged_configurations/bij_abstract_class.py index b99ec3a10fc..8a12d5ce409 100644 --- a/src/sage/combinat/rigged_configurations/bij_abstract_class.py +++ b/src/sage/combinat/rigged_configurations/bij_abstract_class.py @@ -218,20 +218,21 @@ def _update_vacancy_nums(self, a): """ # Check to make sure we have a valid index (currently removed) # If the current tableau is empty, there is nothing to do - if len(self.ret_rig_con[a]) == 0: # Check to see if we have vacancy numbers + if not self.ret_rig_con[a]: # Check to see if we have vacancy numbers return # Setup the first block block_len = self.ret_rig_con[a][0] - vac_num = self.ret_rig_con.parent()._calc_vacancy_number(self.ret_rig_con.nu(), - a, 0, dims=self.cur_dims) + nu = self.ret_rig_con.nu() + vac_num = self.ret_rig_con.parent()._calc_vacancy_number(nu, a, nu[a][0], + dims=self.cur_dims) for i, row_len in enumerate(self.ret_rig_con[a]): # If we've gone to a different sized block, then update the # values which change when moving to a new block size if block_len != row_len: - vac_num = self.ret_rig_con.parent()._calc_vacancy_number(self.ret_rig_con.nu(), - a, i, dims=self.cur_dims) + vac_num = self.ret_rig_con.parent()._calc_vacancy_number(nu, a, row_len, + dims=self.cur_dims) block_len = row_len self.ret_rig_con[a].vacancy_numbers[i] = vac_num @@ -431,7 +432,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) @@ -490,14 +491,16 @@ def _update_vacancy_numbers(self, a): # Setup the first block block_len = partition[0] vac_num = self.rigged_con.parent()._calc_vacancy_number(self.cur_partitions, - a, 0, dims=self.cur_dims) + a, partition[0], + dims=self.cur_dims) for i, row_len in enumerate(self.cur_partitions[a]): # If we've gone to a different sized block, then update the # values which change when moving to a new block size if block_len != row_len: vac_num = self.rigged_con.parent()._calc_vacancy_number(self.cur_partitions, - a, i, dims=self.cur_dims) + a, row_len, + dims=self.cur_dims) block_len = row_len partition.vacancy_numbers[i] = vac_num diff --git a/src/sage/combinat/rigged_configurations/bij_type_D.py b/src/sage/combinat/rigged_configurations/bij_type_D.py index a191ed3e0df..5b660866e24 100644 --- a/src/sage/combinat/rigged_configurations/bij_type_D.py +++ b/src/sage/combinat/rigged_configurations/bij_type_D.py @@ -542,7 +542,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) diff --git a/src/sage/combinat/rigged_configurations/kleber_tree.py b/src/sage/combinat/rigged_configurations/kleber_tree.py index 30d1d634a6f..031c85902b3 100644 --- a/src/sage/combinat/rigged_configurations/kleber_tree.py +++ b/src/sage/combinat/rigged_configurations/kleber_tree.py @@ -24,30 +24,30 @@ sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree sage: KT = KleberTree(['A', 3, 1], [[3,2], [2,1], [1,1], [1,1]]) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 3] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 2, 2] and upwards edge root [1, 0, 0] - Kleber tree node with weight [2, 1, 2] and upwards edge root [0, 0, 0] - Kleber tree node with weight [2, 0, 0] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 1, 0] and upwards edge root [0, 0, 1] - Kleber tree node with weight [3, 0, 1] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 1, 0] and upwards edge root [1, 1, 1] - Kleber tree node with weight [1, 1, 1] and upwards edge root [1, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [2, 2, 1] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 2), (1, 1, 0)), + ((0, 0, 2), (2, 2, 1)), + ((0, 1, 0), (0, 0, 1)), + ((0, 1, 0), (1, 1, 1)), + ((0, 2, 2), (1, 0, 0)), + ((1, 0, 3), (1, 1, 0)), + ((1, 1, 1), (1, 1, 1)), + ((2, 0, 0), (0, 1, 1)), + ((2, 1, 2), (0, 0, 0)), + ((3, 0, 1), (0, 1, 1))] sage: KT = KleberTree(['A', 7, 1], [[3,2], [2,1], [1,1]]) sage: KT Kleber tree of Cartan type ['A', 7, 1] and B = ((3, 2), (2, 1), (1, 1)) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 1, 0, 1, 0, 0] and upwards edge root [1, 2, 2, 1, 0, 0, 0] - Kleber tree node with weight [0, 0, 1, 0, 0, 1, 0] and upwards edge root [2, 3, 3, 2, 1, 0, 0] - Kleber tree node with weight [1, 1, 2, 0, 0, 0, 0] and upwards edge root [0, 0, 0, 0, 0, 0, 0] - Kleber tree node with weight [2, 0, 1, 1, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [1, 0, 0, 2, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 3, 0, 0, 0, 0] and upwards edge root [1, 1, 0, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 0, 1, 1, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 1, 1, 1, 0, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 0, 1, 1, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((0, 0, 1, 0, 0, 1, 0), (2, 3, 3, 2, 1, 0, 0)), + ((0, 0, 3, 0, 0, 0, 0), (1, 1, 0, 0, 0, 0, 0)), + ((0, 1, 1, 1, 0, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((1, 0, 0, 2, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0)), + ((1, 0, 1, 0, 1, 0, 0), (1, 2, 2, 1, 0, 0, 0)), + ((1, 1, 2, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0)), + ((2, 0, 1, 1, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0))] """ #***************************************************************************** @@ -65,6 +65,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.misc.latex import latex @@ -77,7 +79,6 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex @@ -347,6 +348,22 @@ def multiplicity(self): return mult + def __hash__(self): + r""" + TESTS:: + + sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree + sage: RS = RootSystem(['A', 2]) + sage: WS = RS.weight_space() + sage: R = RS.root_space() + sage: KT = KleberTree(['A', 2, 1], [[1,1]]) + sage: n = KT(WS.sum_of_terms([(1,5), (2,2)]), R.zero()) + sage: hash(n) + -603608031356818252 # 64-bit + -1956156236 # 32-bit + """ + return hash(self.depth) ^ hash(self.weight) + def __cmp__(self, rhs): r""" Check whether two nodes are equal. @@ -469,7 +486,7 @@ def _latex_(self): # Kleber tree classes # ####################### -class KleberTree(Parent, UniqueRepresentation): +class KleberTree(UniqueRepresentation, Parent): r""" The tree that is generated by Kleber's algorithm. @@ -830,7 +847,9 @@ def _children_iter(self, node): L = [range(val + 1) for val in node.up_root.to_vector()] - for root in CartesianProduct(*L).list()[1:]: # First element is the zero element + it = itertools.product(*L) + it.next() # First element is the zero element + for root in it: # Convert the list to an honest root in the root space converted_root = RS.sum_of_terms([[I[i], val] for i, val in enumerate(root)]) diff --git a/src/sage/combinat/rigged_configurations/kr_tableaux.py b/src/sage/combinat/rigged_configurations/kr_tableaux.py index 1637d297003..c059ef3ea85 100644 --- a/src/sage/combinat/rigged_configurations/kr_tableaux.py +++ b/src/sage/combinat/rigged_configurations/kr_tableaux.py @@ -308,20 +308,16 @@ def __iter__(self): EXAMPLES:: - sage: KR = crystals.KirillovReshetikhin(['A', 3, 1], 2, 1, model='KR') - sage: g = KR.__iter__() - sage: next(g) - [[1], [2]] - sage: next(g) - [[1], [3]] - sage: next(g) - [[2], [3]] + sage: KR = crystals.KirillovReshetikhin(['A', 5, 2], 2, 1, model='KR') + sage: L = [x for x in KR] + sage: len(L) + 15 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() def module_generator(self, i=None, **options): r""" @@ -1322,7 +1318,7 @@ def e(self, i): sage: KRT.module_generators[0].e(0) [[-2, 1], [-1, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().e0() if ret is None: return None @@ -1343,7 +1339,7 @@ def f(self, i): sage: KRT.module_generators[0].f(0) [[1, 1], [2, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().f0() if ret is None: return None @@ -1365,7 +1361,7 @@ def epsilon(self, i): sage: KRT.module_generators[0].epsilon(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().epsilon0() return TensorProductOfRegularCrystalsElement.epsilon(self, i) @@ -1383,7 +1379,7 @@ def phi(self, i): sage: KRT.module_generators[0].phi(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().phi0() return TensorProductOfRegularCrystalsElement.phi(self, i) @@ -1481,7 +1477,8 @@ def e(self, i): [[1], [3], [-4], [-2]] sage: KRT(-1, -4, 3, 2).e(3) """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.e(self, i) half = KirillovReshetikhinTableauxElement.e(self, i) @@ -1500,7 +1497,8 @@ def f(self, i): sage: KRT(-1, -4, 3, 2).f(3) [[2], [4], [-3], [-1]] """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.f(self, i) half = KirillovReshetikhinTableauxElement.f(self, i) @@ -1521,7 +1519,8 @@ def epsilon(self, i): sage: KRT(-1, -4, 3, 2).epsilon(3) 0 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.epsilon(self, i) return KirillovReshetikhinTableauxElement.epsilon(self, i) // 2 @@ -1537,7 +1536,8 @@ def phi(self, i): sage: KRT(-1, -4, 3, 2).phi(3) 1 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.phi(self, i) return KirillovReshetikhinTableauxElement.phi(self, i) // 2 diff --git a/src/sage/combinat/rigged_configurations/rc_crystal.py b/src/sage/combinat/rigged_configurations/rc_crystal.py index c770aeea1b7..1c31feb3a0b 100644 --- a/src/sage/combinat/rigged_configurations/rc_crystal.py +++ b/src/sage/combinat/rigged_configurations/rc_crystal.py @@ -39,7 +39,7 @@ from sage.combinat.rigged_configurations.rigged_partition import RiggedPartition # Note on implementation, this class is used for simply-laced types only -class CrystalOfRiggedConfigurations(Parent, UniqueRepresentation): +class CrystalOfRiggedConfigurations(UniqueRepresentation, Parent): r""" A highest weight crystal of rigged configurations. @@ -223,8 +223,7 @@ def _element_constructor_(self, *lst, **options): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}(\nu)` in ``self``. This assumes that `\gamma_a = 1` for all `a` and `(\alpha_a | \alpha_b ) = A_{ab}`. @@ -235,25 +234,20 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: La = RootSystem(['A', 2]).weight_lattice().fundamental_weights() sage: RC = crystals.RiggedConfigurations(La[1] + La[2]) sage: elt = RC(partition_list=[[1],[2]]) - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 2) -2 """ vac_num = self._wt[self.index_set()[a]] - if i is None: - row_len = float('inf') - else: - row_len = partitions[a][i] - for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(row_len) + vac_num -= value * partitions[b].get_num_cells_to_column(i) return vac_num @@ -312,8 +306,7 @@ def virtual(self): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}(\nu)` in ``self``. INPUT: @@ -321,31 +314,26 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: La = RootSystem(['C', 3]).weight_lattice().fundamental_weights() sage: RC = crystals.RiggedConfigurations(La[2]) sage: elt = RC(partition_list=[[], [1], [1]]) - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 1) 0 - sage: RC._calc_vacancy_number(elt.nu(), 2, 0) + sage: RC._calc_vacancy_number(elt.nu(), 2, 1) -1 """ I = self.index_set() ia = I[a] vac_num = self._wt[ia] - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - gamma = self._folded_ct.scaling_factors() for b, value in enumerate(self._cartan_matrix.row(a)): ib = I[b] - q = partitions[b].get_num_cells_to_column(gamma[ia]*row_len, gamma[ib]) + q = partitions[b].get_num_cells_to_column(gamma[ia]*i, gamma[ib]) vac_num -= value * q / gamma[ib] return vac_num diff --git a/src/sage/combinat/rigged_configurations/rc_infinity.py b/src/sage/combinat/rigged_configurations/rc_infinity.py index 36d97eb7484..e9cf187af27 100644 --- a/src/sage/combinat/rigged_configurations/rc_infinity.py +++ b/src/sage/combinat/rigged_configurations/rc_infinity.py @@ -34,7 +34,7 @@ from sage.combinat.rigged_configurations.rigged_partition import RiggedPartition # Note on implementation, this class is used for simply-laced types only -class InfinityCrystalOfRiggedConfigurations(Parent, UniqueRepresentation): +class InfinityCrystalOfRiggedConfigurations(UniqueRepresentation, Parent): r""" Rigged configuration model for `\mathcal{B}(\infty)`. @@ -218,8 +218,7 @@ def _coerce_map_from_(self, P): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}(\nu)` in ``self``. This assumes that `\gamma_a = 1` for all `a` and `(\alpha_a \mid \alpha_b ) = A_{ab}`. @@ -230,24 +229,19 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: RC = crystals.infinity.RiggedConfigurations(['A', 4]) sage: elt = RC(partition_list=[[1], [1], [], []]) - sage: RC._calc_vacancy_number(elt.nu(), 0, 0) + sage: RC._calc_vacancy_number(elt.nu(), 0, 1) -1 """ vac_num = 0 - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(row_len) + vac_num -= value * partitions[b].get_num_cells_to_column(i) return vac_num @@ -313,10 +307,9 @@ def __init__(self, vct): self._folded_ct = vct InfinityCrystalOfRiggedConfigurations.__init__(self, vct._cartan_type) - def _calc_vacancy_number(self, partitions, a, i, **options): + def _calc_vacancy_number(self, partitions, a, i): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}(\nu)` in ``self``. INPUT: @@ -324,31 +317,26 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: La = RootSystem(['C',2]).weight_lattice().fundamental_weights() sage: RC = crystals.RiggedConfigurations(La[1]) sage: elt = RC(partition_list=[[1], [1]]) - sage: RC._calc_vacancy_number(elt.nu(), 0, 0) + sage: RC._calc_vacancy_number(elt.nu(), 0, 1) 0 - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 1) -1 """ I = self.index_set() ia = I[a] vac_num = 0 - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - gamma = self._folded_ct.scaling_factors() for b, value in enumerate(self._cartan_matrix.row(a)): ib = I[b] - q = partitions[b].get_num_cells_to_column(gamma[ia]*row_len, gamma[ib]) + q = partitions[b].get_num_cells_to_column(gamma[ia]*i, gamma[ib]) vac_num -= value * q / gamma[ib] return vac_num diff --git a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py index 780ade27b54..bbfe85b94a7 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py +++ b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py @@ -245,13 +245,13 @@ def __init__(self, parent, rigged_partitions=[], **options): # Setup the first block block_len = partition[0] - vac_num = parent._calc_vacancy_number(nu, a, 0) + vac_num = parent._calc_vacancy_number(nu, a, block_len) for i, row_len in enumerate(partition): # If we've gone to a different sized block, then update the # values which change when moving to a new block size if block_len != row_len: - vac_num = parent._calc_vacancy_number(nu, a, i) + vac_num = parent._calc_vacancy_number(nu, a, row_len) block_len = row_len partition.vacancy_numbers[i] = vac_num @@ -587,10 +587,11 @@ def e(self, a): new_partitions.append(RiggedPartition(new_list, new_rigging, new_vac_nums)) ret_RC = self.__class__(self.parent(), new_partitions) + nu = ret_RC.nu() if k != 1 and not set_vac_num: # If we did not remove a row nor found another row of length k-1 # Update that row's vacancy number ret_RC[a].vacancy_numbers[rigging_index] = \ - self.parent()._calc_vacancy_number(ret_RC.nu(), a, rigging_index) + self.parent()._calc_vacancy_number(nu, a, nu[a][rigging_index]) return(ret_RC) def _generate_partition_e(self, a, b, k): @@ -729,7 +730,8 @@ def f(self, a): new_partitions.append(RiggedPartition(new_list, new_rigging, new_vac_nums)) new_partitions[a].vacancy_numbers[add_index] = \ - self.parent()._calc_vacancy_number(new_partitions, a, add_index) + self.parent()._calc_vacancy_number(new_partitions, a, + new_partitions[a][add_index]) # Note that we do not need to sort the rigging since if there was a # smaller rigging in a larger row, then `k` would be larger. @@ -821,30 +823,12 @@ def phi(self, a): [1 1] """ a = self.parent()._rc_index.index(a) - p_inf = self.parent()._calc_vacancy_number(self, a, None) + p_inf = self.parent()._calc_vacancy_number(self, a, float("inf")) if not self[a]: return Integer(p_inf) return Integer(p_inf - min(0, min(self[a].rigging))) - def get_vacancy_numbers(self, a): - r""" - Return the list of all vacancy numbers of the rigged partition - `\nu^{(a)}` (with duplicates). - - INPUT: - - - ``a`` -- the index of the rigged partition - - EXAMPLES:: - - sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 2]]) - sage: RC(partition_list=[[1], [2,1], [1], []]).get_vacancy_numbers(2) - [-2, -1] - """ - a = self.parent()._rc_index.index(a) - return self[a].vacancy_numbers - - def get_vacancy_number(self, a, i): + def vacancy_number(self, a, i): r""" Return the vacancy number `p_i^{(a)}`. @@ -858,20 +842,23 @@ def get_vacancy_number(self, a, i): sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 2]]) sage: elt = RC(partition_list=[[1], [2,1], [1], []]) - sage: elt.get_vacancy_number(2, 3) - sage: elt.get_vacancy_number(2, 2) + sage: elt.vacancy_number(2, 3) -2 - sage: elt.get_vacancy_number(2, 1) + sage: elt.vacancy_number(2, 2) + -2 + sage: elt.vacancy_number(2, 1) -1 + + sage: RC = RiggedConfigurations(['D',4,1], [[2,1], [2,1]]) + sage: x = RC(partition_list=[[3], [3,1,1], [2], [3,1]]); ascii_art(x) + -1[ ][ ][ ]-1 1[ ][ ][ ]1 0[ ][ ]0 -3[ ][ ][ ]-3 + 0[ ]0 -1[ ]-1 + 0[ ]0 + sage: x.vacancy_number(2,2) + 1 """ - a = self.parent()._rc_index.index(a) - partition = self[a] - for k, val in enumerate(partition): - if val == i: - return partition.vacancy_numbers[k] - elif val < i: - return None - return None + a = self.cartan_type().classical().index_set().index(a) + return self.parent()._calc_vacancy_number(self.nu(), a, i) def partition_rigging_lists(self): """ @@ -1818,6 +1805,125 @@ def left_box(self, return_b=False): delta = left_box + def left_column_box(self): + r""" + Return the image of ``self`` under the left column box splitting + map `\gamma`. + + Consider the map `\gamma : RC(B^{r,1} \otimes B) \to RC(B^{1,1} + \otimes B^{r-1,1} \otimes B)` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a singular string of length `1` to `\nu^{(a)}`. + + We can extend `\gamma` when the left-most factor is not a single + column by precomposing with a :meth:`left_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[3,1], [2,1]]) + sage: mg = RC.module_generators[-1] + sage: ascii_art(mg) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 1[ ]1 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + """ + P = self.parent() + r = P.dims[0][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[0][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[0][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[0][1] > 1: + return self.left_split().left_column_box() + + B = [[1,1], [r-1,1]] + B.extend(P.dims[1:]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in self] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, 1) + i = nu._list.index(1) + nu.vacancy_numbers.insert(i, vac_num) + nu.rigging.insert(i, vac_num) + return RC(*parts) + + def right_column_box(self): + r""" + Return the image of ``self`` under the right column box splitting + map `\gamma^*`. + + Consider the map `\gamma^* : RC(B \otimes B^{r,1}) \to RC(B \otimes + B^{r-1,1} \otimes B^{1,1})` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a string of length `1` with rigging 0 to `\nu^{(a)}` + for all `a < r` to a classically highest weight element and extended + as a classical crystal morphism. + + We can extend `\gamma^*` when the right-most factor is not a single + column by precomposing with a :meth:`right_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.right_column_box()) + 1[ ]0 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + """ + P = self.parent() + r = P.dims[-1][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[-1][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[-1][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[-1][1] > 1: + return self.right_split().right_column_box() + + rc, e_string = self.to_highest_weight(P.cartan_type().classical().index_set()) + + B = P.dims[:-1] + ([r-1,1], [1,1]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in rc] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, -1) + nu.vacancy_numbers.append(vac_num) + nu.rigging.append(0) + return RC(*parts).f_string(reversed(e_string)) + def complement_rigging(self, reverse_factors=False): r""" Apply the complement rigging morphism `\theta` to ``self``. @@ -1897,7 +2003,7 @@ def complement_rigging(self, reverse_factors=False): rig = [] for a,p in enumerate(mg): nu.append(list(p)) - vac_nums = mg.get_vacancy_numbers(a+1) + vac_nums = p.vacancy_numbers riggings = [vac - p.rigging[i] for i,vac in enumerate(vac_nums)] block = 0 for j,i in enumerate(p): @@ -2197,7 +2303,7 @@ def phi(self, a): return self.to_tensor_product_of_kirillov_reshetikhin_tableaux().phi(a) a = self.parent()._rc_index.index(a) - p_inf = self.parent()._calc_vacancy_number(self, a, None) + p_inf = self.parent()._calc_vacancy_number(self, a, float("inf")) if not self[a]: phi = p_inf else: diff --git a/src/sage/combinat/rigged_configurations/rigged_configurations.py b/src/sage/combinat/rigged_configurations/rigged_configurations.py index 3078d3b9e97..190a57e9944 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configurations.py +++ b/src/sage/combinat/rigged_configurations/rigged_configurations.py @@ -21,6 +21,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.structure.global_options import GlobalOptions @@ -32,7 +34,6 @@ from sage.categories.finite_crystals import FiniteCrystals from sage.categories.regular_crystals import RegularCrystals from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.rigged_configurations.kleber_tree import KleberTree, VirtualKleberTree from sage.combinat.rigged_configurations.rigged_configuration_element import ( RiggedConfigurationElement, KRRCSimplyLacedElement, KRRCNonSimplyLacedElement, @@ -129,7 +130,7 @@ def KirillovReshetikhinCrystal(cartan_type, r, s): return RiggedConfigurations(cartan_type, [[r,s]]) # Note on implementation, this class is used for simply-laced types only -class RiggedConfigurations(Parent, UniqueRepresentation): +class RiggedConfigurations(UniqueRepresentation, Parent): r""" Rigged configurations as `U_q^{\prime}(\mathfrak{g})`-crystals. @@ -249,7 +250,7 @@ class RiggedConfigurations(Parent, UniqueRepresentation): 6 sage: len(RC.list()) == RC.cardinality() True - sage: RC.list() + sage: RC.list() # random [ 0[ ]0 @@ -480,29 +481,15 @@ def __iter__(self): EXAMPLES:: sage: RC = RiggedConfigurations(['A', 3, 1], [[2,1], [1,1]]) - sage: g = RC.__iter__() - sage: next(g) - - (/) - - (/) - - (/) - - sage: next(g) - - (/) - - -1[ ]-1 - - (/) - + sage: L = [x for x in RC] + sage: len(L) + 24 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() @lazy_attribute def module_generators(self): @@ -602,10 +589,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), @@ -771,8 +757,7 @@ def _element_constructor_(self, *lst, **options): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}` in ``self``. This assumes that `\gamma_a = 1` for all `a` and `(\alpha_a \mid \alpha_b ) = A_{ab}`. @@ -783,41 +768,36 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: RC = RiggedConfigurations(['A', 4, 1], [[2, 1]]) sage: elt = RC(partition_list=[[1], [1], [], []]) - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 1) 0 """ - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - vac_num = 0 if "B" in options: for tableau in options["B"]: if len(tableau) == self._rc_index[a]: - vac_num += min(row_len, len(tableau[0])) + vac_num += min(i, len(tableau[0])) elif "L" in options: L = options["L"] if a in L: for kvp in L[a].items(): - vac_num += min(kvp[0], row_len) * kvp[1] + vac_num += min(kvp[0], i) * kvp[1] elif "dims" in options: for dim in options["dims"]: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) else: for dim in self.dims: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(row_len) + vac_num -= value * partitions[b].get_num_cells_to_column(i) return vac_num @@ -858,12 +838,18 @@ def cardinality(self): sage: RC = RiggedConfigurations(['A', 3, 1], [[3, 2], [1, 2]]) sage: RC.cardinality() 100 - sage: RC = RiggedConfigurations(['B', 3, 1], [[2,2],[1,2]]) - sage: RC.cardinality() - 5130 + sage: len(RC.list()) + 100 + sage: RC = RiggedConfigurations(['E', 7, 1], [[1,1]]) sage: RC.cardinality() 134 + sage: len(RC.list()) + 134 + + sage: RC = RiggedConfigurations(['B', 3, 1], [[2,2],[1,2]]) + sage: RC.cardinality() + 5130 """ CWLR = self.cartan_type().classical().root_system().ambient_space() return sum(CWLR.weyl_dimension(mg.classical_weight()) for mg in self.module_generators) @@ -1135,8 +1121,7 @@ def __init__(self, cartan_type, dims): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}` in ``self``. INPUT: @@ -1144,42 +1129,37 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: RC = RiggedConfigurations(['C', 4, 1], [[2, 1]]) sage: elt = RC(partition_list=[[1], [2], [2], [1]]) - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 2) 0 """ - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - vac_num = 0 if "B" in options: for tableau in options["B"]: if len(tableau) == self._rc_index[a]: - vac_num += min(row_len, len(tableau[0])) + vac_num += min(i, len(tableau[0])) elif "L" in options: L = options["L"] if a in L: for kvp in L[a].items(): - vac_num += min(kvp[0], row_len) * kvp[1] + vac_num += min(kvp[0], i) * kvp[1] elif "dims" in options: for dim in options["dims"]: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) else: for dim in self.dims: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) gamma = self._folded_ct.scaling_factors() for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(gamma[a+1]*row_len, gamma[b+1]) // gamma[b+1] + vac_num -= value * partitions[b].get_num_cells_to_column(gamma[a+1]*i, gamma[b+1]) // gamma[b+1] return vac_num @@ -1285,10 +1265,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for cur_blocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(cur_blocks[:]), vac_nums[:]]) ) @@ -1488,8 +1467,7 @@ def virtual(self): def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number of the `i`-th row of the `a`-th rigged - partition. + Calculate the vacancy number `p_i^{(a)}` in ``self``. This is a special implementation for type `A_{2n}^{(2)}`. @@ -1499,42 +1477,37 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row length TESTS:: sage: RC = RiggedConfigurations(['A', 4, 2], [[2, 1]]) sage: elt = RC(partition_list=[[1], [2]]) - sage: RC._calc_vacancy_number(elt.nu(), 1, 0) + sage: RC._calc_vacancy_number(elt.nu(), 1, 2) 0 """ - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] - vac_num = 0 if "B" in options: for tableau in options["B"]: if len(tableau) == self._rc_index[a]: - vac_num += min(row_len, len(tableau[0])) + vac_num += min(i, len(tableau[0])) elif "L" in options: L = options["L"] if a in L: for kvp in L[a].items(): - vac_num += min(kvp[0], row_len) * kvp[1] + vac_num += min(kvp[0], i) * kvp[1] elif "dims" in options: for dim in options["dims"]: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) else: for dim in self.dims: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) gamma = self._folded_ct.scaling_factors() for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(row_len) // gamma[b+1] + vac_num -= value * partitions[b].get_num_cells_to_column(i) // gamma[b+1] return vac_num @@ -1645,8 +1618,8 @@ class RCTypeA2Dual(RCTypeA2Even): """ def _calc_vacancy_number(self, partitions, a, i, **options): r""" - Calculate the vacancy number. A special case is needed for the `n`-th - partition for type `A_{2n}^{(2)\dagger}`. + Calculate the vacancy number `p_i^{(a)}` in ``self``. A special case + is needed for the `n`-th partition for type `A_{2n}^{(2)\dagger}`. INPUT: @@ -1654,43 +1627,39 @@ def _calc_vacancy_number(self, partitions, a, i, **options): - ``a`` -- the rigged partition index - - ``i`` -- the row index of the `a`-th rigged partition + - ``i`` -- the row lenth TESTS:: sage: RC = RiggedConfigurations(CartanType(['A', 6, 2]).dual(), [[2,1]]) sage: elt = RC(partition_list=[[1], [2], [2]]) - sage: RC._calc_vacancy_number(elt.nu(), 0, 0) + sage: RC._calc_vacancy_number(elt.nu(), 0, 1) -1 """ if a != self._cartan_type.classical().rank()-1: return RCTypeA2Even._calc_vacancy_number(self, partitions, a, i, **options) - if i is None: - row_len = float("inf") - else: - row_len = partitions[a][i] vac_num = 0 if "B" in options: for tableau in options["B"]: if len(tableau) == self._rc_index[a]: - vac_num += min(row_len, len(tableau[0])) + vac_num += min(i, len(tableau[0])) elif "L" in options: L = options["L"] if a in L: for kvp in L[a].items(): - vac_num += min(kvp[0], row_len) * kvp[1] + vac_num += min(kvp[0], i) * kvp[1] elif "dims" in options: for dim in options["dims"]: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) else: for dim in self.dims: if dim[0] == self._rc_index[a]: - vac_num += min(dim[1], row_len) + vac_num += min(dim[1], i) for b, value in enumerate(self._cartan_matrix.row(a)): - vac_num -= value * partitions[b].get_num_cells_to_column(row_len) / 2 + vac_num -= value * partitions[b].get_num_cells_to_column(i) / 2 return vac_num @@ -1784,7 +1753,7 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) # Special case for the final tableau partition = base[-1] @@ -1815,10 +1784,9 @@ def module_generators(self): L2.append(IterableFunctionCall(self._block_iterator_n_odd, block)) else: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), vac_nums[:]]) ) diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index f4cfb1b9a9a..6a9be3b1b0c 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -35,6 +35,7 @@ - :ref:`sage.combinat.root_system.dynkin_diagram` - :ref:`sage.combinat.root_system.cartan_matrix` - :ref:`sage.combinat.root_system.coxeter_matrix` +- :ref:`sage.combinat.root_system.coxeter_type` Root systems ------------ diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index f3b45cbe666..91e894eee4c 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -6,8 +6,9 @@ from cartan_type import CartanType from dynkin_diagram import DynkinDiagram -from cartan_matrix import CartanMatrix, cartan_matrix -from coxeter_matrix import coxeter_matrix +from cartan_matrix import CartanMatrix +from coxeter_matrix import CoxeterMatrix, coxeter_matrix +from coxeter_type import CoxeterType from root_system import RootSystem, WeylDim from weyl_group import WeylGroup, WeylGroupElement lazy_import('sage.combinat.root_system.extended_affine_weyl_group', 'ExtendedAffineWeylGroup') diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 0f96b9bc1ca..6d152d57376 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -35,6 +35,7 @@ from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.root_system import RootSystem from sage.sets.family import Family +from sage.graphs.digraph import DiGraph class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): r""" @@ -197,9 +198,10 @@ class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): __metaclass__ = InheritComparisonClasscallMetaclass @staticmethod - def __classcall_private__(cls, *args, **kwds): + def __classcall_private__(cls, data=None, index_set=None, + cartan_type=None, cartan_type_check=True): """ - Normalize input so we can inherit from spare integer matrix. + Normalize input so we can inherit from sparse integer matrix. .. NOTE:: @@ -227,30 +229,31 @@ def __classcall_private__(cls, *args, **kwds): ('a', 'b') """ # Special case with 0 args and kwds has Cartan type - if "cartan_type" in kwds and len(args) == 0: - args = (CartanType(kwds["cartan_type"]),) - if len(args) == 0: + if cartan_type is not None and data is None: + data = CartanType(cartan_type) + + if data is None: data = [] n = 0 index_set = tuple() cartan_type = None subdivisions = None - elif len(args) == 4 and isinstance(args[0], MatrixSpace): # For pickling - return typecall(cls, args[0], args[1], args[2], args[3]) - elif isinstance(args[0], CartanMatrix): - return args[0] + elif isinstance(data, CartanMatrix): + if index_set is not None: + d = {a: index_set[i] for i,a in enumerate(data.index_set())} + return data.relabel(d) + return data else: - cartan_type = None dynkin_diagram = None subdivisions = None from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class - if isinstance(args[0], DynkinDiagram_class): - dynkin_diagram = args[0] - cartan_type = args[0]._cartan_type + if isinstance(data, DynkinDiagram_class): + dynkin_diagram = data + cartan_type = data._cartan_type else: try: - cartan_type = CartanType(args[0]) + cartan_type = CartanType(data) dynkin_diagram = cartan_type.dynkin_diagram() except (TypeError, ValueError): pass @@ -258,54 +261,74 @@ def __classcall_private__(cls, *args, **kwds): if dynkin_diagram is not None: n = dynkin_diagram.rank() index_set = dynkin_diagram.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) + reverse = {a: i for i,a in enumerate(index_set)} data = {(i, i): 2 for i in range(n)} for (i,j,l) in dynkin_diagram.edge_iterator(): data[(reverse[j], reverse[i])] = -l else: - M = matrix(args[0]) + M = matrix(data) if not is_generalized_cartan_matrix(M): raise ValueError("the input matrix is not a generalized Cartan matrix") n = M.ncols() - if "cartan_type" in kwds: - cartan_type = CartanType(kwds["cartan_type"]) - elif n == 1: - cartan_type = CartanType(['A', 1]) - elif kwds.get("cartan_type_check", True): - cartan_type = find_cartan_type_from_matrix(M) data = M.dict() subdivisions = M._subdivisions - if len(args) == 1: - if cartan_type is not None: - index_set = tuple(cartan_type.index_set()) - elif dynkin_diagram is None: - index_set = tuple(range(n)) - elif len(args) == 2: - index_set = tuple(args[1]) - if len(index_set) != n and len(set(index_set)) != n: - raise ValueError("the given index set is not valid") + if index_set is None: + index_set = tuple(range(n)) else: - raise ValueError("too many arguments") + index_set = tuple(index_set) + + if len(index_set) != n and len(set(index_set)) != n: + raise ValueError("the given index set is not valid") - mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, cartan_type, index_set) + # We can do the Cartan type initialization later as this is not + # a unqiue representation + mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, False, False) + # FIXME: We have to initialize the CartanMatrix part separately because + # of the __cinit__ of the matrix. We should get rid of this workaround + mat._CM_init(cartan_type, index_set, cartan_type_check) mat._subdivisions = subdivisions return mat - def __init__(self, parent, data, cartan_type, index_set): + def _CM_init(self, cartan_type, index_set, cartan_type_check): """ - Initialize ``self``. + Initialize ``self`` as a Cartan matrix. TESTS:: - sage: C = CartanMatrix(['A',1,1]) + sage: C = CartanMatrix(['A',1,1]) # indirect doctest sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) """ - Matrix_integer_sparse.__init__(self, parent, data, False, False) - self._cartan_type = cartan_type self._index_set = index_set self.set_immutable() + if cartan_type is not None: + cartan_type = CartanType(cartan_type) + elif self.nrows() == 1: + cartan_type = CartanType(['A', 1]) + elif cartan_type_check: + # Placeholder so we don't have to reimplement creating a + # Dynkin diagram from a Cartan matrix + self._cartan_type = None + cartan_type = find_cartan_type_from_matrix(self) + + self._cartan_type = cartan_type + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: CM = CartanMatrix(['A',4]) + sage: x = loads(dumps(CM)) + sage: x._index_set + (1, 2, 3, 4) + """ + if self._cartan_type: + return (CartanMatrix, (self._cartan_type,)) + return (CartanMatrix, (self.dynkin_diagram(),)) + def root_system(self): """ Return the root system corresponding to ``self``. @@ -462,14 +485,17 @@ def subtype(self, index_set): EXAMPLES:: sage: C = CartanMatrix(['F',4]) - sage: C.subtype([1,2,3]) + sage: S = C.subtype([1,2,3]) + sage: S [ 2 -1 0] [-1 2 -1] [ 0 -2 2] + sage: S.index_set() + (1, 2, 3) """ ind = self.index_set() I = [ind.index(i) for i in index_set] - return CartanMatrix(self.matrix_from_rows_and_columns(I, I)) + return CartanMatrix(self.matrix_from_rows_and_columns(I, I), index_set) def rank(self): r""" @@ -715,7 +741,7 @@ def is_hyperbolic(self, compact=False): - ``compact`` -- if ``True``, check if matrix is compact hyperbolic EXAMPLES:: - + sage: M = CartanMatrix([[2,-2,0],[-2,2,-1],[0,-1,2]]) sage: M.is_hyperbolic() True @@ -751,7 +777,7 @@ def is_lorentzian(self): and exactly one negative eigenvalue. EXAMPLES:: - + sage: M = CartanMatrix([[2,-3],[-3,2]]) sage: M.is_lorentzian() True @@ -769,7 +795,7 @@ def is_indefinite(self): Return if ``self`` is an indefinite type or ``False`` otherwise. EXAMPLES:: - + sage: M = CartanMatrix([[2,-3],[-3,2]]) sage: M.is_indefinite() True @@ -785,7 +811,7 @@ def is_indecomposable(self): Return if ``self`` is an indecomposable matrix or ``False`` otherwise. EXAMPLES:: - + sage: M = CartanMatrix(['A',5]) sage: M.is_indecomposable() True @@ -806,7 +832,7 @@ def principal_submatrices(self, proper=False): - ``proper`` -- if ``True``, return only proper submatrices EXAMPLES:: - + sage: M = CartanMatrix(['A',2]) sage: M.principal_submatrices() [ @@ -830,7 +856,7 @@ def indecomposable_blocks(self): Return a tuple of all indecomposable blocks of ``self``. EXAMPLES:: - + sage: M = CartanMatrix(['A',2]) sage: M.indecomposable_blocks() ( @@ -891,88 +917,84 @@ def is_generalized_cartan_matrix(M): return True def find_cartan_type_from_matrix(CM): - """ - Find a Cartan type by direct comparison of matrices given from the - generalized Cartan matrix ``CM`` and return ``None`` if not found. + r""" + Find a Cartan type by direct comparison of Dynkin diagrams given from + the generalized Cartan matrix ``CM`` and return ``None`` if not found. INPUT: - - ``CM`` -- A generalized Cartan matrix + - ``CM`` -- a generalized Cartan matrix EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import find_cartan_type_from_matrix - sage: M = matrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) + sage: find_cartan_type_from_matrix(CM) ['A', 2, 1] - sage: M = matrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) - sage: find_cartan_type_from_matrix(M) - ['C', 3] - sage: M = matrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) + sage: find_cartan_type_from_matrix(CM) + ['C', 3] relabelled by {1: 0, 2: 1, 3: 2} + sage: CM = CartanMatrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) + sage: find_cartan_type_from_matrix(CM) """ - n = CM.ncols() - # Build the list to test based upon rank - if n == 1: - return CartanType(['A', 1]) - - test = [['A', n]] - if n >= 2: - if n == 2: - test += [['G',2], ['A',2,2]] - test += [['B',n], ['A',n-1,1]] - if n >= 3: - if n == 3: - test += [['G',2,1], ['D',4,3]] - test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] - if n >= 4: - if n == 4: - test += [['F',4], ['G',2,1], ['D',4,3]] - test += [['D',n], ['B',n-1,1]] - if n == 5: - test += [['F',4,1], ['D',n-1,1]] - elif n == 6: - test.append(['E',6]) - elif n == 7: - test += [['E',7], ['E',6,1]] - elif n == 8: - test += [['E',8], ['E',7,1]] - elif n == 9: - test.append(['E',8,1]) - - # Test every possible Cartan type and its dual - for x in test: - ct = CartanType(x) - if ct.cartan_matrix() == CM: - return ct - if ct == ct.dual(): + types = [] + for S in CM.dynkin_diagram().connected_components_subgraphs(): + S = DiGraph(S) # We need a simple digraph here + n = S.num_verts() + # Build the list to test based upon rank + if n == 1: + types.append(CartanType(['A', 1])) continue - ct = ct.dual() - if ct.cartan_matrix() == CM: - return ct - return None - -def cartan_matrix(t): - """ - Return the Cartan matrix of type `t`. - .. NOTE:: - - This function is deprecated in favor of - ``CartanMatrix(...)``, to avoid polluting the - global namespace. - - EXAMPLES:: - - sage: cartan_matrix(['A', 4]) - doctest:...: DeprecationWarning: cartan_matrix() is deprecated. Use CartanMatrix() instead - See http://trac.sagemath.org/14137 for details. - [ 2 -1 0 0] - [-1 2 -1 0] - [ 0 -1 2 -1] - [ 0 0 -1 2] - """ - from sage.misc.superseded import deprecation - deprecation(14137, 'cartan_matrix() is deprecated. Use CartanMatrix() instead') - return CartanMatrix(t) + test = [['A', n]] + if n >= 2: + if n == 2: + test += [['G',2], ['A',2,2]] + test += [['B',n], ['A',n-1,1]] + if n >= 3: + if n == 3: + test.append(['G',2,1]) + test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] + if n >= 4: + if n == 4: + test.append(['F',4]) + test += [['D',n], ['B',n-1,1]] + if n >= 5: + if n == 5: + test.append(['F',4,1]) + test.append(['D',n-1,1]) + if n == 6: + test.append(['E',6]) + elif n == 7: + test += [['E',7], ['E',6,1]] + elif n == 8: + test += [['E',8], ['E',7,1]] + elif n == 9: + test.append(['E',8,1]) + + # Test every possible Cartan type and its dual + found = False + for x in test: + ct = CartanType(x) + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + + if ct == ct.dual(): + continue # self-dual, so nothing more to test + + ct = ct.dual() + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CartanType(types) diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index ad25dff373a..17fbc7cd24c 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -279,9 +279,9 @@ .. rubric:: Affine Cartan types -For affine types, we use the usual conventions for affine Coxeter groups: each affine type -is either untwisted (that is arise from the natural affinisation -of a finite Cartan type):: +For affine types, we use the usual conventions for affine Coxeter +groups: each affine type is either untwisted (that is arise from the +natural affinisation of a finite Cartan type):: sage: CartanType(["A", 4, 1]).dynkin_diagram() 0 @@ -451,9 +451,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.global_options import GlobalOptions from sage.sets.family import Family -from sage.misc.superseded import deprecated_function_alias from sage.misc.decorators import rename_keyword -from sage.misc.misc import union from __builtin__ import sorted # TODO: @@ -605,6 +603,16 @@ def __call__(self, *args): True sage: CartanType('A2') ['A', 2] + + Check that we can pass any Cartan type as a single element list:: + + sage: CT = CartanType(['A2', 'A2', 'A2']) + sage: CartanType([CT]) + A2xA2xA2 + + sage: CT = CartanType('A2').relabel({1:-1, 2:-2}) + sage: CartanType([CT]) + ['A', 2] relabelled by {1: -1, 2: -2} """ if len(args) == 1: t = args[0] @@ -618,6 +626,10 @@ def __call__(self, *args): if len(t) == 1: # Fix for trac #13774 t = t[0] + # We need to make another check + if isinstance(t, CartanType_abstract): + return t + if isinstance(t, str): if "x" in t: import type_reducible @@ -1085,16 +1097,20 @@ def coxeter_matrix(self): [2 3 1 3] [2 2 3 1] """ - from sage.matrix.constructor import matrix - from sage.rings.all import ZZ - index_set = self.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - m = matrix(ZZ,len(index_set), lambda i,j: 1 if i==j else 2) - for (i,j,l) in self.coxeter_diagram().edge_iterator(): - m[reverse[i], reverse[j]] = l - m[reverse[j], reverse[i]] = l - m.set_immutable() - return m + from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix + return CoxeterMatrix(self) + + def coxeter_type(self): + """ + Return the Coxeter type for ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 4]).coxeter_type() + Coxeter type of ['A', 4] + """ + from sage.combinat.root_system.coxeter_type import CoxeterType + return CoxeterType(self) def dual(self): """ @@ -1305,20 +1321,9 @@ def is_crystallographic(self): [['E', 6], True], [['E', 7], True], [['E', 8], True], [['F', 4], True], [['G', 2], True], [['I', 5], False], [['H', 3], False], [['H', 4], False]] - - TESTS:: - - sage: all(t.is_crystallographic() for t in CartanType.samples(affine=True)) - True - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return False - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def is_simply_laced(self): """ Return whether this Cartan type is simply laced. @@ -1485,8 +1490,12 @@ def ascii_art(self, label='lambda x: x', node=None): 1 2 2 2 2 """ + # The default value of label should really be lambda i:i, but sphinx does + # not like it currently (see #14553); since this is an abstract method + # the value won't actually be used, so we put a fake instead. @abstract_method(optional=True) - def _latex_dynkin_diagram(self, label=lambda i: i, node=None, node_dist=2): + def _latex_dynkin_diagram(self, label='lambda i: i', + node=None, node_dist=2): r""" Return a latex representation of the Dynkin diagram. @@ -1593,18 +1602,9 @@ def is_crystallographic(self): sage: CartanType(['A', 3, 1]).is_crystallographic() True - - TESTS:: - - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - @cached_method def symmetrizer(self): """ diff --git a/src/sage/combinat/root_system/coxeter_group.py b/src/sage/combinat/root_system/coxeter_group.py index 015f2c1abcb..b1d4aa5b2a6 100644 --- a/src/sage/combinat/root_system/coxeter_group.py +++ b/src/sage/combinat/root_system/coxeter_group.py @@ -16,7 +16,6 @@ from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.combinat.root_system.weyl_group import WeylGroup from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.parent import Parent from sage.combinat.root_system.cartan_type import CartanType from sage.groups.perm_gps.permgroup import PermutationGroup_generic diff --git a/src/sage/combinat/root_system/coxeter_matrix.py b/src/sage/combinat/root_system/coxeter_matrix.py index 499f861f5a8..7344c72e159 100644 --- a/src/sage/combinat/root_system/coxeter_matrix.py +++ b/src/sage/combinat/root_system/coxeter_matrix.py @@ -1,8 +1,10 @@ """ -Coxeter matrices +Coxeter Matrices """ #***************************************************************************** # Copyright (C) 2007 Mike Hansen , +# 2015 Travis Scrimshaw +# 2015 Jean-Philippe Labbe # # Distributed under the terms of the GNU General Public License (GPL) # @@ -15,64 +17,63 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from cartan_type import CartanType -from sage.matrix.all import MatrixSpace -from sage.rings.all import ZZ -def coxeter_matrix_as_function(t): - """ - Returns the Coxeter matrix, as a function +from sage.misc.cachefunc import cached_method +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall +from sage.matrix.matrix_generic_dense import Matrix_generic_dense +from sage.graphs.graph import Graph +from sage.rings.all import ZZ, QQ, RR +from sage.rings.infinity import infinity +from sage.combinat.root_system.cartan_type import CartanType +from sage.combinat.root_system.coxeter_type import CoxeterType - INPUT: +class CoxeterMatrix(CoxeterType): + r""" + A Coxeter matrix. - - ``t`` -- a Cartan type + A Coxeter matrix `M = (m_{ij})_{i,j \in I}` is a matrix encoding + a Coxeter system `(W, S)`, where the relations are given by + `(s_i s_j)^{m_{ij}}`. Thus `M` is symmetric and has entries + in `\{1, 2, 3, \ldots, \infty\}` with `m_{ij} = 1` if and only + if `i = j`. - EXAMPLES:: + We represent `m_{ij} = \infty` by any number `m_{ij} \leq -1`. In + particular, we can construct a bilinear form `B = (b_{ij})_{i,j \in I}` + from `M` by - sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function - sage: f = coxeter_matrix_as_function(['A',4]) - sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) - [1 3 2 2] - [3 1 3 2] - [2 3 1 3] - [2 2 3 1] - """ - t = CartanType(t) - m = t.coxeter_matrix() - index_set = t.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - return lambda i,j: m[reverse[i], reverse[j]] + .. MATH:: -def coxeter_matrix(t): - """ - Returns the Coxeter matrix of type t. + b_{ij} = \begin{cases} + m_{ij} & m_{ij} < 0\ (\text{i.e., } m_{ij} = \infty), \\ + -\cos\left( \frac{\pi}{m_{ij}} \right) & \text{otherwise}. + \end{cases} EXAMPLES:: - sage: coxeter_matrix(['A', 4]) + sage: CoxeterMatrix(['A', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 3] [2 2 3 1] - sage: coxeter_matrix(['B', 4]) + sage: CoxeterMatrix(['B', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['C', 4]) + sage: CoxeterMatrix(['C', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['D', 4]) + sage: CoxeterMatrix(['D', 4]) [1 3 2 2] [3 1 3 3] [2 3 1 2] [2 3 2 1] - :: - - sage: coxeter_matrix(['E', 6]) + sage: CoxeterMatrix(['E', 6]) [1 2 3 2 2 2] [2 1 2 3 2 2] [3 2 1 3 2 2] @@ -80,18 +81,1120 @@ def coxeter_matrix(t): [2 2 2 3 1 3] [2 2 2 2 3 1] - :: - - sage: coxeter_matrix(['F', 4]) + sage: CoxeterMatrix(['F', 4]) [1 3 2 2] [3 1 4 2] [2 4 1 3] [2 2 3 1] - :: - - sage: coxeter_matrix(['G', 2]) + sage: CoxeterMatrix(['G', 2]) [1 6] [6 1] + + By default, entries representing `\infty` are given by `-1` + in the Coxeter matrix:: + + sage: G = Graph([(0,1,None), (1,2,4), (0,2,oo)]) + sage: CoxeterMatrix(G) + [ 1 3 -1] + [ 3 1 4] + [-1 4 1] + + It is possible to give a number `\leq -1` to represent an infinite label:: + + sage: CoxeterMatrix([[1,-1],[-1,1]]) + [ 1 -1] + [-1 1] + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]]) + [ 1 -3/2] + [-3/2 1] + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, data=None, index_set=None, coxeter_type=None, + cartan_type=None, coxeter_type_check=True): + r""" + A Coxeter matrix can we created via a graph, a Coxeter type, or + a matrix. + + .. NOTE:: + + To disable the Coxeter type check, use the optional argument + ``coxeter_type_check = False``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1],['a','b']) + sage: C2 = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: C3 = CoxeterMatrix(matrix([[1, -1], [-1, 1]]), [0, 1]) + sage: C == C2 and C == C3 + True + + Check with `\infty` because of the hack of using `-1` to represent + `\infty` in the Coxeter matrix:: + + sage: G = Graph([(0, 1, 3), (1, 2, oo)]) + sage: W1 = CoxeterMatrix([[1, 3, 2], [3, 1, -1], [2, -1, 1]]) + sage: W2 = CoxeterMatrix(G) + sage: W1 == W2 + True + sage: CoxeterMatrix(W1.coxeter_graph()) == W1 + True + + The base ring of the matrix depends on the entries given:: + + sage: CoxeterMatrix([[1,-1],[-1,1]])._matrix.base_ring() + Integer Ring + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]])._matrix.base_ring() + Rational Field + sage: CoxeterMatrix([[1,-1.5],[-1.5,1]])._matrix.base_ring() + Real Field with 53 bits of precision + """ + if not data: + if coxeter_type: + data = CoxeterType(coxeter_type) + elif cartan_type: + data = CoxeterType(CartanType(cartan_type)) + + # Special cases with no arguments passed + if not data: + data = [] + n = 0 + index_set = tuple() + coxeter_type = None + base_ring = ZZ + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), data, coxeter_type, index_set) + mat._subdivisions = None + + return mat + + if isinstance(data, CoxeterMatrix): # Initiate from itself + return data + + # Initiate from a graph: + # TODO: Check if a CoxeterDiagram once implemented + if isinstance(data, Graph): + return cls._from_graph(data, coxeter_type_check) + + # Get the Coxeter type + coxeter_type = None + from sage.combinat.root_system.cartan_type import CartanType_abstract + if isinstance(data, CartanType_abstract): + coxeter_type = data.coxeter_type() + else: + try: + coxeter_type = CoxeterType(data) + except (TypeError, ValueError, NotImplementedError): + pass + + # Initiate from a Coxeter type + if coxeter_type: + return cls._from_coxetertype(coxeter_type) + + # TODO:: remove when oo is possible in matrices. + n = len(data[0]) + data = [x if x != infinity else -1 for r in data for x in r] + data = matrix(n, n, data) + # until here + + # Get the index set + if index_set: + index_set = tuple(index_set) + else: + index_set = tuple(range(1,n+1)) + if len(set(index_set)) != n: + raise ValueError("the given index set is not valid") + + return cls._from_matrix(data, coxeter_type, index_set, coxeter_type_check) + + def __init__(self, parent, data, coxeter_type, index_set): + """ + Initialize ``self``. + + TESTS:: + + sage: C = CoxeterMatrix(['A', 2, 1]) + sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) + """ + self._matrix = Matrix_generic_dense(parent, data, False, True) + self._matrix.set_immutable() + + if self._matrix.base_ring() not in [ZZ, QQ]: + self._is_cyclotomic = False + else: + self._is_cyclotomic = True + self._coxeter_type = coxeter_type + + if self._coxeter_type is not None: + if self._coxeter_type.is_finite(): + self._is_finite = True + self._is_affine = False + elif self._coxeter_type.is_affine(): + self._is_finite = False + self._is_affine = True + else: + self._is_finite = False + self._is_affine = False + else: + self._is_finite = False + self._is_affine = False + + self._index_set = index_set + self._rank = self._matrix.nrows() + + self._dict = {(self._index_set[i], self._index_set[j]): self._matrix[i, j] + for i in range(self._rank) for j in range(self._rank)} + + for i,key in enumerate(self._index_set): + self._dict[key] = {key2: self._matrix[i,j] + for j,key2 in enumerate(self._index_set)} + + @classmethod + def _from_matrix(cls, data, coxeter_type, index_set, coxeter_type_check): + """ + Initiate the Coxeter matrix from a matrix. + + TESTS:: + + sage: CM = CoxeterMatrix([[1,2],[2,1]]); CM + [1 2] + [2 1] + sage: CM = CoxeterMatrix([[1,-1],[-1,1]]); CM + [ 1 -1] + [-1 1] + sage: CM = CoxeterMatrix([[1,-1.5],[-1.5,1]]); CM + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,-1],[5,-1,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,oo],[5,oo,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + """ + # Check that the data is valid + check_coxeter_matrix(data) + + M = matrix(data) + n = M.ncols() + + base_ring = M.base_ring() + + if not coxeter_type: + if n == 1: + coxeter_type = CoxeterType(['A', 1]) + elif coxeter_type_check: + coxeter_type = recognize_coxeter_type_from_matrix(M, index_set) + else: + coxeter_type = None + + raw_data = M.list() + + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), raw_data, + coxeter_type, index_set) + mat._subdivisions = M._subdivisions + + return mat + + @classmethod + def _from_graph(cls, graph, coxeter_type_check): + """ + Initiate the Coxeter matrix from a graph. + + TESTS:: + + sage: CoxeterMatrix(CoxeterMatrix(['A',4,1]).coxeter_graph()) + [1 3 2 2 3] + [3 1 3 2 2] + [2 3 1 3 2] + [2 2 3 1 3] + [3 2 2 3 1] + sage: CoxeterMatrix(CoxeterMatrix(['B',4,1]).coxeter_graph()) + [1 2 3 2 2] + [2 1 3 2 2] + [3 3 1 3 2] + [2 2 3 1 4] + [2 2 2 4 1] + sage: CoxeterMatrix(CoxeterMatrix(['F',4]).coxeter_graph()) + [1 3 2 2] + [3 1 4 2] + [2 4 1 3] + [2 2 3 1] + + sage: G=Graph() + sage: G.add_edge([0,1,oo]) + sage: CoxeterMatrix(G) + [ 1 -1] + [-1 1] + sage: H = Graph() + sage: H.add_edge([0,1,-1.5]) + sage: CoxeterMatrix(H) + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + """ + verts = sorted(graph.vertices()) + index_set = tuple(verts) + n = len(index_set) + + # Setup the basis matrix as all 2 except 1 on the diagonal + data = [] + for i in range(n): + data += [[]] + for j in range(n): + if i == j: + data[-1] += [ZZ.one()] + else: + data[-1] += [2] + + for e in graph.edges(): + label = e[2] + if label is None: + label = 3 + elif label == infinity: + label = -1 + elif label not in ZZ and label > -1: + raise ValueError("invalid Coxeter graph label") + elif label == 0 or label == 1: + raise ValueError("invalid Coxeter graph label") + i = verts.index(e[0]) + j = verts.index(e[1]) + data[j][i] = data[i][j] = label + + return cls._from_matrix(data, None, index_set, coxeter_type_check) + + @classmethod + def _from_coxetertype(cls, coxeter_type): + """ + Initiate the Coxeter matrix from a Coxeter type. + + TESTS:: + + sage: CoxeterMatrix(['A',4]).coxeter_type() + Coxeter type of ['A', 4] + sage: CoxeterMatrix(['A',4,1]).coxeter_type() + Coxeter type of ['A', 4, 1] + sage: CoxeterMatrix(['D',4,1]).coxeter_type() + Coxeter type of ['D', 4, 1] + """ + index_set = coxeter_type.index_set() + n = len(index_set) + reverse = {index_set[i]: i for i in range(n)} + data = [[1 if i == j else 2 for j in range(n)] for i in range(n)] + for (i, j, l) in coxeter_type.coxeter_graph().edge_iterator(): + if l == infinity: + l = -1 + data[reverse[i]][reverse[j]] = l + data[reverse[j]][reverse[i]] = l + + return cls._from_matrix(data, coxeter_type, index_set, False) + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None, higher_rank=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- (default: ``None``) a boolean or ``None`` + + - ``affine`` -- (default: ``None``) a boolean or ``None`` + + - ``crystallographic`` -- (default: ``None``) a boolean or ``None`` + + - ``higher_rank`` -- (default: ``None``) a boolean or ``None`` + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + Here the ``higher_rank`` term denotes non-finite, non-affine, + Coxeter groups (including hyperbolic types). + + .. TODO:: Implement the hyperbolic and compact hyperbolic in the samples. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(finite=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + Coxeter type of ['H', 4], Coxeter type of ['I', 10]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(affine=True)] + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(crystallographic=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1]] + + sage: CoxeterMatrix.samples(crystallographic=False) + [ + [1 3 2 2] + [1 3 2] [3 1 3 2] [ 1 -1 -1] [1 2 3] + [3 1 5] [2 3 1 5] [ 1 10] [ 1 -1] [-1 1 -1] [2 1 7] + [2 5 1], [2 2 5 1], [10 1], [-1 1], [-1 -1 1], [3 7 1], + + [ 1 -2 3 2] + [-2 1 2 3] + [ 3 2 1 -8] + [ 2 3 -8 1] + ] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterMatrix.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + if higher_rank is not None: + result = [t for t in result if not t.is_affine() and not t.is_finite()] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix._samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + """ + finite = [CoxeterMatrix(t) for t in [['A', 1], ['A', 5], ['B', 5], + ['D', 4], ['D', 5], ['E', 6], ['E', 7], + ['E', 8], ['F', 4], ['H', 3], ['H', 4], + ['I', 10]]] + + affine = [CoxeterMatrix(t) for t in [['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]]] + + higher_matrices = [[[1, -1, -1], [-1, 1, -1], [-1, -1, 1]], + [[1, 2, 3], [2, 1, 7], [3, 7, 1]], + [[1, -2, 3, 2], [-2, 1, 2, 3], [3, 2, 1, -8], [2, 3, -8, 1]]] + + higher = [CoxeterMatrix(m) for m in higher_matrices] + + return finite + affine + higher + + def relabel(self, relabelling): + """ + Return a relabelled copy of this Coxeter matrix. + + INPUT: + + - ``relabelling`` -- a function (or dictionary) + + OUTPUT: + + an isomorphic Coxeter type obtained by relabelling the nodes of + the Coxeter graph. Namely, the node with label ``i`` is + relabelled ``f(i)`` (or, by ``f[i]`` if ``f`` is a dictionary). + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).relabel({ 1:2, 2:3, 3:4, 4:1}) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + sage: CoxeterMatrix(['F',4]).relabel(lambda x: x+1 if x<4 else 1) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + """ + if isinstance(relabelling, dict): + data = [[self[relabelling[i]][relabelling[j]] + for j in self.index_set()] for i in self.index_set()] + else: + data = [[self[relabelling(i)][relabelling(j)] + for j in self.index_set()] for i in self.index_set()] + + return CoxeterMatrix(data) + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: C = CoxeterMatrix(['A',4]) + sage: M = loads(dumps(C)) + sage: M._index_set + (1, 2, 3, 4) + """ + if self._coxeter_type: + return (CoxeterMatrix, (self._coxeter_type,)) + return (CoxeterMatrix, (self._matrix, self._index_set)) + + def _repr_(self): + """ + String representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]); CM + [1 3 2] + [3 1 3] + [2 3 1] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + """ + return self._matrix.__repr__() + + def _repr_option(self, key): + """ + Metadata about the :meth:`_repr_` output. + + See :meth:`sage.structure.parent._repr_option` for details. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: CM._repr_option('ascii_art') + True + """ + if key == 'ascii_art' or key == 'element_ascii_art': + return self._matrix.nrows() > 1 + return super(CoxeterMatrix, self)._repr_option(key) + + def _latex_(self): + r""" + Latex representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: latex(CM) + \left(\begin{array}{rrr} + 1 & 3 & 2 \\ + 3 & 1 & 3 \\ + 2 & 3 & 1 + \end{array}\right) + """ + return self._matrix._latex_() + + + def __iter__(self): + """ + Return an iterator for the rows of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,8],[8,1]]) + sage: CM.__iter__().next() + (1, 8) + """ + return self._matrix.__iter__() + + def __getitem__(self, key): + """ + Return a dictionary of labels adjacent to a node or + the label of an edge in the Coxeter graph. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]]) + sage: CM = CoxeterMatrix([[1,-2],[-2,1]], ['a','b']) + sage: CM['a'] + {'a': 1, 'b': -2} + sage: CM['b'] + {'a': -2, 'b': 1} + sage: CM['a','b'] + -2 + sage: CM['a','a'] + 1 + """ + return self._dict[key] + + def __hash__(self): + r""" + Return hash of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__hash__() + + def __eq__(self, other): + r""" + Return if ``self`` and ``other`` are equal, ``False`` otherwise. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__eq__(other._matrix) + + def _matrix_(self, R=None): + """ + Return ``self`` as a matrix over the ring ``R``. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-3],[-3,1]]) + sage: matrix(CM) + [ 1 -3] + [-3 1] + sage: matrix(CM,RR) + [ 1.00000000000000 -3.00000000000000] + [-3.00000000000000 1.00000000000000] + """ + if R is not None: + return self._matrix.change_ring(R) + else: + return self._matrix + + ########################################################################## + # Coxeter type methods + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1]) + sage: C.index_set() + (0, 1) + sage: C = CoxeterMatrix(['E',6]) + sage: C.index_set() + (1, 2, 3, 4, 5, 6) + """ + return self._index_set + + def coxeter_type(self): + """ + Return the Coxeter type of ``self`` or ``self`` if unknown. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',4,1]) + sage: C.coxeter_type() + Coxeter type of ['A', 4, 1] + + If the Coxeter type is unknown:: + + sage: C = CoxeterMatrix([[1,3,4], [3,1,-1], [4,-1,1]]) + sage: C.coxeter_type() + [ 1 3 4] + [ 3 1 -1] + [ 4 -1 1] + """ + if self._coxeter_type is None: + return self + return self._coxeter_type + + def rank(self): + r""" + Return the rank of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).rank() + 3 + sage: CoxeterMatrix(["A2","B2","F4"]).rank() + 8 + """ + return self._rank + + def coxeter_matrix(self): + r""" + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).coxeter_matrix() + [1 3 2] + [3 1 4] + [2 4 1] + """ + return self + + def bilinear_form(self): + r""" + Return the bilinear form of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + return CoxeterType.bilinear_form(self) + + @cached_method + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',3]) + sage: C.coxeter_graph() + Graph on 3 vertices + + sage: C = CoxeterMatrix([['A',3],['A',1]]) + sage: C.coxeter_graph() + Graph on 4 vertices + """ + n = self.rank() + I = self.index_set() + val = lambda x: infinity if x == -1 else x + G = Graph([(I[i], I[j], val((self._matrix)[i, j])) + for i in range(n) for j in range(i) + if self._matrix[i, j] not in [1, 2]]) + G.add_vertices(I) + return G.copy(immutable = True) + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + A Coxeter matrix is simply-laced if all non-diagonal entries are + either 2 or 3. + + EXAMPLES:: + + sage: cm = CoxeterMatrix([[1,3,3,3], [3,1,3,3], [3,3,1,3], [3,3,3,1]]) + sage: cm.is_simply_laced() + True + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3] + return all(x in L for row in self for x in row) + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + A Coxeter matrix is crystallographic if all non-diagonal entries + are either 2, 4, or 6. + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).is_crystallographic() + True + sage: CoxeterMatrix(['H',3]).is_crystallographic() + False + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3, 4, 6] + return all(x in L for row in self for x in row) + + def is_finite(self): + """ + Return if ``self`` is a finite type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_finite() + True + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_finite() + False + sage: M = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: M.is_finite() + False + """ + return self._is_finite + + def is_affine(self): + """ + Return if ``self`` is an affine type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_affine() + True + sage: M = CoxeterMatrix([[1, 3],[3,1]]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix([[1, -1, 7], [-1, 1, 3], [7, 3, 1]]) + sage: M.is_affine() + False + """ + return self._is_affine + + +##################################################################### +## Type check functions + +def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): """ - return CartanType(t).coxeter_matrix() + Return the Coxeter type of ``coxeter_matrix`` if known, + otherwise return ``None``. + + EXAMPLES: + + Some infinite ones:: + + sage: C = CoxeterMatrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: C.is_finite() # indirect doctest + False + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.is_finite() # indirect doctest + False + + Some finite ones:: + + sage: m = matrix(CoxeterMatrix(['D', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + sage: m = matrix(CoxeterMatrix(['H', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + + sage: CoxeterMatrix(CoxeterType(['A',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10] + sage: CoxeterMatrix(CoxeterType(['B',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['C',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['D',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10] + sage: CoxeterMatrix(CoxeterType(['E',6]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6] + sage: CoxeterMatrix(CoxeterType(['E',7]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7] + sage: CoxeterMatrix(CoxeterType(['E',8]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8] + sage: CoxeterMatrix(CoxeterType(['F',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4] + sage: CoxeterMatrix(CoxeterType(['G',2]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2] + sage: CoxeterMatrix(CoxeterType(['H',3]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 3] + sage: CoxeterMatrix(CoxeterType(['H',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 4] + sage: CoxeterMatrix(CoxeterType(['I',100]).coxeter_graph()).coxeter_type() + Coxeter type of ['I', 100] + + Some affine graphs:: + + sage: CoxeterMatrix(CoxeterType(['A',1,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 1, 1] + sage: CoxeterMatrix(CoxeterType(['A',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10, 1] + sage: CoxeterMatrix(CoxeterType(['B',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10, 1] + sage: CoxeterMatrix(CoxeterType(['C',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['C', 10, 1] + sage: CoxeterMatrix(CoxeterType(['D',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10, 1] + sage: CoxeterMatrix(CoxeterType(['E',6,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6, 1] + sage: CoxeterMatrix(CoxeterType(['E',7,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7, 1] + sage: CoxeterMatrix(CoxeterType(['E',8,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8, 1] + sage: CoxeterMatrix(CoxeterType(['F',4,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4, 1] + sage: CoxeterMatrix(CoxeterType(['G',2,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2, 1] + + TESTS: + + Check that we detect relabellings:: + + sage: M = CoxeterMatrix([[1,2,3],[2,1,6],[3,6,1]], index_set=['a', 'b', 'c']) + sage: M.coxeter_type() + Coxeter type of ['G', 2, 1] relabelled by {0: 'a', 1: 'b', 2: 'c'} + + sage: from sage.combinat.root_system.coxeter_matrix import recognize_coxeter_type_from_matrix + sage: for C in CoxeterMatrix.samples(): + ....: relabelling_perm = Permutations(C.index_set()).random_element() + ....: relabelling_dict = {C.index_set()[i]: relabelling_perm[i] for i in range(C.rank())} + ....: relabeled_matrix = C.relabel(relabelling_dict)._matrix + ....: recognized_type = recognize_coxeter_type_from_matrix(relabeled_matrix, relabelling_perm) + ....: if C.is_finite() or C.is_affine(): + ....: assert recognized_type == C.coxeter_type() + """ + # First, we build the Coxeter graph of the group without the edge labels + n = ZZ(coxeter_matrix.nrows()) + G = Graph([[index_set[i], index_set[j], coxeter_matrix[i, j]] + for i in range(n) for j in range(i,n) + if coxeter_matrix[i, j] not in [1, 2]]) + G.add_vertices(index_set) + + types = [] + for S in G.connected_components_subgraphs(): + r = S.num_verts() + # Handle the special cases first + if r == 1: + types.append(CoxeterType(['A',1]).relabel({1: S.vertices()[0]})) + continue + if r == 2: # Type B2, G2, or I_2(p) + e = S.edge_labels()[0] + if e == 3: # Can't be 2 because it is connected + ct = CoxeterType(['B',2]) + elif e == 4: + ct = CoxeterType(['G',2]) + elif e > 0 and e < float('inf'): # Remaining non-affine types + ct = CoxeterType(['I',e]) + else: # Otherwise it is infinite dihedral group Z_2 \ast Z_2 + ct = CoxeterType(['A',1,1]) + if not ct.is_affine(): + types.append(ct.relabel({1: S.vertices()[0], 2: S.vertices()[1]})) + else: + types.append(ct.relabel({0: S.vertices()[0], 1: S.vertices()[1]})) + continue + + test = [['A',r], ['B',r], ['A',r-1,1]] + if r >= 3: + if r == 3: + test += [['G',2,1], ['H',3]] + test.append(['C',r-1,1]) + if r >= 4: + if r == 4: + test += [['F',4], ['H',4]] + test += [['D',r], ['B',r-1,1]] + if r >= 5: + if r == 5: + test.append(['F',4,1]) + test.append(['D',r-1,1]) + if r == 6: + test.append(['E',6]) + elif r == 7: + test += [['E',7], ['E',6,1]] + elif r == 8: + test += [['E',8], ['E',7,1]] + elif r == 9: + test.append(['E',8,1]) + + found = False + for ct in test: + ct = CoxeterType(ct) + T = ct.coxeter_graph() + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CoxeterType(types) + +##################################################################### +## Other functions + +def check_coxeter_matrix(m): + """ + Check if ``m`` represents a generalized Coxeter matrix and raise + and error if not. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import check_coxeter_matrix + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + + sage: m = matrix([[1,3],[3,1],[2,-1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: not a square matrix + + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,2]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix diagonal is not all 1 + + sage: m = matrix([[1,3,3],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix is not symmetric + + sage: m = matrix([[1,3,1/2],[3,1,-1],[1/2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1/2 + + sage: m = matrix([[1,3,1],[3,1,-1],[1,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1 + """ + mat = matrix(m) + if not mat.is_square(): + raise ValueError("not a square matrix") + for i, row in enumerate(m): + if mat[i, i] != 1: + raise ValueError("the matrix diagonal is not all 1") + for j, val in enumerate(row[i+1:]): + if val != m[j+i+1][i]: + raise ValueError("the matrix is not symmetric") + if val not in ZZ: + if val > -1 and val in RR and val != infinity: + raise ValueError("invalid Coxeter label {}".format(val)) + else: + if val == 1 or val == 0: + raise ValueError("invalid Coxeter label {}".format(val)) + +def coxeter_matrix_as_function(t): + """ + Return the Coxeter matrix, as a function. + + INPUT: + + - ``t`` -- a Cartan type + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function + sage: f = coxeter_matrix_as_function(['A',4]) + sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + t = CartanType(t) + m = t.coxeter_matrix() + return lambda i, j: m[i, j] + +def coxeter_matrix(t): + """ + This was deprecated in :trac:`17798` for :class:`CartanMatrix`. + + EXAMPLES:: + + sage: coxeter_matrix(['A', 4]) + doctest:...: DeprecationWarning: coxeter_matrix() is deprecated. Use CoxeterMatrix() instead + See http://trac.sagemath.org/17798 for details. + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + from sage.misc.superseded import deprecation + deprecation(17798, 'coxeter_matrix() is deprecated. Use CoxeterMatrix() instead') + return CoxeterMatrix(t) + diff --git a/src/sage/combinat/root_system/coxeter_type.py b/src/sage/combinat/root_system/coxeter_type.py new file mode 100644 index 00000000000..ecbd5ab6a20 --- /dev/null +++ b/src/sage/combinat/root_system/coxeter_type.py @@ -0,0 +1,576 @@ +""" +Coxeter Types +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw , +# 2015 Jean-Philippe Labbe , +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.combinat.root_system.cartan_type import CartanType +from sage.matrix.all import MatrixSpace +from sage.symbolic.ring import SR +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.sage_object import SageObject + + +class CoxeterType(SageObject): + """ + Abstract class for Coxeter types. + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, *x): + """ + Parse input ``x``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + if len(x) == 1: + x = x[0] + + if isinstance(x, CoxeterType): + return x + + try: + return CoxeterTypeFromCartanType(CartanType(x)) + except (ValueError, TypeError): + pass + + if len(x) == 1: # In case the input is similar to CoxeterType([['A',2]]) + return CoxeterType(x[0]) + + raise NotImplementedError("Coxeter types not from Cartan types not yet implemented") + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- a boolean or ``None`` (default: ``None``) + + - ``affine`` -- a boolean or ``None`` (default: ``None``) + + - ``crystallographic`` -- a boolean or ``None`` (default: ``None``) + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + EXAMPLES:: + + sage: CoxeterType.samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: CoxeterType.samples(finite=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + sage: CoxeterType.samples(affine=True) + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=False) + [Coxeter type of ['H', 3], + Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterType.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: + + This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: CoxeterType._samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + """ + finite = [CoxeterType(t) for t in [['A', 1], ['A', 5], ['B', 1], ['B', 5], + ['C', 1], ['C', 5], ['D', 4], ['D', 5], + ['E', 6], ['E', 7], ['E', 8], ['F', 4], + ['H', 3], ['H', 4], ['I', 10]]] + + affine = [CoxeterType(t) for t in ['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]] + + return finite + affine + + @abstract_method + def rank(self): + """ + Return the rank of ``self``. + + This is the number of nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 4]).rank() + 4 + sage: CoxeterType(['A', 7, 2]).rank() + 5 + sage: CoxeterType(['I', 8]).rank() + 2 + """ + + @abstract_method + def index_set(self): + """ + Return the index set for ``self``. + + This is the list of the nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 3, 1]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 4]).index_set() + (1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 6, 2]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 6, 2]).index_set() + (0, 1, 2, 3, 4, 5) + sage: CoxeterType(['E', 6, 1]).index_set() + (0, 1, 2, 3, 4, 5, 6) + sage: CoxeterType(['E', 6, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 2, 2]).index_set() + (0, 1) + sage: CoxeterType(['G', 2, 1]).index_set() + (0, 1, 2) + sage: CoxeterType(['F', 4, 1]).index_set() + (0, 1, 2, 3, 4) + """ + + @abstract_method + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_matrix() + [1 3 2] + [3 1 3] + [2 3 1] + sage: CoxeterType(['A', 3, 1]).coxeter_matrix() + [1 3 2 3] + [3 1 3 2] + [2 3 1 3] + [3 2 3 1] + """ + + @abstract_method + def coxeter_graph(self): + """ + Return the Coxeter graph associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_graph() + Graph on 3 vertices + sage: CoxeterType(['A', 3, 1]).coxeter_graph() + Graph on 4 vertices + """ + + @abstract_method + def is_finite(self): + """ + Return whether ``self`` is finite. + + EXAMPLES:: + + sage: CoxeterType(['A',4]).is_finite() + True + sage: CoxeterType(['A',4, 1]).is_finite() + False + """ + + @abstract_method + def is_affine(self): + """ + Return whether ``self`` is affine. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).is_affine() + False + sage: CoxeterType(['A', 3, 1]).is_affine() + True + """ + + def is_crystallographic(self): + """ + Return whether ``self`` is crystallographic. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_crystallographic() ] for t in CartanType.samples(finite=True) ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], True], + [['C', 1], True], [['C', 5], True], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], True], [['G', 2], True], + [['I', 5], False], [['H', 3], False], [['H', 4], False]] + """ + return False + + def is_simply_laced(self): + """ + Return whether ``self`` is simply laced. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_simply_laced() ] for t in CartanType.samples() ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], False], + [['C', 1], True], [['C', 5], False], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], False], [['G', 2], False], + [['I', 5], False], [['H', 3], False], [['H', 4], False], + [['A', 1, 1], False], [['A', 5, 1], True], + [['B', 1, 1], False], [['B', 5, 1], False], + [['C', 1, 1], False], [['C', 5, 1], False], + [['D', 3, 1], True], [['D', 5, 1], True], + [['E', 6, 1], True], [['E', 7, 1], True], [['E', 8, 1], True], + [['F', 4, 1], False], [['G', 2, 1], False], + [['BC', 1, 2], False], [['BC', 5, 2], False], + [['B', 5, 1]^*, False], [['C', 4, 1]^*, False], + [['F', 4, 1]^*, False], [['G', 2, 1]^*, False], + [['BC', 1, 2]^*, False], [['BC', 5, 2]^*, False]] + """ + return False + + @cached_method + def bilinear_form(self, R=None): + """ + Return the bilinear form over ``R`` associated to ``self``. + + INPUT: + + - ``R`` -- (default: universal cyclotomic field) a ring used to + compute the bilinear form + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + + n = self.rank() + mat = self.coxeter_matrix()._matrix + base_ring = mat.base_ring() + + from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField + UCF = UniversalCyclotomicField() + if UCF.has_coerce_map_from(base_ring): + R = UCF + else: + R = base_ring + # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. + if R is UCF: + val = lambda x: (R.gen(2*x) + ~R.gen(2*x)) / R(-2) if x > -1 else R.one()*x + else: + from sage.functions.trig import cos + from sage.symbolic.constants import pi + val = lambda x: -R(cos(pi / SR(x))) if x > -1 else x + + MS = MatrixSpace(R, n, sparse=True) + MC = MS._get_matrix_class() + + bilinear = MC(MS, entries={(i, j): val(mat[i, j]) + for i in range(n) for j in range(n) + if mat[i, j] != 2}, + coerce=True, copy=True) + bilinear.set_immutable() + return bilinear + + +class CoxeterTypeFromCartanType(CoxeterType, UniqueRepresentation): + """ + A Coxeter type associated to a Cartan type. + """ + @staticmethod + def __classcall_private__(cls, cartan_type): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_type import CoxeterTypeFromCartanType + sage: C1 = CoxeterTypeFromCartanType(['A',3]) + sage: C2 = CoxeterTypeFromCartanType(CartanType(['A',3])) + sage: C1 is C2 + True + """ + return super(CoxeterTypeFromCartanType, cls).__classcall__(cls, + CartanType(cartan_type)) + + def __init__(self, cartan_type): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A',3]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['H',4]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['C',3,1]) + sage: TestSuite(C).run() + """ + self._cartan_type = cartan_type + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + return "Coxeter type of {}".format(self._cartan_type) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_matrix() + [1 3 2] + [3 1 5] + [2 5 1] + """ + return self._cartan_type.coxeter_matrix() + + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_graph().edges() + [(1, 2, 3), (2, 3, 5)] + """ + return self._cartan_type.coxeter_diagram() + + def cartan_type(self): + """ + Return the Cartan type used to construct ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['C',3]) + sage: C.cartan_type() + ['C', 3] + """ + return self._cartan_type + + def rank(self): + """ + Return the rank of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['I', 16]) + sage: C.rank() + 2 + """ + return self._cartan_type.rank() + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 4]) + sage: C.index_set() + (1, 2, 3, 4) + """ + return self._cartan_type.index_set() + + def is_finite(self): + """ + Return if ``self`` is a finite type. + + EXAMPLES:: + + sage: C = CoxeterType(['E', 6]) + sage: C.is_finite() + True + """ + return self._cartan_type.is_finite() + + def is_affine(self): + """ + Return if ``self`` is an affine type. + + EXAMPLES:: + + sage: C = CoxeterType(['F', 4, 1]) + sage: C.is_affine() + True + """ + return self._cartan_type.is_affine() + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + EXAMPLES:: + + sage: C = CoxeterType(['C', 3]) + sage: C.is_crystallographic() + True + + sage: C = CoxeterType(['H', 3]) + sage: C.is_crystallographic() + False + """ + return self._cartan_type.is_crystallographic() + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 5]) + sage: C.is_simply_laced() + True + + sage: C = CoxeterType(['B', 3]) + sage: C.is_simply_laced() + False + """ + return self._cartan_type.is_simply_laced() + + def relabel(self, relabelling): + """ + Return a relabelled copy of ``self``. + + EXAMPLES:: + + sage: ct = CoxeterType(['A',2]) + sage: ct.relabel({1:-1, 2:-2}) + Coxeter type of ['A', 2] relabelled by {1: -1, 2: -2} + """ + return CoxeterType(self._cartan_type.relabel(relabelling)) + diff --git a/src/sage/combinat/root_system/dynkin_diagram.py b/src/sage/combinat/root_system/dynkin_diagram.py index d3fdd03aaf1..bebe22fabbf 100644 --- a/src/sage/combinat/root_system/dynkin_diagram.py +++ b/src/sage/combinat/root_system/dynkin_diagram.py @@ -32,7 +32,6 @@ from sage.graphs.digraph import DiGraph from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.cartan_matrix import CartanMatrix -from sage.misc.superseded import deprecated_function_alias def DynkinDiagram(*args, **kwds): r""" @@ -56,10 +55,10 @@ def DynkinDiagram(*args, **kwds): in Bourbaki and Wikipedia's Dynkin diagram (:wikipedia:`Dynkin_diagram`). That is for `i \neq j`:: - i <--k-- j <==> a_ij = -k - <==> -scalar(coroot[i], root[j]) = k - <==> multiple arrows point from the longer root - to the shorter one + i <--k-- j <==> a_ij = -k + <==> -scalar(coroot[i], root[j]) = k + <==> multiple arrows point from the longer root + to the shorter one For example, in type `C_2`, we have:: @@ -564,9 +563,7 @@ def subtype(self, index_set): 0 1 2 3 BC3~ sage: D.subtype([1,2,3]) - O---O=<=O - 1 2 3 - C3 + Dynkin diagram of rank 3 """ return self.cartan_matrix().subtype(index_set).dynkin_diagram() @@ -626,15 +623,11 @@ def is_crystallographic(self): TESTS:: - sage: CartanType(['G',2]).dynkin_diagram().is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. + sage: CartanType(['G',2]).dynkin_diagram().is_crystallographic() True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def symmetrizer(self): """ Return the symmetrizer of the corresponding Cartan matrix. @@ -668,13 +661,13 @@ def symmetrizer(self): def __getitem__(self, i): r""" With a tuple (i,j) as argument, returns the scalar product - `\langle - \alpha^\vee_i, \alpha_j\rangle`. + `\langle \alpha^\vee_i, \alpha_j\rangle`. + + Otherwise, behaves as the usual ``DiGraph.__getitem__`` - Otherwise, behaves as the usual DiGraph.__getitem__ + EXAMPLES: - EXAMPLES: We use the `C_4` Dynkin diagram as a cartan - matrix:: + We use the `C_4` Dynkin diagram as a cartan matrix:: sage: g = DynkinDiagram(['C',4]) sage: matrix([[g[i,j] for j in range(1,5)] for i in range(1,5)]) diff --git a/src/sage/combinat/root_system/hecke_algebra_representation.py b/src/sage/combinat/root_system/hecke_algebra_representation.py index 2e675681ea1..aa5bfbd67ec 100644 --- a/src/sage/combinat/root_system/hecke_algebra_representation.py +++ b/src/sage/combinat/root_system/hecke_algebra_representation.py @@ -11,6 +11,7 @@ import functools from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation from sage.sets.family import Family @@ -18,7 +19,7 @@ from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ -class HeckeAlgebraRepresentation(SageObject): +class HeckeAlgebraRepresentation(WithEqualityById, SageObject): r""" A representation of an (affine) Hecke algebra given by the action of the `T` generators @@ -64,6 +65,16 @@ class HeckeAlgebraRepresentation(SageObject): sage: H.Y() Lazy family (...)_{i in Coroot lattice of the Root system of type ['A', 3, 1]} + TESTS:: + + sage: from sage.combinat.root_system.hecke_algebra_representation import HeckeAlgebraRepresentation + sage: W = SymmetricGroup(3) + sage: domain = W.algebra(QQ) + sage: action = lambda x,i: domain.monomial(x.apply_simple_reflection(i, side="right")) + sage: r = HeckeAlgebraRepresentation(domain, action, CartanType(["A",2]), 1, -1) + sage: hash(r) # random + 3 + REFERENCES: .. [HST2008] F. Hivert, A. Schilling, N. Thiery, diff --git a/src/sage/combinat/root_system/integrable_representations.py b/src/sage/combinat/root_system/integrable_representations.py index c15ebff7444..14e3386db3d 100644 --- a/src/sage/combinat/root_system/integrable_representations.py +++ b/src/sage/combinat/root_system/integrable_representations.py @@ -5,6 +5,7 @@ #***************************************************************************** # Copyright (C) 2014, 2105 Daniel Bump # Travis Scrimshaw +# Valentin Buciumas # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ @@ -20,7 +21,7 @@ from sage.combinat.root_system.weyl_characters import WeylCharacterRing # TODO: Make this a proper parent and implement actions -class IntegrableRepresentation(CategoryObject, UniqueRepresentation): +class IntegrableRepresentation(UniqueRepresentation, CategoryObject): r""" An irreducible integrable highest weight representation of an affine Lie algebra. @@ -42,6 +43,9 @@ class IntegrableRepresentation(CategoryObject, UniqueRepresentation): .. [KacPeterson] Kac and Peterson. *Infinite-dimensional Lie algebras, theta functions and modular forms*. Adv. in Math. 53 (1984), no. 2, 125-264. + + .. [Carter] Carter, *Lie algebras of finite and affine type*. Cambridge + University Press, 2005 If `\Lambda` is a dominant integral weight for an affine root system, there exists a unique integrable representation `V=V_\Lambda` of highest @@ -147,6 +151,31 @@ class IntegrableRepresentation(CategoryObject, UniqueRepresentation): Lambda[0] + Lambda[2] - delta: 1 5 18 55 149 372 872 1941 4141 8523 17005 33019 2*Lambda[1] - delta: 1 4 15 44 122 304 721 1612 3469 7176 14414 28124 2*Lambda[2] - 2*delta: 2 7 26 72 194 467 1084 2367 5010 10191 20198 38907 + + Examples for twisted affine types:: + + sage: Lambda = RootSystem(["A",2,2]).weight_lattice(extended=True).fundamental_weights() + sage: IntegrableRepresentation(Lambda[0]).strings() + {Lambda[0]: [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56]} + sage: Lambda = RootSystem(['G',2,1]).dual.weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[2]) + sage: V.print_strings() # long time + 6*Lambdacheck[0]: 4 28 100 320 944 2460 6064 14300 31968 69020 144676 293916 + 4*Lambdacheck[0] + Lambdacheck[2]: 4 22 84 276 800 2124 5288 12470 28116 61056 128304 261972 + 3*Lambdacheck[0] + Lambdacheck[1]: 2 16 58 192 588 1568 3952 9520 21644 47456 100906 207536 + Lambdacheck[0] + Lambdacheck[1] + Lambdacheck[2]: 1 6 26 94 294 832 2184 5388 12634 28390 61488 128976 + 2*Lambdacheck[1] - deltacheck: 2 8 32 120 354 980 2576 6244 14498 32480 69776 145528 + 2*Lambdacheck[0] + 2*Lambdacheck[2]: 2 12 48 164 492 1344 3428 8256 18960 41844 89208 184512 + 3*Lambdacheck[2] - deltacheck: 4 16 60 208 592 1584 4032 9552 21728 47776 101068 207888 + sage: Lambda = RootSystem(['A',6,2]).weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+2*Lambda[1]) + sage: V.print_strings() # long time + 5*Lambda[0]: 3 42 378 2508 13707 64650 272211 1045470 3721815 12425064 39254163 118191378 + 3*Lambda[0] + Lambda[2]: 1 23 234 1690 9689 47313 204247 800029 2893198 9786257 31262198 95035357 + Lambda[0] + 2*Lambda[1]: 1 14 154 1160 6920 34756 153523 612354 2248318 7702198 24875351 76341630 + Lambda[0] + Lambda[1] + Lambda[3] - 2*delta: 6 87 751 4779 25060 113971 464842 1736620 6034717 19723537 61152367 181068152 + Lambda[0] + 2*Lambda[2] - 2*delta: 3 54 499 3349 18166 84836 353092 1341250 4725259 15625727 48938396 146190544 + Lambda[0] + 2*Lambda[3] - 4*delta: 15 195 1539 9186 45804 200073 789201 2866560 9723582 31120281 94724550 275919741 """ def __init__(self, Lam): """ @@ -160,16 +189,16 @@ def __init__(self, Lam): """ CategoryObject.__init__(self, base=ZZ, category=Modules(ZZ)) - if not Lam.parent().cartan_type().is_affine() or not Lam.parent()._extended: - raise ValueError("the parent of %s must be an extended affine root lattice"%Lam) self._Lam = Lam self._P = Lam.parent() self._Q = self._P.root_system.root_lattice() + # Store some extra simple computations that appear in tight loops + self._Lam_rho = self._Lam + self._P.rho() + self._cartan_matrix = self._P.root_system.cartan_matrix() self._cartan_type = self._P.root_system.cartan_type() - if not self._cartan_type.is_untwisted_affine(): - raise NotImplementedError("integrable representations are only implemented for untwisted affine types") + self._classical_rank = self._cartan_type.classical().rank() self._index_set = self._P.index_set() self._index_set_classical = self._cartan_type.classical().index_set() @@ -186,11 +215,14 @@ def __init__(self, Lam): self._a = self._cartan_type.a() # This is not cached self._ac = self._cartan_type.dual().a() # This is not cached self._eps = {i: self._a[i] / self._ac[i] for i in self._index_set} - self._coxeter_number = sum(self._a) - self._dual_coxeter_number = sum(self._ac) E = Matrix.diagonal([self._eps[i] for i in self._index_set_classical]) self._ip = (self._cartan_type.classical().cartan_matrix()*E).inverse() + # Extra data for the twisted cases + if not self._cartan_type.is_untwisted_affine(): + self._classical_short_roots = frozenset(al for al in self._classical_roots + if self._inner_qq(al,al) == 2) + def highest_weight(self): """ Returns the highest weight of ``self``. @@ -243,6 +275,7 @@ def level(self): """ return ZZ(self._inner_pq(self._Lam, self._Q.null_root())) + @cached_method def coxeter_number(self): """ Return the Coxeter number of the Cartan type of ``self``. @@ -257,8 +290,9 @@ def coxeter_number(self): sage: V.coxeter_number() 12 """ - return self._coxeter_number + return sum(self._a) + @cached_method def dual_coxeter_number(self): r""" Return the dual Coxeter number of the Cartan type of ``self``. @@ -273,7 +307,7 @@ def dual_coxeter_number(self): sage: V.dual_coxeter_number() 9 """ - return self._dual_coxeter_number + return sum(self._ac) def _repr_(self): """ @@ -570,8 +604,8 @@ def to_dominant(self, n): def _freudenthal_roots_imaginary(self, nu): r""" - Return the set of imaginary roots `\alpha \in \Delta^+` in ``self`` - such that `\nu - \alpha \in Q^+`. + Iterate over the set of imaginary roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. INPUT: @@ -581,20 +615,24 @@ def _freudenthal_roots_imaginary(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) - sage: [V._freudenthal_roots_imaginary(V.highest_weight() - mw) + sage: [list(V._freudenthal_roots_imaginary(V.highest_weight() - mw)) ....: for mw in V.dominant_maximal_weights()] [[], [], [], [], []] """ l = self._from_weight_helper(nu) kp = min(l[i] // self._a[i] for i in self._index_set) delta = self._Q.null_root() - return [u * delta for u in range(1, kp+1)] + for u in range(1, kp+1): + yield u * delta def _freudenthal_roots_real(self, nu): r""" - Return the set of real positive roots `\alpha \in \Delta^+` in - ``self`` such that `\nu - \alpha \in Q^+`. - + Iterate over the set of real positive roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. + + See [Kac]_ Proposition 6.3 for the way to compute the set of real + roots for twisted affine case. + INPUT: - ``nu`` -- an element in `Q` @@ -604,7 +642,7 @@ def _freudenthal_roots_real(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) sage: mw = V.dominant_maximal_weights()[0] - sage: V._freudenthal_roots_real(V.highest_weight() - mw) + sage: list(V._freudenthal_roots_real(V.highest_weight() - mw)) [alpha[1], alpha[2], alpha[3], @@ -612,14 +650,66 @@ def _freudenthal_roots_real(self, nu): alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[3]] """ - ret = [] for al in self._classical_positive_roots: - if all(x >= 0 for x in self._from_weight_helper(nu-al)): - ret.append(al) - for al in self._classical_roots: - for ir in self._freudenthal_roots_imaginary(nu-al): - ret.append(al+ir) - return ret + if min(self._from_weight_helper(nu-al)) >= 0: + yield al + + if self._cartan_type.is_untwisted_affine(): + # untwisted case + for al in self._classical_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + + elif self._cartan_type.type() == 'BC': + #case A^2_{2l} + # We have to keep track of the roots we have visted for this case + ret = set(self._classical_positive_roots) + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + ret.add(al + ir) + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + ret.add(al + 2*ir) + yield al + 2*ir + alpha = self._Q.simple_roots() + fri = list(self._freudenthal_roots_imaginary(2*nu-al)) + for ir in fri[::2]: + rt = sum( val // 2 * alpha[i] for i,val in + enumerate(self._from_weight_helper(al+ir)) ) + if rt not in ret: + ret.add(rt) + yield rt + + elif self._cartan_type.dual().type() == 'G': + # case D^3_4 in the Kac notation + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 3*ir in friset: + yield al + 3*ir + + elif self._cartan_type.dual().type() in ['B','C','F']: + #case A^2_{2l-1} or case D^2_{l+1} or case E^2_6: + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + yield al + 2*ir def _freudenthal_accum(self, nu, al): """ @@ -640,21 +730,25 @@ def _freudenthal_accum(self, nu, al): n_shift = self._from_weight_helper(al) ip_shift = self._inner_qq(al, al) - while all(val >= 0 for val in n): + while min(n) >= 0: # Change in data by adding ``al`` to our current weight ip += ip_shift for i,val in enumerate(n_shift): n[i] -= val # Compute the multiplicity - mk = self.m(tuple(n)) - ret += 2*mk*ip + ret += 2 * self.m(tuple(n)) * ip return ret def _m_freudenthal(self, n): - """ + r""" Compute the weight multiplicity using the Freudenthal multiplicity formula in ``self``. + The multiplicities of the imaginary roots for the twisted + affine case are different than those for the untwisted case. + See [Carter]_ Corollary 18.10 for general type and Corollary + 18.15 for `A^2_{2l}` + EXAMPLES:: sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() @@ -670,12 +764,48 @@ def _m_freudenthal(self, n): I = self._index_set al = self._Q._from_dict({I[i]: val for i,val in enumerate(n) if val}, remove_zeros=False) - den = 2*self._inner_pq(self._Lam+self._P.rho(), al) - self._inner_qq(al, al) - num = 0 - for al in self._freudenthal_roots_real(self._Lam - mu): - num += self._freudenthal_accum(mu, al) - for al in self._freudenthal_roots_imaginary(self._Lam - mu): - num += self._classical_rank * self._freudenthal_accum(mu, al) + cr = self._classical_rank + num = sum(self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_real(self._Lam - mu)) + + if self._cartan_type.is_untwisted_affine(): + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual().type() == 'B': # A_{2n-1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.type() == 'BC': # A_{2n}^{(2)} + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual() == 'C': # D_{n+1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - (cr - 1)*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'F': # E_6^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (4 - 2*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'G': # D_4^{(3)} (or dual of G_2^{(1)}) + for k,rt in enumerate(self._freudenthal_roots_imaginary(self._Lam - mu)): + # k-th element (starting from 1) is k*delta + if (k+1) % 3 == 0: + num += 2 * self._freudenthal_accum(mu, rt) + else: + num += self._freudenthal_accum(mu, rt) + + den = 2*self._inner_pq(self._Lam_rho, al) - self._inner_qq(al, al) try: return ZZ(num / den) except TypeError: @@ -872,9 +1002,9 @@ def modular_characteristic(self, mu=None): else: n = self.from_weight(mu) k = self.level() - hd = self._dual_coxeter_number + hd = self.dual_coxeter_number() rho = self._P.rho() - m_Lambda = self._inner_pp(self._Lam+rho, self._Lam+rho) / (2*(k+hd)) \ + m_Lambda = self._inner_pp(self._Lam_rho, self._Lam_rho) / (2*(k+hd)) \ - self._inner_pp(rho, rho) / (2*hd) if n is None: return m_Lambda diff --git a/src/sage/combinat/root_system/plot.py b/src/sage/combinat/root_system/plot.py index cec3c3ff33c..8305963e7b4 100644 --- a/src/sage/combinat/root_system/plot.py +++ b/src/sage/combinat/root_system/plot.py @@ -416,7 +416,7 @@ :: sage: L = RootSystem(["A",3,1]).ambient_space() - sage: alcoves = CartesianProduct([0,1],[0,1],[0,1]) + sage: alcoves = cartesian_product([[0,1],[0,1],[0,1]]) sage: color = lambda i: "black" if i==0 else None sage: L.plot_alcoves(alcoves=alcoves, color=color, bounding_box=10,wireframe=True).show(frame=False) # long time @@ -671,7 +671,7 @@ def __init__(self, space, # Bounding box from sage.rings.real_mpfr import RR from sage.geometry.polyhedron.all import Polyhedron - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product if bounding_box in RR: bounding_box = [[-bounding_box,bounding_box]] * self.dimension else: @@ -679,7 +679,7 @@ def __init__(self, space, raise TypeError("bounding_box argument doesn't match with the plot dimension") elif not all(len(b)==2 for b in bounding_box): raise TypeError("Invalid bounding box %s"%bounding_box) - self.bounding_box = Polyhedron(vertices=CartesianProduct(*bounding_box)) + self.bounding_box = Polyhedron(vertices=product(*bounding_box)) @cached_method def in_bounding_box(self, x): diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 5429e7bcd90..3df1e443fef 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -794,7 +794,7 @@ def positive_real_roots(self): if not self.cartan_type().is_affine(): raise NotImplementedError("only implemented for finite and affine Cartan types") - from sage.combinat.cartesian_product import CartesianProduct + from sage.categories.cartesian_product import cartesian_product from sage.combinat.root_system.root_system import RootSystem from sage.sets.positive_integers import PositiveIntegers from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets @@ -813,19 +813,19 @@ def lift(x): # Add all of the delta shifts delta = self.null_root() if self.cartan_type().is_untwisted_affine(): - C = CartesianProduct(PositiveIntegers(), Q.roots()) + C = cartesian_product([PositiveIntegers(), Q.roots()]) F = Family(C, lambda x: lift(x[1]) + x[0]*delta) D = DisjointUnionEnumeratedSets([P, F]) elif self.cartan_type().type() == 'BC' or self.cartan_type().dual().type() == 'BC': - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cl, lambda x: (lift(x[1]) + (2*x[0]-1)*delta) / 2) Fm = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) Fl = Family(Cl, lambda x: lift(x[1]) + 2*x[0]*delta) D = DisjointUnionEnumeratedSets([P, Fs, Fm, Fl]) else: # Other twisted types - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) if self.cartan_type().dual() == 'G': # D_4^3 k = 3 @@ -1046,7 +1046,7 @@ def root_poset(self, restricted=False, facade=False): TESTS: - Check that trac:`17982` is fixed:: + Check that :trac:`17982` is fixed:: sage: RootSystem(['A', 2]).ambient_space().root_poset() Finite poset containing 3 elements @@ -2155,11 +2155,14 @@ def _plot_projection(self, x): @cached_method def _plot_projection_barycentric_matrix(self): """ - A rational approximation of the matrix for the barycentric projection + A rational approximation of the matrix for the barycentric + projection. - OUTPUT: a matrix with rational coefficients whose column sum is zero + OUTPUT: + + a matrix with rational coefficients whose column sum is zero - .. SEE_ALSO:: + .. SEEALSO:: - :func:`sage.combinat.root_system.plot.barycentric_projection_matrix` - :meth:`_plot_projection_barycentric` diff --git a/src/sage/combinat/root_system/root_system.py b/src/sage/combinat/root_system/root_system.py index 668f29e43c9..5809ff8a37b 100644 --- a/src/sage/combinat/root_system/root_system.py +++ b/src/sage/combinat/root_system/root_system.py @@ -447,6 +447,11 @@ def __cmp__(self, other): True sage: r1 == r2 False + + Check that they inherit a hash method from ``UniqueRepresentation``:: + + sage: hash(r1) # random + 42 """ if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__) diff --git a/src/sage/combinat/root_system/type_I.py b/src/sage/combinat/root_system/type_I.py index ea8a7cdeb6f..bcbcb8dbfbf 100644 --- a/src/sage/combinat/root_system/type_I.py +++ b/src/sage/combinat/root_system/type_I.py @@ -22,7 +22,7 @@ def __init__(self, n): sage: ct.rank() 2 sage: ct.index_set() - [1, 2] + (1, 2) sage: ct.is_irreducible() True @@ -47,6 +47,7 @@ def rank(self): Type `I_p` is of rank 2 EXAMPLES:: + sage: CartanType(['I', 5]).rank() 2 """ @@ -57,10 +58,11 @@ def index_set(self): Type `I_p` is of rank 2 EXAMPLES:: + sage: CartanType(['I', 5]).index_set() - [1, 2] + (1, 2) """ - return [1, 2] + return (1, 2) def coxeter_diagram(self): """ @@ -92,3 +94,4 @@ def coxeter_number(self): 12 """ return self.n + diff --git a/src/sage/combinat/root_system/type_affine.py b/src/sage/combinat/root_system/type_affine.py index f505c47f172..f8ede598a60 100644 --- a/src/sage/combinat/root_system/type_affine.py +++ b/src/sage/combinat/root_system/type_affine.py @@ -378,7 +378,7 @@ def coroot_lattice(self): return self def _plot_projection(self, x): - """ + r""" Implements the default projection to be used for plots For affine ambient spaces, the default implementation is to @@ -387,7 +387,9 @@ def _plot_projection(self, x): keeping an extra coordinate for the coefficient of `\delta^\vee` to keep the level information. - .. SEEALSO:: :meth:`sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations._plot_projection` + .. SEEALSO:: + + :meth:`sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations._plot_projection` EXAMPLES:: diff --git a/src/sage/combinat/root_system/type_folded.py b/src/sage/combinat/root_system/type_folded.py index 28b388f7e2a..f4847610ab7 100644 --- a/src/sage/combinat/root_system/type_folded.py +++ b/src/sage/combinat/root_system/type_folded.py @@ -19,7 +19,7 @@ from sage.sets.family import Family from sage.combinat.root_system.cartan_type import CartanType -class CartanTypeFolded(SageObject, UniqueRepresentation): +class CartanTypeFolded(UniqueRepresentation, SageObject): r""" A Cartan type realized from a (Dynkin) diagram folding. @@ -193,6 +193,8 @@ def __init__(self, cartan_type, folding_of, orbit): sage: fct = CartanType(['C',4,1]).as_folding() sage: TestSuite(fct).run() + sage: hash(fct) # random + 42 """ self._cartan_type = cartan_type self._folding = folding_of diff --git a/src/sage/combinat/root_system/type_reducible.py b/src/sage/combinat/root_system/type_reducible.py index 6b0c602c71e..5b059b12fa2 100644 --- a/src/sage/combinat/root_system/type_reducible.py +++ b/src/sage/combinat/root_system/type_reducible.py @@ -153,6 +153,16 @@ def _latex_(self): """ return " \\times ".join(x._latex_() for x in self.component_types()) + def __hash__(self): + r""" + EXAMPLES:: + + sage: hash(CartanType(['A',1],['B',2])) + 1110723648 # 32-bit + -6896789355307447232 # 64-bit + """ + return hash(repr(self._types)) + def __cmp__(self, other): """ EXAMPLES:: diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 81dd06761aa..e8e1f3b5185 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -1104,7 +1104,9 @@ def branch(self, S, rule="default"): def __pow__(self, n): """ - We override the method in :module:`sage.monoids.monoids` since + Return the nth power of self. + + We override the method in :mod:`sage.monoids.monoids` since using the Brauer-Klimyk algorithm, it is more efficient to compute ``a*(a*(a*a))`` than ``(a*a)*(a*a)``. diff --git a/src/sage/combinat/root_system/weyl_group.py b/src/sage/combinat/root_system/weyl_group.py index bbb31e61974..2c1c152dab4 100644 --- a/src/sage/combinat/root_system/weyl_group.py +++ b/src/sage/combinat/root_system/weyl_group.py @@ -412,6 +412,7 @@ def one(self): Returns the unit element of the Weyl group EXAMPLES:: + sage: W = WeylGroup(['A',3]) sage: e = W.one(); e [1 0 0 0] @@ -751,14 +752,16 @@ def _repr_(self): def _latex_(self): """ + Return the latex representation of ``self``. + EXAMPLES:: sage: W = WeylGroup(['A',2,1], prefix="s") - sage: [s0,s1,s2]=W.simple_reflections() - sage: latex(s0*s1) # indirect doctest + sage: [s0,s1,s2] = W.simple_reflections() + sage: latex(s0*s1) # indirect doctest s_{0}s_{1} sage: W = WeylGroup(['A',2,1]) - sage: [s0,s1,s2]=W.simple_reflections() + sage: [s0,s1,s2] = W.simple_reflections() sage: latex(s0*s1) \left(\begin{array}{rrr} 0 & -1 & 2 \\ @@ -817,6 +820,7 @@ def action(self, v): Returns the action of self on the vector v. EXAMPLES:: + sage: W = WeylGroup(['A',2]) sage: s = W.simple_reflections() sage: v = W.domain()([1,0,0]) diff --git a/src/sage/combinat/rsk.py b/src/sage/combinat/rsk.py index 34348cb8960..a52f08fee2d 100644 --- a/src/sage/combinat/rsk.py +++ b/src/sage/combinat/rsk.py @@ -334,7 +334,7 @@ def RSK_inverse(p, q, output='array', insertion='RSK'): Return the generalized permutation corresponding to the pair of tableaux `(p,q)` under the inverse of the Robinson-Schensted-Knuth algorithm. - For more information on the bijeciton, see :func:`RSK`. + For more information on the bijection, see :func:`RSK`. INPUT: diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 6e794e11c87..b61d29cb9ee 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -38,7 +38,6 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from sage.combinat.combinatorial_map import combinatorial_map import sage.combinat.subset as subset @@ -845,7 +844,7 @@ def refinements(self): [{}] """ L = [SetPartitions(part) for part in self] - return [SetPartition(sum(map(list, x), [])) for x in CartesianProduct(*L)] + return [SetPartition(sum(map(list, x), [])) for x in itertools.product(*L)] def coarsenings(self): """ @@ -944,7 +943,7 @@ def arcs(self): arcs.append((p[i], p[i+1])) return arcs -class SetPartitions(Parent, UniqueRepresentation): +class SetPartitions(UniqueRepresentation, Parent): r""" An (unordered) partition of a set `S` is a set of pairwise disjoint nonempty subsets with union `S`, and is represented @@ -1118,7 +1117,7 @@ def _iterator_part(self, part): for b in blocs: lb = [IterableFunctionCall(_listbloc, nonzero[i][0], nonzero[i][1], b[i]) for i in range(len(nonzero))] - for x in itertools.imap(lambda x: _union(x), CartesianProduct( *lb )): + for x in itertools.imap(lambda x: _union(x), itertools.product( *lb )): yield x def is_less_than(self, s, t): diff --git a/src/sage/combinat/set_partition_ordered.py b/src/sage/combinat/set_partition_ordered.py index 7f041d8dc6d..07e05ffb64a 100644 --- a/src/sage/combinat/set_partition_ordered.py +++ b/src/sage/combinat/set_partition_ordered.py @@ -65,7 +65,7 @@ class OrderedSetPartition(ClonableArray): function is .. MATH:: - + \sum_n {T_n \over n!} x^n = {1 \over 2-e^x}. (See sequence A000670 in OEIS.) @@ -191,7 +191,7 @@ def to_composition(self): """ return Composition([len(_) for _ in self]) -class OrderedSetPartitions(Parent, UniqueRepresentation): +class OrderedSetPartitions(UniqueRepresentation, Parent): """ Return the combinatorial class of ordered set partitions of ``s``. diff --git a/src/sage/combinat/sf/classical.py b/src/sage/combinat/sf/classical.py index d3cb6efad83..6bb45629c23 100644 --- a/src/sage/combinat/sf/classical.py +++ b/src/sage/combinat/sf/classical.py @@ -19,12 +19,11 @@ from sage.rings.integer import Integer -import sage.combinat.skew_partition import sage.libs.symmetrica.all as symmetrica # used in eval() from sage.rings.integer_ring import IntegerRing from sage.rings.rational_field import RationalField - +from sage.combinat.partition import _Partitions import hall_littlewood @@ -93,11 +92,10 @@ class SymmetricFunctionAlgebra_classical(sfa.SymmetricFunctionAlgebra_generic): def _element_constructor_(self, x): """ - Convert ``x`` into ``self``, if coercion failed + Convert ``x`` into ``self``, if coercion failed. INPUT: - - ``self`` -- a basis of the symmetric functions - ``x`` -- an element of the symmetric functions EXAMPLES:: @@ -108,15 +106,21 @@ def _element_constructor_(self, x): sage: s([2,1]) # indirect doctest s[2, 1] - sage: s([[2,1],[1]]) - s[1, 1] + s[2] - sage: s([[],[]]) - s[] - sage: McdJ = SymmetricFunctions(QQ['q','t'].fraction_field()).macdonald().J() sage: s = SymmetricFunctions(McdJ.base_ring()).s() sage: s._element_constructor_(McdJ(s[2,1])) s[2, 1] + + TESTS: + + Check that non-Schur bases raise an error when given skew partitions + (:trac:`19218`):: + + sage: e = SymmetricFunctions(QQ).e() + sage: e([[2,1],[1]]) + Traceback (most recent call last): + ... + TypeError: do not know how to make x (= [[2, 1], [1]]) an element of self """ R = self.base_ring() @@ -128,8 +132,8 @@ def _element_constructor_(self, x): ############## # Partitions # ############## - if x in sage.combinat.partition.Partitions(): - return eclass(self, {sage.combinat.partition.Partition([z for z in x if z!=0]): R.one()}) + if x in _Partitions: + return eclass(self, {_Partitions(x): R.one()}) # Todo: discard all of this which is taken care by Sage's coercion # (up to changes of base ring) @@ -286,35 +290,14 @@ def _element_constructor_(self, x): else: return self( xp._sf_base(x) ) - ################### - # Skew Partitions # - ################### - elif x in sage.combinat.skew_partition.SkewPartitions(): - import sage.libs.lrcalc.lrcalc as lrcalc - skewschur = lrcalc.skew(x[0], x[1]) - return self._from_dict(skewschur) - - - ############################# - # Elements of the base ring # - ############################# - elif x.parent() is R: - return eclass(self, {sage.combinat.partition.Partition([]):x}) - - ########################################### - # Elements that coerce into the base ring # - ########################################### - elif R.has_coerce_map_from(x.parent()): - return eclass(self, {sage.combinat.partition.Partition([]):R(x)}) - ################################# # Last shot -- try calling R(x) # ################################# else: try: - return eclass(self, {sage.combinat.partition.Partition([]):R(x)}) + return eclass(self, {_Partitions([]): R(x)}) except Exception: - raise TypeError("do not know how to make x (= %s) an element of self"%(x)) + raise TypeError("do not know how to make x (= {}) an element of self".format(x)) # This subclass is currently needed for the test above: # isinstance(x, SymmetricFunctionAlgebra_classical.Element): diff --git a/src/sage/combinat/sf/k_dual.py b/src/sage/combinat/sf/k_dual.py index 0e21f803443..245f9196fe3 100644 --- a/src/sage/combinat/sf/k_dual.py +++ b/src/sage/combinat/sf/k_dual.py @@ -650,6 +650,7 @@ def indices(self): The set of `k`-bounded partitions of all non-negative integers. EXAMPLES:: + sage: km = SymmetricFunctions(QQ).kBoundedQuotient(3,t=1).km() sage: km.indices() 3-Bounded Partitions diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 21e989c24a0..f7472b4aa42 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -1391,7 +1391,7 @@ def _m_to_self( self, f ): mu_to_H = lambda mu: self._self_to_m(self(mu)).theta_qt(q=self.t, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Hmu = mu_to_H(sprt[-1]) fl_sprt = fl(sprt[-1]) out[fl_sprt] = self._base(g.coefficient(sprt[-1]) / Hmu.coefficient(sprt[-1])) @@ -1625,7 +1625,7 @@ def _m_to_self( self, f ): g = f.omega_qt(q=subsval, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Htmu = self._self_to_m(self(fl(sprt[-1]))).omega_qt(q=subsval, t=0) out[fl(sprt[-1])] = self._base(g.coefficient(sprt[-1]) / Htmu.coefficient(sprt[-1])) g -= out[fl(sprt[-1])] * Htmu diff --git a/src/sage/combinat/sf/new_kschur.py b/src/sage/combinat/sf/new_kschur.py index 40b65a3b98a..0c94633aa70 100644 --- a/src/sage/combinat/sf/new_kschur.py +++ b/src/sage/combinat/sf/new_kschur.py @@ -66,10 +66,13 @@ class KBoundedSubspace(UniqueRepresentation, Parent): def __init__(self, Sym, k, t='t'): r""" - The class modeling the abstract vector space of `k`-Schur functions; if `t=1` this - is actually an abstract ring. Another way to describe this space is as the subspace of - a ring of symmetric functions generated by the complete homogeneous symmetric functions - `h_i` for `1\le i \le k`. + The class modeling the abstract vector space of `k`-Schur + functions. + + If `t=1` this is actually an abstract ring. Another + way to describe this space is as the subspace of a ring of + symmetric functions generated by the complete homogeneous + symmetric functions `h_i` for `1\le i \le k`. TESTS:: @@ -260,7 +263,7 @@ def __init__(self, base, t='t'): INPUT: - ``base`` -- a basis in the `k`-bounded subspace - - ``t` -- a parameter (default: 't') + - ``t`` -- a parameter (default: 't') TESTS:: diff --git a/src/sage/combinat/sf/schur.py b/src/sage/combinat/sf/schur.py index 8c53bbdc592..0e78dbdcb95 100644 --- a/src/sage/combinat/sf/schur.py +++ b/src/sage/combinat/sf/schur.py @@ -18,6 +18,7 @@ #***************************************************************************** import classical import sage.libs.symmetrica.all as symmetrica +import sage.libs.lrcalc.lrcalc as lrcalc from sage.rings.all import ZZ, QQ, Integer class SymmetricFunctionAlgebra_schur(classical.SymmetricFunctionAlgebra_classical): @@ -111,7 +112,6 @@ def _multiply_basis(self, left, right): # TODO: factor out this code for all bas sage: 0*s([2,1]) 0 """ - import sage.libs.lrcalc.lrcalc as lrcalc return lrcalc.mult(left,right) def coproduct_on_basis(self, mu): @@ -138,10 +138,29 @@ def coproduct_on_basis(self, mu): sage: s.coproduct_on_basis([2]) s[] # s[2] + s[1] # s[1] + s[2] # s[] """ - import sage.libs.lrcalc.lrcalc as lrcalc T = self.tensor_square() return T._from_dict( lrcalc.coprod(mu, all=1) ) + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + TESTS:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s([[2,1],[1]]) + s[1, 1] + s[2] + sage: s([[],[]]) + s[] + """ + ################### + # Skew Partitions # + ################### + try: + return self.skew_schur(x) + except ValueError: + return super(SymmetricFunctionAlgebra_schur, self)._element_constructor_(x) + class Element(classical.SymmetricFunctionAlgebra_classical.Element): def __pow__(self, n): """ diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 5d17372220a..e82edcf7bb7 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -833,6 +833,46 @@ def corresponding_basis_over(self, R): #This code relied heavily on the construction of bases of #``SymmetricFunctions`` and on their reduction. + def skew_schur(self, x): + """ + Return the skew Schur function indexed by ``x`` in ``self``. + + INPUT: + + - ``x`` -- a skew partition + + EXAMPLES:: + + sage: sp = SkewPartition([[5,3,3,1], [3,2,1]]) + sage: s = SymmetricFunctions(QQ).s() + sage: s.skew_schur(sp) + s[2, 2, 1, 1] + s[2, 2, 2] + s[3, 1, 1, 1] + 3*s[3, 2, 1] + + s[3, 3] + 2*s[4, 1, 1] + 2*s[4, 2] + s[5, 1] + + sage: e = SymmetricFunctions(QQ).e() + sage: ess = e.skew_schur(sp); ess + e[2, 1, 1, 1, 1] - e[2, 2, 1, 1] - e[3, 1, 1, 1] + e[3, 2, 1] + sage: ess == e(s.skew_schur(sp)) + True + + TESTS:: + + sage: s.skew_schur([[2,1], [1]]) + s[1, 1] + s[2] + + sage: s.skew_schur([[2,1], [3]]) + Traceback (most recent call last): + ... + ValueError: not a valid skew partition + """ + from sage.combinat.skew_partition import SkewPartitions + if x not in SkewPartitions(): + raise ValueError("not a valid skew partition") + import sage.libs.lrcalc.lrcalc as lrcalc + s = self.realization_of().schur() + skewschur = lrcalc.skew(x[0], x[1]) + return self(s._from_dict(skewschur)) + def Eulerian(self, n, j, k=None): """ Return the Eulerian symmetric function `Q_{n,j}` (with `n` @@ -986,13 +1026,13 @@ def gessel_reutenauer(self, lam): - h[4, 2] - h[5, 1] + h[6] Gessel-Reutenauer functions indexed by partitions:: - + sage: h.gessel_reutenauer([2, 1]) h[1, 1, 1] - h[2, 1] sage: h.gessel_reutenauer([2, 2]) h[1, 1, 1, 1] - 3*h[2, 1, 1] + 2*h[2, 2] + h[3, 1] - h[4] - The Gessel-Reutenauer functions are Schur-postive:: + The Gessel-Reutenauer functions are Schur-positive:: sage: s = Sym.s() sage: s.gessel_reutenauer([2, 1]) @@ -1495,7 +1535,7 @@ def _change_by_proportionality(self, x, function): r""" Return the symmetric function obtained from ``x`` by scaling each basis element corresponding to the partition `\lambda` by - ``function``(`\lambda`). + the value of ``function`` on `\lambda`. INPUT: @@ -1529,7 +1569,7 @@ def _change_by_plethysm(self, x, expr, deg_one): INPUT: - - ``x` -- a symmetric function + - ``x`` -- a symmetric function - ``expr`` -- an expression used in the plethysm - ``deg_one`` -- a list (or iterable) specifying the degree one variables (that is, the terms to be treated as degree-one @@ -2055,8 +2095,8 @@ def transition_matrix(self, basis, n): sage: a = s([3,1])+5*s([1,1,1,1])-s([4]) sage: a 5*s[1, 1, 1, 1] + s[3, 1] - s[4] - sage: mon = a.support() - sage: coeffs = a.coefficients() + sage: mon = sorted(a.support()) + sage: coeffs = [a[i] for i in mon] sage: coeffs [5, 1, -1] sage: mon @@ -4429,10 +4469,6 @@ def frobenius(self, n): where `p_n` is the `n`-th powersum symmetric function, and `\circ` denotes (outer) plethysm. - :meth:`adams_operation` serves as alias for :meth:`frobenius`, since the - Frobenius operators are the Adams operations of the `\Lambda`-ring - of symmetric functions. - INPUT: - ``n`` -- a positive integer @@ -4536,7 +4572,9 @@ def frobenius(self, n): result_in_m_basis = m._from_dict(dct) return parent(result_in_m_basis) - adams_operation = frobenius + def adams_operation(self, *args, **opts): + from sage.misc.superseded import deprecation + deprecation(19255, "Do not use this method! Please use `frobenius` or `adams_operator` methods following what you expect.") def verschiebung(self, n): r""" diff --git a/src/sage/combinat/sf/witt.py b/src/sage/combinat/sf/witt.py index b49c3d57a5d..eb58afcbde4 100644 --- a/src/sage/combinat/sf/witt.py +++ b/src/sage/combinat/sf/witt.py @@ -412,6 +412,7 @@ def _precompute_cache(self, n, to_self_cache, from_self_cache, transition_matric Compute the transition matrices between ``self`` and another multiplicative homogeneous basis in the homogeneous components of degree `n`. + The results are not returned, but rather stored in the caches. This assumes that the transition matrices in all degrees smaller @@ -441,7 +442,7 @@ def _precompute_cache(self, n, to_self_cache, from_self_cache, transition_matric Examples for usage of this function are the ``_precompute_h``, ``_precompute_e`` and ``_precompute_p`` methods of this class. - EXAMPLES:: + EXAMPLES: The examples below demonstrate how the caches are built step by step using the ``_precompute_cache`` method. In order diff --git a/src/sage/combinat/sidon_sets.py b/src/sage/combinat/sidon_sets.py index 47a0055ec81..d595cb2b554 100644 --- a/src/sage/combinat/sidon_sets.py +++ b/src/sage/combinat/sidon_sets.py @@ -46,12 +46,12 @@ def sidon_sets(N, g = 1): sage: S.cardinality() 8 sage: S.category() - Category of sets + Category of finite sets sage: sid = S.an_element() sage: sid {2} sage: sid.category() - Category of sets + Category of finite sets TESTS:: diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index b3f0593a3d8..2231480778b 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -176,12 +176,12 @@ class type, it is also possible to compute the number of classes of that type #***************************************************************************** from operator import mul -from itertools import chain +from itertools import chain, product from sage.misc.all import prod from sage.functions.all import factorial from sage.rings.arith import moebius from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass -from sage.structure.element import Element +from sage.structure.element import Element, parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -189,7 +189,6 @@ class type, it is also possible to compute the number of classes of that type from sage.combinat.partition import Partitions, Partition from sage.rings.all import ZZ, QQ, FractionField, divisors from sage.misc.cachefunc import cached_in_parent_method, cached_function -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from functools import reduce @@ -400,6 +399,25 @@ def __repr__(self): """ return "%s"%([self._deg, self._par]) + def __hash__(self): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT3 = PrimarySimilarityClassType(2, [4, 2, 1]) + sage: hash(PT1) + 5050909583595644741 # 64-bit + 1658169157 # 32-bit + sage: hash(PT2) + 5050909583595644740 # 64-bit + 1658169156 # 32-bit + sage: hash(PT3) + 6312110366011971308 # 64-bit + 1429493484 # 32-bit + """ + return hash(self._deg) ^ hash(tuple(self._par)) + def __eq__(self, other): """ Check equality. @@ -420,9 +438,25 @@ def __eq__(self, other): sage: PT1 == PT5 False """ - if isinstance(other, PrimarySimilarityClassType): - return self.degree() == other.degree() and self.partition() == other.partition() - return False + return isinstance(other, PrimarySimilarityClassType) and \ + self.degree() == other.degree() and \ + self.partition() == other.partition() + + def __ne__(self, other): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(2, Partition([3, 2, 1])) + sage: PT1 != PT2 + False + sage: PT3 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT1 != PT3 + True + """ + return not isinstance(other, PrimarySimilarityClassType) or \ + self.degree() != other.degree() or \ + self.partition() != other.partition() def size(self): """ @@ -519,7 +553,7 @@ def centralizer_group_card(self, q = None): #p = q.parent()(prod(map(lambda n:fq(n, q = q), self.partition().to_exp()),1)) #return q**self.centralizer_algebra_dim()*p.substitute(q = q**self.degree()) -class PrimarySimilarityClassTypes(Parent, UniqueRepresentation): +class PrimarySimilarityClassTypes(UniqueRepresentation, Parent): r""" All primary similarity class types of size ``n`` whose degree is greater than that of ``min`` or whose degree is that of ``min`` and whose partition @@ -933,7 +967,7 @@ def statistic(self, func, q = None): q = FractionField(ZZ['q']).gen() return prod([PT.statistic(func, q = q) for PT in self]) -class SimilarityClassTypes(Parent, UniqueRepresentation): +class SimilarityClassTypes(UniqueRepresentation, Parent): r""" Class of all similarity class types of size ``n`` with all primary matrix types greater than or equal to the primary matrix type ``min``. @@ -1520,7 +1554,7 @@ def ext_orbit_centralizers(input_data, q = None, selftranspose = False): yield (item[0].substitute(q = q**tau.degree()), item[1].substitute(q = q**tau.degree())) elif case == 'sim': tau = data - for item in CartesianProduct(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): + for item in product(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): size = prod([list(entry)[0] for entry in item]) freq = prod([list(entry)[1] for entry in item]) yield(size, freq) diff --git a/src/sage/combinat/six_vertex_model.py b/src/sage/combinat/six_vertex_model.py index 58d9c28a9cf..4d693c50b23 100644 --- a/src/sage/combinat/six_vertex_model.py +++ b/src/sage/combinat/six_vertex_model.py @@ -36,7 +36,7 @@ def _repr_(self): | | | --> # <- # <- # <-- | ^ ^ - V | | + V | | --> # -> # <- # <-- | | ^ V V | @@ -289,7 +289,7 @@ def energy(self, epsilon): raise ValueError("there must be 6 energy constants") return sum(epsilon[entry] for row in self for entry in row) -class SixVertexModel(Parent, UniqueRepresentation): +class SixVertexModel(UniqueRepresentation, Parent): """ The six vertex model. @@ -663,10 +663,10 @@ class SquareIceModel(SixVertexModel): The square ice model is a 6 vertex model on an `n \times n` grid with the boundary conditions that the top and bottom boundaries are pointing - outward and the left and right boundaries are pointing inward. These + outward and the left and right boundaries are pointing inward. These boundary conditions are also called domain wall boundary conditions. - Configurations of the 6 vertex model with domain wall boundary conditions + Configurations of the 6 vertex model with domain wall boundary conditions are in bijection with alternating sign matrices. """ def __init__(self, n): @@ -772,7 +772,8 @@ def to_alternating_sign_matrix(self): [ 0 1 -1 1] [ 0 0 1 0] """ - from sage.combinat.alternating_sign_matrix import AlternatingSignMatrices - ASM = AlternatingSignMatrices(self.parent()._nrows) - return ASM(self.to_signed_matrix()) + from sage.combinat.alternating_sign_matrix import AlternatingSignMatrix #AlternatingSignMatrices + #ASM = AlternatingSignMatrices(self.parent()._nrows) + #return ASM(self.to_signed_matrix()) + return AlternatingSignMatrix(self.to_signed_matrix()) diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index d5d129005a1..93247394892 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -1245,7 +1245,7 @@ def row_lengths_aux(skp): else: return [x[0] - x[1] for x in zip(skp[0], skp[1])] -class SkewPartitions(Parent, UniqueRepresentation): +class SkewPartitions(UniqueRepresentation, Parent): """ Skew partitions. diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 89d439c85b2..cb1b6a308bc 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -40,7 +40,8 @@ from sage.structure.list_clone import ClonableList from sage.combinat.partition import Partition -from sage.combinat.tableau import Tableau, TableauOptions +from sage.combinat.tableau import (Tableau, TableauOptions, + StandardTableau, SemistandardTableau) from sage.combinat.skew_partition import SkewPartition, SkewPartitions from sage.combinat.integer_vector import IntegerVectors from sage.combinat.words.words import Words @@ -407,9 +408,9 @@ def conjugate(self): def to_word_by_row(self): """ Return a word obtained from a row reading of ``self``. - Specifically, this is the word obtained by concatenating the - rows from the bottommost one (in English notation) to the - topmost one. + + This is the word obtained by concatenating the rows from + the bottommost one (in English notation) to the topmost one. EXAMPLES:: @@ -434,19 +435,16 @@ def to_word_by_row(self): sage: SkewTableau([]).to_word_by_row() word: """ - word = [] - for row in self: - word = list(row) + word - - return Words("positive integers")([i for i in word if i is not None]) + word = [x for row in reversed(self) for x in row if x is not None] + return Words("positive integers")(word) def to_word_by_column(self): """ Return the word obtained from a column reading of the skew tableau. - Specifically, this is the word obtained by concatenating the - columns from the rightmost one (in English notation) to the - leftmost one. + + This is the word obtained by concatenating the columns from + the rightmost one (in English notation) to the leftmost one. EXAMPLES:: @@ -778,11 +776,11 @@ def to_chain(self, max_entry=None): def slide(self, corner=None): """ - Apply a jeu-de-taquin slide to ``self`` on the specified corner and - returns the new tableau. If no corner is given an arbitrary corner - is chosen. + Apply a jeu-de-taquin slide to ``self`` on the specified inner corner and + return the resulting tableau. If no corner is given, an arbitrary inner + corner is chosen. - See [FW]_ p12-13. + See [Fulton97]_ p12-13. EXAMPLES:: @@ -854,38 +852,74 @@ def slide(self, corner=None): return SkewTableau(new_st) - def rectify(self): + def rectify(self, algorithm=None): """ - Return a :class:`Tableau` formed by applying the jeu de taquin - process to ``self``. See page 15 of [FW]_. + Return a :class:`StandardTableau`, :class:`SemistandardTableau`, + or just :class:`Tableau` formed by applying the jeu de taquin + process to ``self``. - REFERENCES: + See page 15 of [Fulton97]_. - .. [FW] William Fulton, - *Young Tableaux*, - Cambridge University Press 1997. + INPUT: + + - ``algorithm`` -- optional: if set to ``'jdt'``, rectifies by jeu de + taquin; if set to ``'schensted'``, rectifies by Schensted insertion + of the reading word; otherwise, guesses which will be faster. EXAMPLES:: - sage: s = SkewTableau([[None,1],[2,3]]) - sage: s.rectify() + sage: S = SkewTableau([[None,1],[2,3]]) + sage: S.rectify() [[1, 3], [2]] - sage: SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]).rectify() + sage: T = SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]) + sage: T.rectify() + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='jdt') [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='schensted') + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='spaghetti') + Traceback (most recent call last): + ... + ValueError: algorithm must be 'jdt', 'schensted', or None TESTS:: - sage: s + sage: S [[None, 1], [2, 3]] - """ - rect = copy.deepcopy(self) - inner_corners = rect.inner_shape().corners() + sage: T + [[None, None, None, 4], [None, None, 1, 6], [None, None, 5], [2, 3]] + + REFERENCES: - while len(inner_corners) > 0: - rect = rect.slide() - inner_corners = rect.inner_shape().corners() + .. [Fulton97] William Fulton, *Young Tableaux*, + Cambridge University Press 1997. + """ + mu_size = self.inner_shape().size() - return rect.to_tableau() + # Roughly, use jdt with a small inner shape, Schensted with a large one + if algorithm is None: + la = self.outer_shape() + la_size = la.size() + if mu_size ** 2 < len(la) * (la_size - mu_size): + algorithm = 'jdt' + else: + algorithm = 'schensted' + + if algorithm == 'jdt': + rect = self + for i in range(mu_size): + rect = rect.slide() + elif algorithm == 'schensted': + w = [x for row in reversed(self) for x in row if x is not None] + rect = Tableau([]).insert_word(w) + else: + raise ValueError("algorithm must be 'jdt', 'schensted', or None") + if self in StandardSkewTableaux(): + return StandardTableau(rect[:]) + if self in SemistandardSkewTableaux(): + return SemistandardTableau(rect[:]) + return Tableau(rect) def standardization(self, check=True): r""" @@ -1411,7 +1445,7 @@ def _label_skew(list_of_cells, sk): i += 1 return skew -class SkewTableaux(Parent, UniqueRepresentation): +class SkewTableaux(UniqueRepresentation, Parent): r""" Class of all skew tableaux. """ @@ -1773,9 +1807,10 @@ def cardinality(self): def __iter__(self): """ - An iterator for all the standard skew tableaux with shape of the - skew partition ``skp``. The standard skew tableaux are ordered - lexicographically by the word obtained from their row reading. + An iterator for all the standard skew tableaux whose shape is + the skew partition ``skp``. The standard skew tableaux are + ordered lexicographically by the word obtained from their row + reading. EXAMPLES:: diff --git a/src/sage/combinat/sloane_functions.py b/src/sage/combinat/sloane_functions.py index 5f0e2a6a120..4add448d707 100644 --- a/src/sage/combinat/sloane_functions.py +++ b/src/sage/combinat/sloane_functions.py @@ -313,16 +313,12 @@ def __init__(self): r""" Number of groups of order `n`. - Note: The database_gap-4.4.9 must be installed for - `n > 50`. - - run ``sage -i database_gap-4.4.9`` or higher first. + Note: The package database_gap must be installed for + `n > 50`: run ``sage -i database_gap`` first. INPUT: - - - ``n`` - positive integer - + - ``n`` -- positive integer OUTPUT: integer @@ -334,13 +330,13 @@ def __init__(self): Traceback (most recent call last): ... ValueError: input n (=0) must be a positive integer - sage: a(1) #optional database_gap + sage: a(1) 1 - sage: a(2) #optional database_gap + sage: a(2) 1 - sage: a(9) #optional database_gap + sage: a(9) 2 - sage: a.list(16) #optional database_gap + sage: a.list(16) [1, 1, 1, 2, 1, 2, 1, 5, 2, 2, 1, 5, 1, 2, 1, 14] sage: a(60) # optional - database_gap 13 diff --git a/src/sage/combinat/species/combinatorial_logarithm.py b/src/sage/combinat/species/combinatorial_logarithm.py index a410d5cc7bf..0023708310d 100644 --- a/src/sage/combinat/species/combinatorial_logarithm.py +++ b/src/sage/combinat/species/combinatorial_logarithm.py @@ -6,21 +6,18 @@ the species `E^{+}` of nonempty sets: .. MATH:: - \Omega \circ E^{+} = E^{+} \circ \Omega = X. -AUTHORS: + \Omega \circ E^{+} = E^{+} \circ \Omega = X. -- Andrew Gainer-Dewar (2013): initial version +.. warning:: -TESTS:: + This module is now deprecated. Please use + :meth:`sage.combinat.species.generating_series.CycleIndexSeriesRing.exponential` + instead of :func:`CombinatorialLogarithmSeries`. - sage: from sage.combinat.species.combinatorial_logarithm import CombinatorialLogarithmSeries - sage: CombinatorialLogarithmSeries().coefficients(5) - [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3], -1/4*p[1, 1, 1, 1] + 1/4*p[2, 2]] +AUTHORS: - sage: Eplus = sage.combinat.species.set_species.SetSpecies(min=1).cycle_index_series() - sage: CombinatorialLogarithmSeries().compose(Eplus).coefficients(4) - [0, p[1], 0, 0] +- Andrew Gainer-Dewar (2013): initial version """ #***************************************************************************** @@ -32,54 +29,14 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.combinat.species.generating_series import CycleIndexSeriesRing, LogarithmCycleIndexSeries +from sage.rings.all import QQ from sage.misc.cachefunc import cached_function -from sage.combinat.species.stream import _integers_from -from sage.combinat.sf.all import SymmetricFunctions -from sage.combinat.species.generating_series import CycleIndexSeriesRing -from sage.rings.all import RationalField, Integer, divisors +from sage.misc.superseded import deprecation @cached_function -def _cl_term(n, R = RationalField()): - """ - Compute the order-n term of the cycle index series of the virtual species `\Omega`, - the compositional inverse of the species `E^{+}` of nonempty sets. - - EXAMPLES:: - - sage: from sage.combinat.species.combinatorial_logarithm import _cl_term - sage: [_cl_term(i) for i in range(4)] - [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] - """ - - n = Integer(n) #check that n is an integer - - p = SymmetricFunctions(R).power() - - res = p.zero() - if n == 1: - res = p([1]) - elif n > 1: - res = 1/n * ((-1)**(n-1) * p([1])**n - sum(d * p([Integer(n/d)]).plethysm(_cl_term(d, R)) for d in divisors(n)[:-1])) - - return res - -def _cl_gen (R = RationalField()): - """ - Produce a generator which yields the terms of the cycle index series of the virtual species - `\Omega`, the compositional inverse of the species `E^{+}` of nonempty sets. - - EXAMPLES:: - - sage: from sage.combinat.species.combinatorial_logarithm import _cl_gen - sage: g = _cl_gen() - sage: [next(g) for i in range(4)] - [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] - """ - return (_cl_term(i, R) for i in _integers_from(0)) - -@cached_function -def CombinatorialLogarithmSeries(R = RationalField()): - """ +def CombinatorialLogarithmSeries(R=QQ): + r""" Return the cycle index series of the virtual species `\Omega`, the compositional inverse of the species `E^{+}` of nonempty sets. @@ -93,6 +50,8 @@ def CombinatorialLogarithmSeries(R = RationalField()): sage: from sage.combinat.species.combinatorial_logarithm import CombinatorialLogarithmSeries sage: CombinatorialLogarithmSeries().coefficients(4) + doctest:...: DeprecationWarning: CombinatorialLogarithmSeries is deprecated, use CycleIndexSeriesRing(R).logarithm_series() or CycleIndexSeries().logarithm() instead + See http://trac.sagemath.org/14846 for details. [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] Its defining property is that `\Omega \circ E^{+} = E^{+} \circ \Omega = X` (that is, that @@ -101,12 +60,6 @@ def CombinatorialLogarithmSeries(R = RationalField()): sage: Eplus = sage.combinat.species.set_species.SetSpecies(min=1).cycle_index_series() sage: CombinatorialLogarithmSeries().compose(Eplus).coefficients(4) [0, p[1], 0, 0] - - REFERENCES: - - .. [BLL] F. Bergeron, G. Labelle, and P. Leroux. "Combinatorial species and tree-like structures". Encyclopedia of Mathematics and its Applications, vol. 67, Cambridge Univ. Press. 1998. - - .. [Labelle] G. Labelle. "New combinatorial computational methods arising from pseudo-singletons." DMTCS Proceedings 1, 2008. """ - CIS = CycleIndexSeriesRing(R) - return CIS(_cl_gen(R)) + deprecation(14846, "CombinatorialLogarithmSeries is deprecated, use CycleIndexSeriesRing(R).logarithm_series() or CycleIndexSeries().logarithm() instead") + return LogarithmCycleIndexSeries(R) diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index cbc23c9e43f..88e5544305f 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -42,7 +42,7 @@ def __repr__(self): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).random_element() - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')] + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),) """ f, gs = self._list return "F-structure: %s; G-structures: %s"%(repr(f), repr(gs)) @@ -56,9 +56,9 @@ def transport(self, perm): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.transport(p) - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'c'), ('b')) """ f, gs = self._list pi = self._partition.transport(perm) @@ -75,7 +75,7 @@ def change_labels(self, labels): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.change_labels([1,2,3]) F-structure: {{1, 3}, {2}}; G-structures: [(1, 3), (2)] """ @@ -126,12 +126,12 @@ def _structures(self, structure_class, labels): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).list() - [F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')], - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'c', 'b')], - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')], - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'b'), ('c')], - F-structure: {{'b', 'c'}, {'a'}}; G-structures: [('b', 'c'), ('a')], - F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: [('a'), ('b'), ('c')]] + [F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),), + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'c', 'b'),), + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')), + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'b'), ('c')), + F-structure: {{'b', 'c'}, {'a'}}; G-structures: (('b', 'c'), ('a')), + F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: (('a'), ('b'), ('c'))] TESTS:: @@ -152,16 +152,16 @@ def _structures(self, structure_class, labels): sage: [g._list for g in gs] [[1, 2], [1]] """ - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product P = PartitionSpecies() for pi in P.structures(labels): #The labels of the G-structures will be just be the things #in labels - gs = CartesianProduct(*[self._G.structures(part.labels()) for part in pi]) + gs = product(*[self._G.structures(part.labels()) for part in pi]) #The labels of the F-structure will be set objects fs = self._F.structures(list(pi)) - for f, gg in CartesianProduct(fs, gs): + for f, gg in product(fs, gs): yield structure_class(self, labels, pi, f, gg) def _isotypes(self, structure_class, labels): diff --git a/src/sage/combinat/species/generating_series.py b/src/sage/combinat/species/generating_series.py index 7e2e93fe8ff..9403d8f6a7d 100644 --- a/src/sage/combinat/species/generating_series.py +++ b/src/sage/combinat/species/generating_series.py @@ -57,6 +57,14 @@ p[1, 1, 1] + p[2, 1] sage: s[4] p[1, 1, 1, 1] + p[2, 1, 1] + p[2, 2] + +REFERENCES: + +.. [BLL] F. Bergeron, G. Labelle, and P. Leroux. + "Combinatorial species and tree-like structures". + Encyclopedia of Mathematics and its Applications, vol. 67, Cambridge Univ. Press. 1998. +.. [BLL-Intro] Francois Bergeron, Gilbert Labelle, and Pierre Leroux. + "Introduction to the Theory of Species of Structures", March 14, 2008. """ #***************************************************************************** # Copyright (C) 2008 Mike Hansen , @@ -74,7 +82,7 @@ #***************************************************************************** from series import LazyPowerSeriesRing, LazyPowerSeries from stream import Stream, _integers_from -from sage.rings.all import Integer, moebius, lcm, divisors +from sage.rings.all import Integer, moebius, lcm, divisors, RationalField from sage.combinat.partition import Partition, Partitions from functools import partial from sage.combinat.sf.sf import SymmetricFunctions @@ -593,12 +601,11 @@ def __invert__(self): REFERENCES: - .. [BLL] F. Bergeron, G. Labelle, and P. Leroux. - "Combinatorial species and tree-like structures". - Encyclopedia of Mathematics and its Applications, vol. 67, Cambridge Univ. Press. 1998. - .. [BLL-Intro] Francois Bergeron, Gilbert Labelle, and Pierre Leroux. - "Introduction to the Theory of Species of Structures", March 14, 2008. - http://bergeron.math.uqam.ca/Site/bergeron_anglais_files/livre_combinatoire.pdf + [BLL]_ + + [BLL-Intro]_ + + http://bergeron.math.uqam.ca/Site/bergeron_anglais_files/livre_combinatoire.pdf AUTHORS: @@ -863,11 +870,11 @@ def _card(self, n): def _compose_gen(self, y, ao): """ - Return a generator for the coefficients of the composition of this - cycle index series and the cycle index series ``y``. This overrides - the method defined in ``LazyPowerSeries``. + Return a generator for the coefficients of the composition of this + cycle index series and the cycle index series ``y``. This overrides + the method defined in ``LazyPowerSeries``. - The notion "composition" means plethystic substitution here, as + The notion "composition" means plethystic substitution here, as defined in Section 2.2 of [BLL-Intro]_. EXAMPLES:: @@ -1049,14 +1056,14 @@ def compositional_inverse(self): It follows that `(F - X) \circ G = F \circ G - X \circ G = X - G`. Rearranging, we obtain the recursive equation `G = X - (F - X) \circ G`, which can be solved using iterative methods. - + .. WARNING:: - + This algorithm is functional but can be very slow. Use with caution! .. SEEALSO:: - + The compositional inverse `\Omega` of the species `E_{+}` of nonempty sets can be handled much more efficiently using specialized methods. These are implemented in @@ -1079,4 +1086,273 @@ def compositional_inverse(self): return res + def derivative(self, order=1): + r""" + Return the species-theoretic nth derivative of ``self``, where n is ``order``. + + For a cycle index series `F (p_{1}, p_{2}, p_{3}, \dots)`, its derivative is the cycle index series + `F' = D_{p_{1}} F` (that is, the formal derivative of `F` with respect to the variable `p_{1}`). + + If `F` is the cycle index series of a species `S` then `F'` is the cycle index series of an associated + species `S'` of `S`-structures with a "hole". + + EXAMPLES: + + The species `E` of sets satisfies the relationship `E' = E`:: + + sage: E = species.SetSpecies().cycle_index_series() + sage: E.coefficients(8) == E.derivative().coefficients(8) + True + + The species `C` of cyclic orderings and the species `L` of linear orderings satisfy the relationship `C' = L`:: + + sage: C = species.CycleSpecies().cycle_index_series() + sage: L = species.LinearOrderSpecies().cycle_index_series() + sage: L.coefficients(8) == C.derivative().coefficients(8) + True + + """ + + # Make sure that order is integral + order = Integer(order) + + if order < 0: + raise ValueError("Order must be a non-negative integer") + + elif order == 0: + return self + + elif order == 1: + parent = self.parent() + derivative_term = lambda n: parent.term(self.coefficient(n+1).derivative_with_respect_to_p1(), n) + return parent.sum_generator(derivative_term(i) for i in _integers_from(0)) + + else: + return self.derivative(order-1) + + def pointing(self): + r""" + Return the species-theoretic pointing of ``self``. + + For a cycle index `F`, its pointing is the cycle index series `F^{\bullet} = p_{1} \cdot F'`. + + If `F` is the cycle index series of a species `S` then `F^{\bullet}` is the cycle index series of an associated + species `S^{\bullet}` of `S`-structures with a marked "root". + + EXAMPLES: + + The species `E^{\bullet}` of "pointed sets" satisfies `E^{\bullet} = X \cdot E`:: + + sage: E = species.SetSpecies().cycle_index_series() + sage: X = species.SingletonSpecies().cycle_index_series() + sage: E.pointing().coefficients(8) == (X*E).coefficients(8) + True + + """ + p1 = self.base_ring()([1]) + X = self.parent()([0, p1, 0]) + + return X*self.derivative() + + def integral(self, *args): + """ + Given a cycle index `G`, it is not in general possible to recover a single cycle index `F` + such that `F' = G` (even up to addition of a constant term). + + More broadly, it may be the case that there are many non-isomorphic species `S` such that + `S' = T` for a given species `T`. + For example, the species `3 C_{3}` of 3-cycles from three distinct classes + and the species `X^{3}` of 3-sets are not isomorphic, but `(3 C_{3})' = (X^{3})' = 3 X^{2}`. + + EXAMPLES:: + + sage: C3 = species.CycleSpecies(size=3).cycle_index_series() + sage: X = species.SingletonSpecies().cycle_index_series() + sage: (3*C3).derivative().coefficients(8) == (3*X^2).coefficients(8) + True + sage: (X^3).derivative().coefficients(8) == (3*X^2).coefficients(8) + True + + .. WARNING:: + + This method has no implementation and exists only to prevent you from doing something + strange. Calling it raises a ``NotImplementedError``! + + """ + + raise NotImplementedError + + def exponential(self): + r""" + Return the species-theoretic exponential of ``self``. + + For a cycle index `Z_{F}` of a species `F`, its exponential is the cycle index series + `Z_{E} \\circ Z_{F}`, where `Z_{E}` is the :meth:`~sage.combinat.species.generating_series.ExponentialCycleIndexSeries`. + + The exponential `Z_{E} \circ Z_{F}` is then the cycle index series of the species `E \\circ F` of + "sets of `F`-structures". + + EXAMPLES: + + Let `BT` be the species of binary trees, `BF` the species of binary forests, and + `E` the species of sets. Then we have `BF = E \circ BT`:: + + sage: BT = species.BinaryTreeSpecies().cycle_index_series() + sage: BF = species.BinaryForestSpecies().cycle_index_series() + sage: BT.exponential().isotype_generating_series().coefficients(8) == BF.isotype_generating_series().coefficients(8) + True + + """ + base_ring = self.parent().base_ring().base_ring() + E = ExponentialCycleIndexSeries(base_ring) + return E.compose(self) + + def logarithm(self): + r""" + Return the combinatorial logarithm of ``self``. + + For a cycle index `Z_{F}` of a species `F`, its logarithm is the cycle index series + `Z_{\Omega} \circ Z_{F}`, where `Z_{\Omega}` is the + :meth:`~sage.combinat.species.generating_series.LogarithmCycleIndexSeries`. + + The logarithm `Z_{\Omega} \circ Z_{F}` is then the cycle index series of the (virtual) species + `\Omega \circ F` of "connected `F`-structures". + In particular, if `F = E^{+} \circ G` for `E^{+}` the species of nonempty sets and `G` + some other species, then `\Omega \circ F = G`. + + EXAMPLES: + + Let `G` be the species of nonempty graphs and `CG` be the species of nonempty connected + graphs. Then `G = E^{+} \circ CG`, so `CG = \Omega \circ G`:: + + sage: G = species.SimpleGraphSpecies().cycle_index_series() - 1 + sage: from sage.combinat.species.generating_series import LogarithmCycleIndexSeries + sage: CG = LogarithmCycleIndexSeries().compose(G) + sage: CG.isotype_generating_series().coefficients(8) + [0, 1, 1, 2, 6, 21, 112, 853] + """ + + base_ring = self.parent().base_ring().base_ring() + Omega = LogarithmCycleIndexSeries(base_ring) + return Omega.compose(self) + +@cached_function +def _exp_term(n, R = RationalField()): + """ + Compute the order-n term of the cycle index series of the species `E` of sets. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import _exp_term + sage: [_exp_term(i) for i in range(4)] + [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3]] + """ + + p = SymmetricFunctions(R) + res = sum(p(part)/part.aut() for part in Partitions(n)) + return res + +def _exp_gen(R = RationalField()): + """ + Produce a generator which yields the terms of the cycle index series of the species `E` of sets. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import _exp_gen + sage: g = _exp_gen() + sage: [g.next() for i in range(4)] + [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3]] + """ + return (_exp_term(i, R) for i in _integers_from(0)) + +@cached_function +def ExponentialCycleIndexSeries(R = RationalField()): + """ + Return the cycle index series of the species `E` of sets. + + This cycle index satisfies + + .. math:: + + Z_{E} = \\sum_{n \\geq 0} \\sum_{\\lambda \\vdash n} \\frac{p_{\\lambda}}{z_{\\lambda}}. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import ExponentialCycleIndexSeries + sage: ExponentialCycleIndexSeries().coefficients(5) + [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3], 1/24*p[1, 1, 1, 1] + 1/4*p[2, 1, 1] + 1/8*p[2, 2] + 1/3*p[3, 1] + 1/4*p[4]] + """ + CIS = CycleIndexSeriesRing(R) + return CIS(_exp_gen(R)) + +@cached_function +def _cl_term(n, R = RationalField()): + """ + Compute the order-n term of the cycle index series of the virtual species `\Omega`, + the compositional inverse of the species `E^{+}` of nonempty sets. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import _cl_term + sage: [_cl_term(i) for i in range(4)] + [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] + """ + + n = Integer(n) #check that n is an integer + + p = SymmetricFunctions(R).power() + + res = p.zero() + if n == 1: + res = p([1]) + elif n > 1: + res = 1/n * ((-1)**(n-1) * p([1])**n - sum(d * p([Integer(n/d)]).plethysm(_cl_term(d, R)) for d in divisors(n)[:-1])) + + return res + +def _cl_gen (R = RationalField()): + """ + Produce a generator which yields the terms of the cycle index series of the virtual species + `\Omega`, the compositional inverse of the species `E^{+}` of nonempty sets. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import _cl_gen + sage: g = _cl_gen() + sage: [g.next() for i in range(4)] + [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] + """ + return (_cl_term(i, R) for i in _integers_from(0)) + +@cached_function +def LogarithmCycleIndexSeries(R = RationalField()): + """ + Return the cycle index series of the virtual species `\Omega`, the compositional inverse + of the species `E^{+}` of nonempty sets. + + The notion of virtual species is treated thoroughly in [BLL]_. The specific algorithm used + here to compute the cycle index of `\Omega` is found in [Labelle]_. + + EXAMPLES: + + The virtual species `\Omega` is 'properly virtual', in the sense that its cycle index + has negative coefficients:: + + sage: from sage.combinat.species.generating_series import LogarithmCycleIndexSeries + sage: LogarithmCycleIndexSeries().coefficients(4) + [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] + + Its defining property is that `\Omega \circ E^{+} = E^{+} \circ \Omega = X` (that is, that + composition with `E^{+}` in both directions yields the multiplicative identity `X`):: + + sage: Eplus = sage.combinat.species.set_species.SetSpecies(min=1).cycle_index_series() + sage: LogarithmCycleIndexSeries().compose(Eplus).coefficients(4) + [0, p[1], 0, 0] + + REFERENCES: + + .. [Labelle] G. Labelle. "New combinatorial computational methods arising from pseudo-singletons." DMTCS Proceedings 1, 2008. + """ + CIS = CycleIndexSeriesRing(R) + return CIS(_cl_gen(R)) diff --git a/src/sage/combinat/species/partition_species.py b/src/sage/combinat/species/partition_species.py index a7cada1fef4..c9f9db9bbac 100644 --- a/src/sage/combinat/species/partition_species.py +++ b/src/sage/combinat/species/partition_species.py @@ -274,8 +274,7 @@ def _cis(self, series_ring, base_ring): 5/8*p[1, 1, 1, 1] + 7/4*p[2, 1, 1] + 7/8*p[2, 2] + p[3, 1] + 3/4*p[4]] """ ciset = SetSpecies().cycle_index_series(base_ring) - CIS = ciset.parent() - res = CIS.sum_generator(((1/n)*ciset).stretch(n) for n in _integers_from(ZZ(1))).exponential() + res = ciset.composition(ciset - 1) if self.is_weighted(): res *= self._weight return res diff --git a/src/sage/combinat/species/set_species.py b/src/sage/combinat/species/set_species.py index 672547b7ee4..da44fd3ef2b 100644 --- a/src/sage/combinat/species/set_species.py +++ b/src/sage/combinat/species/set_species.py @@ -177,22 +177,13 @@ def _cis(self, series_ring, base_ring): 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3], 1/24*p[1, 1, 1, 1] + 1/4*p[2, 1, 1] + 1/8*p[2, 2] + 1/3*p[3, 1] + 1/4*p[4]] """ - return base_ring(self._weight)*series_ring( self._cis_gen(base_ring) ).exponential() + from generating_series import ExponentialCycleIndexSeries + res = ExponentialCycleIndexSeries(base_ring) - def _cis_gen(self, base_ring): - """ - EXAMPLES:: + if self.is_weighted(): + res *= self._weight - sage: S = species.SetSpecies() - sage: g = S._cis_gen(QQ) - sage: [next(g) for i in range(5)] - [0, p[1], 1/2*p[2], 1/3*p[3], 1/4*p[4]] - """ - from sage.combinat.sf.sf import SymmetricFunctions - p = SymmetricFunctions(base_ring).power() - yield p(0) - for n in _integers_from(1): - yield p([n])/n + return res #Backward compatibility diff --git a/src/sage/combinat/species/subset_species.py b/src/sage/combinat/species/subset_species.py index 435df21c831..2e6fbfbdfdb 100644 --- a/src/sage/combinat/species/subset_species.py +++ b/src/sage/combinat/species/subset_species.py @@ -17,6 +17,7 @@ #***************************************************************************** from species import GenericCombinatorialSpecies +from set_species import SetSpecies from generating_series import _integers_from, factorial_stream from structure import GenericSpeciesStructure from sage.rings.all import ZZ @@ -208,12 +209,11 @@ def _itgs_iterator(self, base_ring): def _cis(self, series_ring, base_ring): r""" - The cycle index series for the species of subsets is given by + The cycle index series for the species of subsets satisfies .. math:: - exp \left( 2 \cdot \sum_{n=1}^\infty \frac{x_n}{n} \right). - + Z_{\mathfrak{p}} = Z_{\mathcal{E}} \cdot Z_{\mathcal{E}} EXAMPLES:: @@ -226,22 +226,11 @@ def _cis(self, series_ring, base_ring): 4/3*p[1, 1, 1] + 2*p[2, 1] + 2/3*p[3], 2/3*p[1, 1, 1, 1] + 2*p[2, 1, 1] + 1/2*p[2, 2] + 4/3*p[3, 1] + 1/2*p[4]] """ - return series_ring(self._cis_gen(base_ring)).exponential() - - def _cis_gen(self, base_ring): - """ - EXAMPLES:: - - sage: S = species.SubsetSpecies() - sage: g = S._cis_gen(QQ) - sage: [next(g) for i in range(5)] - [0, 2*p[1], p[2], 2/3*p[3], 1/2*p[4]] - """ - from sage.combinat.sf.sf import SymmetricFunctions - p = SymmetricFunctions(base_ring).power() - yield base_ring(0) - for n in _integers_from(ZZ(1)): - yield 2*p([n])/n + ciset = SetSpecies().cycle_index_series(base_ring) + res = ciset**2 + if self.is_weighted(): + res *= self._weight + return res #Backward compatibility SubsetSpecies_class = SubsetSpecies diff --git a/src/sage/combinat/symmetric_group_representations.py b/src/sage/combinat/symmetric_group_representations.py index b2bba4c03f8..630da8f8574 100644 --- a/src/sage/combinat/symmetric_group_representations.py +++ b/src/sage/combinat/symmetric_group_representations.py @@ -285,6 +285,17 @@ def __init__(self, partition, ring=None, cache_matrices=True): if cache_matrices is False: self.representation_matrix = self._representation_matrix_uncached + def __hash__(self): + r""" + TESTS:: + + sage: spc1 = SymmetricGroupRepresentation([3], cache_matrices=True) + sage: hash(spc1) + -1137003014 # 32-bit + 3430541866490 # 64-bit + """ + return hash(self._ring) ^ hash(self._partition) + def __eq__(self, other): r""" Test for equality. diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 51846d38571..2dfe0da2d39 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -2845,7 +2845,7 @@ def row_stabilizer(self): k = self.size() gens = [range(1, k+1)] for row in self: - for j in range(0, len(row)-1): + for j in range(len(row)-1): gens.append( (row[j], row[j+1]) ) return PermutationGroup( gens ) @@ -4456,7 +4456,7 @@ class Tableaux(UniqueRepresentation, Parent): sage: [] in Tableaux(0) True - Check that trac:`14145` has been fixed:: + Check that :trac:`14145` has been fixed:: sage: 1 in Tableaux() False @@ -4998,7 +4998,7 @@ def __init__(self, **kwds): def __getitem__(self, r): r""" - The default implementation of ``__getitem``__ for enumerated sets + The default implementation of ``__getitem__`` for enumerated sets does not allow slices so we override it. EXAMPLES:: diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index b13039e8938..4c23db9ac0f 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -1628,7 +1628,7 @@ class TableauTuples(UniqueRepresentation, Parent): sage: TestSuite( TableauTuples(level=6, size=1) ).run() sage: TestSuite( TableauTuples(level=6, size=10) ).run() - Check that trac:`14145` has been fixed:: + Check that :trac:`14145` has been fixed:: sage: 1 in TableauTuples() False @@ -2508,7 +2508,7 @@ def __iter__(self): :class:`StandardTableauTuples` as their parent, any tuples of level 1 will actually be a :class:`StandardTableauTuples` and NOT :class:`StandardTableaux`. As such they will have a restricted set - of methods compared with usual :class:`StandardTableaux'. As they + of methods compared with usual :class:`StandardTableaux`. As they were constructed via this iterator this is presumably what is required so it should not cause any problems, especially as they are printed with brackets around them to alert the user that something is diff --git a/src/sage/combinat/tiling.py b/src/sage/combinat/tiling.py index 21201c27789..e74d7142f28 100644 --- a/src/sage/combinat/tiling.py +++ b/src/sage/combinat/tiling.py @@ -499,7 +499,7 @@ def orthogonals(self, orientation_preserving=True): def canonical_orthogonals(self, orientation_preserving=True): r""" Iterator over the image of self under orthogonal transformations - where the coordinates are all positive and minimal. + where the coordinates are all nonnegative and minimal. .. NOTE:: @@ -539,7 +539,7 @@ def canonical_orthogonals(self, orientation_preserving=True): def canonical(self): r""" - Returns the translated copy of self having minimal and positive + Returns the translated copy of self having minimal and nonnegative coordinates EXAMPLES:: diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 9d7c416e726..03b7058fc92 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -61,8 +61,8 @@ sage: Suits = Set(["Hearts", "Diamonds", "Spades", "Clubs"]) sage: Values = Set([2, 3, 4, 5, 6, 7, 8, 9, 10, - ... "Jack", "Queen", "King", "Ace"]) - sage: Cards = CartesianProduct(Values, Suits) + ....: "Jack", "Queen", "King", "Ace"]) + sage: Cards = cartesian_product([Values, Suits]) There are `4` suits and `13` possible values, and therefore `4\times 13=52` cards in the poker deck:: @@ -77,21 +77,7 @@ Draw a card at random:: sage: Cards.random_element() # random - [6, 'Clubs'] - -A small technical digression is necessary here. The elements of a -Cartesian product are returned in the form of lists:: - - sage: type(Cards.random_element()) - - -A ``Python`` list not being immutable, it cannot be an element of a set, which -would cause us a problem later. We will therefore redefine our -Cartesian product so that its elements are represented by tuples:: - - sage: Cards = CartesianProduct(Values, Suits).map(tuple) - sage: Cards.an_element() - ('King', 'Hearts') + (6, 'Clubs') Now we can define a set of cards:: @@ -136,7 +122,7 @@ We will construct the set of all flushes, so as to determine how many there are:: - sage: Flushes = CartesianProduct(Subsets(Values, 5), Suits) + sage: Flushes = cartesian_product([Subsets(Values, 5), Suits]) sage: Flushes.cardinality() 5148 @@ -157,7 +143,7 @@ function tests whether a given hand is a flush or not:: sage: def is_flush(hand): - ... return len(set(suit for (val, suit) in hand)) == 1 + ....: return len(set(suit for (val, suit) in hand)) == 1 We now draw 10000 hands at random, and count the number of flushes obtained (this takes about 10 seconds):: @@ -165,9 +151,9 @@ sage: n = 10000 sage: nflush = 0 sage: for i in range(n): # long time - ... hand = Hands.random_element() - ... if is_flush(hand): - ... nflush += 1 + ....: hand = Hands.random_element() + ....: if is_flush(hand): + ....: nflush += 1 sage: print n, nflush # random 10000 18 @@ -647,7 +633,7 @@ {2, 4} but this should be used with care because some sets have a -natural indexing other than by `(0,\dots)`. +natural indexing other than by `(0, 1, \dots)`. Conversely, one can calculate the position of an object in this order:: @@ -668,7 +654,7 @@ which is roughly `2\cdot 10^{19728}`:: - sage: n.ndigits() # long time + sage: n.ndigits() 19729 or ask for its `237102124`-th element:: @@ -914,13 +900,8 @@ [0 0] [1 1] [1 1] [1 0] [0 1] [1 1] [1 1], [1 0], [0 1], [1 1], [1 1], [1 1] ] - -The command below should return 16, but it is not yet implemented:: - sage: C.cardinality() - Traceback (most recent call last): - ... - AttributeError: 'MatrixSpace_with_category' object has no attribute 'cardinality' + 16 .. topic:: Exercise @@ -1034,7 +1015,7 @@ comprehensions provide a much pleasanter syntax:: sage: for s in Subsets(3): - ... print s + ....: print s {} {1} {2} @@ -1100,7 +1081,7 @@ sage: def mersenne(p): return 2^p -1 sage: [ is_prime(p) - ... for p in range(1000) if is_prime(mersenne(p)) ] + ....: for p in range(1000) if is_prime(mersenne(p)) ] [True, True, True, True, True, True, True, True, True, True, True, True, True, True] @@ -1112,24 +1093,24 @@ difference in the length of the calculations:: sage: all( is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p) ) + ....: for p in range(1000) if is_prime(p) ) False sage: all( [ is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p)] ) + ....: for p in range(1000) if is_prime(p)] ) False We now try to find the smallest counter-example. In order to do this, we use the ``Sage`` function ``exists``:: sage: exists( (p for p in range(1000) if is_prime(p)), - ... lambda p: not is_prime(mersenne(p)) ) + ....: lambda p: not is_prime(mersenne(p)) ) (True, 11) Alternatively, we could construct an interator on the counter-examples:: sage: counter_examples = \ - ... (p for p in range(1000) - ... if is_prime(p) and not is_prime(mersenne(p))) + ....: (p for p in range(1000) + ....: if is_prime(p) and not is_prime(mersenne(p))) sage: next(counter_examples) 11 sage: next(counter_examples) @@ -1143,10 +1124,10 @@ sage: cubes = [t**3 for t in range(-999,1000)] sage: exists([(x,y) for x in cubes for y in cubes], # long time (3s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) sage: exists(((x,y) for x in cubes for y in cubes), # long time (2s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) Which of the last two is more economical in terms of time? In terms @@ -1241,7 +1222,7 @@ :: sage: counter_examples = (p for p in Primes() - ... if not is_prime(mersenne(p))) + ....: if not is_prime(mersenne(p))) sage: for p in counter_examples: print p # not tested 11 23 @@ -1283,23 +1264,23 @@ apply a function to all the elements:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] or select the elements satisfying a certain condition:: sage: list(itertools.ifilter(lambda z: z.has_pattern([1,2]), - ... Permutations(3))) + ....: Permutations(3))) [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2]] In all these situations, ``attrcall`` can be an advantageous alternative to creating an anonymous function:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] sage: list(itertools.imap(attrcall("cycle_type"), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] Implementation of new interators @@ -1309,8 +1290,8 @@ instead of ``return`` in a function:: sage: def f(n): - ... for i in range(n): - ... yield i + ....: for i in range(n): + ....: yield i After the ``yield``, execution is not halted, but only suspended, ready to be continued from the same point. The result of the function is @@ -1343,12 +1324,12 @@ generate all words of a given length on a given alphabet:: sage: def words(alphabet,l): - ... if l == 0: - ... yield [] - ... else: - ... for word in words(alphabet, l-1): - ... for l in alphabet: - ... yield word + [l] + ....: if l == 0: + ....: yield [] + ....: else: + ....: for word in words(alphabet, l-1): + ....: for l in alphabet: + ....: yield word + [l] sage: [ w for w in words(['a','b'], 3) ] [['a', 'a', 'a'], ['a', 'a', 'b'], ['a', 'b', 'a'], ['a', 'b', 'b'], ['b', 'a', 'a'], ['b', 'a', 'b'], @@ -1372,13 +1353,13 @@ `w_1` and `w_2` are Dyck words:: sage: def dyck_words(l): - ... if l==0: - ... yield '' - ... else: - ... for k in range(l): - ... for w1 in dyck_words(k): - ... for w2 in dyck_words(l-k-1): - ... yield '('+w1+')'+w2 + ....: if l==0: + ....: yield '' + ....: else: + ....: for k in range(l): + ....: for w1 in dyck_words(k): + ....: for w2 in dyck_words(l-k-1): + ....: yield '('+w1+')'+w2 Here are all the Dyck words of length `4`:: @@ -1428,27 +1409,29 @@ Consider a large Cartesian product:: - sage: C = CartesianProduct(Compositions(8), Permutations(20)); C - Cartesian product of Compositions of 8, Standard permutations of 20 + sage: C = cartesian_product([Compositions(8), Permutations(20)]); C + The cartesian product of (Compositions of 8, Standard permutations of 20) sage: C.cardinality() 311411457046609920000 -Clearly, it is impractical to construct the list of all the elements of -this Cartesian product. For the moment, the contruction -``CartesianProduct`` ignores the algebraic properties of its arguments. -This is partially corrected in Sage 4.4.4, with the construction -``cartesian_product``. Eventually, these two constructions will be merged -and, in the following example, `H` will be equipped with the -usual combinatorial operations and also its structure as a product -group:: +Clearly, it is impractical to construct the list of all the elements of this +Cartesian product! And, in the following example, `H` is equipped with the +usual combinatorial operations and also its structure as a product group:: sage: G = DihedralGroup(4) sage: H = cartesian_product([G,G]) + sage: H in Groups() + True + sage: t = H.an_element() + sage: t + ((1,2,3,4), (1,2,3,4)) + sage: t*t + ((1,3)(2,4), (1,3)(2,4)) We now construct the union of two existing disjoint sets:: sage: C = DisjointUnionEnumeratedSets( - ... [ Compositions(4), Permutations(3)] ) + ....: [ Compositions(4), Permutations(3)] ) sage: C Disjoint union of Family (Compositions of 4, Standard permutations of 3) @@ -1487,7 +1470,7 @@ necessary to interrupt it at some point:: sage: for p in U: # not tested - ... print p + ....: print p [] [1] [1, 2] @@ -1544,21 +1527,21 @@ and length `3`, with parts bounded below by `2`, `4` and `2` respectively:: - sage: IntegerVectors(10, 3, min_part = 2, max_part = 5, - ... inner = [2, 4, 2]).list() + sage: IntegerVectors(10, 3, min_part=2, max_part=5, + ....: inner=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] The compositions of `5` with each part at most `3`, and with length `2` or `3`:: - sage: Compositions(5, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: Compositions(5, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] The strictly decreasing partitions of `5`:: - sage: Partitions(5, max_slope = -1).list() + sage: Partitions(5, max_slope=-1).list() [[5], [4, 1], [3, 2]] These sets share the same underlying algorithmic structure, implemented @@ -1570,16 +1553,16 @@ examples:: sage: IntegerListsLex(10, length=3, - ... min_part = 2, max_part = 5, - ... floor = [2, 4, 2]).list() + ....: min_part=2, max_part=5, + ....: floor=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] - sage: IntegerListsLex(5, min_part = 1, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: IntegerListsLex(5, min_part=1, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] - sage: IntegerListsLex(5, min_part = 1, max_slope = -1).list() + sage: IntegerListsLex(5, min_part=1, max_slope=-1).list() [[5], [4, 1], [3, 2]] sage: list(Compositions(5, max_length=2)) @@ -1648,7 +1631,7 @@ sage: A = random_matrix(ZZ, 6, 3, x=7) sage: L = LatticePolytope(A.rows()) - sage: L.points_pc() # random + sage: L.points() # random M(4, 1, 0), M(0, 3, 5), M(2, 2, 3), @@ -1847,7 +1830,7 @@ selecting only the children which are planar:: sage: [len(list(graphs(n, property = lambda G: G.is_planar()))) - ... for n in range(7)] + ....: for n in range(7)] [1, 1, 2, 4, 11, 33, 142] In a similar fashion, one can generate any family of graphs closed diff --git a/src/sage/combinat/vector_partition.py b/src/sage/combinat/vector_partition.py index 4b5dbea00a2..dd534040e8b 100644 --- a/src/sage/combinat/vector_partition.py +++ b/src/sage/combinat/vector_partition.py @@ -158,7 +158,7 @@ def partition_at_vertex(self, i): """ return Partition(sorted([vec[i] for vec in self._list], reverse = True)) -class VectorPartitions(Parent, UniqueRepresentation): +class VectorPartitions(UniqueRepresentation, Parent): r""" Class of all vector partitions of ``vec`` with all parts greater than or equal to ``min`` in lexicographic order. diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index d24cc704117..072662e5732 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -4873,8 +4873,8 @@ def standard_permutation(self): def _s(self, i): r""" - Implements Lascoux and Schutzenberger's `s_i` operator, swapping the - number of `i` and `i+1`s in a word. + Implement Lascoux and Schutzenberger `s_i` operator, swapping the + number of `i` and `i+1` in a word. EXAMPLES:: @@ -6766,6 +6766,76 @@ def to_monoid_element(self): M = FreeMonoid(len(l), l) return M(self) + def is_christoffel(self): + r""" + Returns True if self is a Christoffel word, and False otherwise. + + The *Christoffel word* of slope `p/q` is obtained from the Cayley + graph of `\ZZ/(p+q)\ZZ` with generator q as follows. If `u + \rightarrow v` is an edge in the Cayley graph, then, `v = u + p + \mod{p+q}`. Let `a`,`b` be the alphabet of `w`. Label the edge + `u \rightarrow v` by `a` if u < v and `b` otherwise. The Christoffel + word is the word obtained by reading the edge labels along the cycle + beginning from 0. + + Equivalently, `w` is a Christoffel word iff `w` is a symmetric + non-empty word and `w[1:n-1]` is a palindrome. + + See for instance [1]_ and [2]_. + + INPUT: + + ``self`` -- word + + OUTPUT: + + Boolean -- ``True`` if ``self`` is a Christoffel word, + ``False`` otherwise. + + EXAMPLES:: + + sage: Word('00100101').is_christoffel() + True + sage: Word('aab').is_christoffel() + True + sage: Word().is_christoffel() + False + sage: Word('123123123').is_christoffel() + False + sage: Word('00100').is_christoffel() + False + sage: Word('0').is_christoffel() + True + + TESTS:: + + sage: words.LowerChristoffelWord(5,4).is_christoffel() + True + sage: words.UpperChristoffelWord(5,4).is_christoffel() + True + sage: Word('aaaaaaaaa').is_christoffel() + False + + REFERENCES: + + .. [1] Jean Berstel. Sturmian and episturmian words (a survey of + some recent results). In S. Bozapalidis and G. Rahonis, editors, + CAI 2007,volume 4728 of Lecture Notes in Computer Science, + pages 23-47. Springer-Verlag, 2007. + .. [2] J. Berstel, A. Lauve, C. R., F. Saliola, Combinatorics on + words: Christoffel words and repetitions in words, CRM Monograph + Series, 27. American Mathematical Society, Providence, RI, 2009. + xii+147 pp. ISBN: 978-0-8218-4480-9 + + """ + if len(self) == 0 or len(self.letters()) > 2 or (self.is_palindrome() and len(self) > 1): + return False + elif self.is_symmetric() and self[1:len(self) - 1].is_palindrome(): + return True + else: + return False + + ####################################################################### class CallableFromListOfWords(tuple): diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index ed1600747b4..b1d85f48795 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -383,6 +383,17 @@ def _build_codomain(self, data): codom_alphabet.update(it) return Words(sorted(codom_alphabet)) + @cached_method + def __hash__(self): + r""" + TESTS:: + + sage: hash(WordMorphism('a->ab,b->ba')) + -1651294583 # 32-bit + 4826519950209531529 # 64-bit + """ + return hash(tuple((k,v) for k,v in self._morph.iteritems())) ^ hash(self._codomain) + def __eq__(self, other): r""" Returns ``True`` if ``self`` is equal to ``other``. @@ -735,10 +746,10 @@ def latex_layout(self, layout=None): def _latex_(self): r""" - Returns the latex representation of the morphism. + Return the latex representation of the morphism. - Use :method:`latex_layout` to change latex layout (oneliner vs - array). The default is an latex array. + Use :meth:`latex_layout` to change latex layout (oneliner vs + array). The default is a latex array. EXAMPLES:: diff --git a/src/sage/combinat/words/paths.py b/src/sage/combinat/words/paths.py index 44b36cbe864..8e9083cfae2 100644 --- a/src/sage/combinat/words/paths.py +++ b/src/sage/combinat/words/paths.py @@ -842,9 +842,9 @@ def __init__(self, alphabet): INPUT: - - ``alphabet - ordered alphabet of length 6. The order for the steps - is : e_x, e_y, e_z, -e_x, -e_y, -e_z, where e_v denotes - the canonical basis. + - ``alphabet`` -- ordered alphabet of length 6. The order for + the steps is `e_x, e_y, e_z, -e_x, -e_y, -e_z`, where `e_v` + denotes the canonical basis. EXAMPLES:: diff --git a/src/sage/combinat/words/shuffle_product.py b/src/sage/combinat/words/shuffle_product.py index 70aba6c9636..83f47dec92a 100644 --- a/src/sage/combinat/words/shuffle_product.py +++ b/src/sage/combinat/words/shuffle_product.py @@ -2,6 +2,7 @@ Shuffle product of words .. SEEALSO:: + The module :mod:`sage.combinat.shuffle` contains a more general implementation of shuffle product. """ diff --git a/src/sage/combinat/words/word_char.pyx b/src/sage/combinat/words/word_char.pyx index bef4c943c05..0c55949c019 100644 --- a/src/sage/combinat/words/word_char.pyx +++ b/src/sage/combinat/words/word_char.pyx @@ -25,6 +25,8 @@ from cpython.number cimport PyIndex_Check, PyNumber_Check from cpython.sequence cimport PySequence_Check from cpython.slice cimport PySlice_Check, PySlice_GetIndicesEx +import itertools + # the maximum value of a size_t cdef size_t SIZE_T_MAX = -( 1) @@ -550,7 +552,6 @@ cdef class WordDatatype_char(WordDatatype): return w._new_c(data, new_length, None) - @cython.boundscheck(False) def has_prefix(self, other): r""" Test whether ``other`` is a prefix of ``self``. @@ -573,6 +574,22 @@ cdef class WordDatatype_char(WordDatatype): True sage: w.has_prefix(w[1:]) False + + TESTS: + + :trac:`19322`:: + + sage: W = Words([0,1,2]) + sage: w = W([0,1,0,2]) + sage: w.has_prefix(words.FibonacciWord()) + False + + sage: w.has_prefix([0,1,0,2,0]) + False + sage: w.has_prefix([0,1,0,2]) + True + sage: w.has_prefix([0,1,0]) + True """ cdef size_t i cdef WordDatatype_char w @@ -582,15 +599,16 @@ cdef class WordDatatype_char(WordDatatype): w = other if w._length > self._length: return False - return memcmp(self._data, w._data, w._length) == 0 + return memcmp(self._data, w._data, w._length * sizeof(unsigned char)) == 0 elif PySequence_Check(other): # python level - if len(other) > self._length: + from sage.combinat.words.infinite_word import InfiniteWord_class + if isinstance(other, InfiniteWord_class) or len(other) > len(self): return False for i in range(len(other)): - if other[i] != self._data[i]: + if other[i] != self[i]: return False return True @@ -638,3 +656,151 @@ cdef class WordDatatype_char(WordDatatype): return memcmp(self._data, self._data + l, l * sizeof(unsigned char)) == 0 + + def longest_common_prefix(self, other): + r""" + Return the longest common prefix of this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_prefix([0,1]) + word: 01 + sage: u = W([0,1,0,0,1]) + sage: v = W([0,1,0,2]) + sage: u.longest_common_prefix(v) + word: 010 + sage: v.longest_common_prefix(u) + word: 010 + + Using infinite words is also possible (and the return type is also a + of the same type as ``self``):: + + sage: W([0,1,0,0]).longest_common_prefix(words.FibonacciWord()) + word: 0100 + sage: type(_) + + + An example of an intensive usage:: + + sage: W = Words([0,1]) + sage: w = words.FibonacciWord() + sage: w = W(list(w[:5000])) + sage: L = [[len(w[n:].longest_common_prefix(w[n+fibonacci(i):])) + ....: for i in range(5,15)] for n in range(1,1000)] + sage: for n,l in enumerate(L): + ....: if l.count(0) > 4: print n+1,l + 375 [0, 13, 0, 34, 0, 89, 0, 233, 0, 233] + 376 [0, 12, 0, 33, 0, 88, 0, 232, 0, 232] + 608 [8, 0, 21, 0, 55, 0, 144, 0, 377, 0] + 609 [7, 0, 20, 0, 54, 0, 143, 0, 376, 0] + 985 [0, 13, 0, 34, 0, 89, 0, 233, 0, 610] + 986 [0, 12, 0, 33, 0, 88, 0, 232, 0, 609] + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_prefix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we allow to compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[i] != w._data[i]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data, i, self) + + elif PySequence_Check(other): + # Python level + # we avoid to call len(other) since it might be an infinite word + for i,a in enumerate(itertools.islice(other, self._length)): + if self._data[i] != a: + break + else: + i += 1 + + return self._new_c(self._data, i, self) + + raise TypeError("unsupported input {}".format(other)) + + def longest_common_suffix(self, other): + r""" + Return the longest common suffix between this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_suffix([2,0,2]) + word: 02 + sage: u = W([0,1,0,0,1]) + sage: v = W([1,2,0,0,1]) + sage: u.longest_common_suffix(v) + word: 001 + sage: v.longest_common_suffix(u) + word: 001 + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_suffix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + cdef size_t lo + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we could compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[self._length-i-1] != w._data[w._length-i-1]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data+self._length-i, i, self) + + elif PySequence_Check(other): + # Python level + lo = len(other) + m = min(self._length, lo) + for i in range(m): + if self._data[self._length-i-1] != other[lo-i-1]: + break + else: + if self._length == m: + return self + else: + i += 1 + + return self._new_c(self._data+self._length-i, i, self) + + raise TypeError("unsupported input {}".format(other)) + diff --git a/src/sage/combinat/words/word_datatypes.pyx b/src/sage/combinat/words/word_datatypes.pyx index 86730d3b1c5..8e4816cb017 100644 --- a/src/sage/combinat/words/word_datatypes.pyx +++ b/src/sage/combinat/words/word_datatypes.pyx @@ -224,7 +224,7 @@ cdef class WordDatatype_list(WordDatatype): def __getitem__(self, key): r""" - Implements :method:``__getitem__`` for words stored as lists. + Implements :meth:`__getitem__` for words stored as lists. INPUT: @@ -435,7 +435,7 @@ cdef class WordDatatype_str(WordDatatype): .. note:: - This just wraps Python's builtin :method:`__contains__` for :class:`str`. + This just wraps Python's builtin :meth:`__contains__` for :class:`str`. INPUT: @@ -569,7 +569,7 @@ cdef class WordDatatype_str(WordDatatype): def __getitem__(self, key): r""" - Implements the :method:`__getitem__`. + Implements the :meth:`__getitem__`. TESTS:: diff --git a/src/sage/combinat/words/word_generators.py b/src/sage/combinat/words/word_generators.py index 22c9d63b23b..d12ea7bb48b 100644 --- a/src/sage/combinat/words/word_generators.py +++ b/src/sage/combinat/words/word_generators.py @@ -1455,10 +1455,16 @@ def _fibonacci_tile(self, n, q_0=None, q_1=3): Returns the word `q_n` defined by the recurrence below. The sequence `(q_n)_{n\in\NN}` is defined by `q_0=\varepsilon`, - `q_1=3` and `q_n = \begin{cases} - q_{n-1}q_{n-2} & \mbox{if $n\equiv 2 \mod 3$,} \\ - q_{n-1}\bar{q_{n-2}} & \mbox{if $n\equiv 0,1 \mod 3$.} - \end{cases}` where the operator `\bar{\,}` exchanges the `1` and `3`. + `q_1=3` and + + .. MATH:: + + q_n = \begin{cases} + q_{n-1}q_{n-2} & \text{if} n\equiv 2 \mod 3, \\ + q_{n-1}\bar{q_{n-2}} & \text{if} n\equiv 0,1 \mod 3. + \end{cases} + + where the operator `\bar{\,}` exchanges the `1` and `3`. INPUT: diff --git a/src/sage/combinat/words/word_infinite_datatypes.py b/src/sage/combinat/words/word_infinite_datatypes.py index 603d35d6fce..d0260ee98d0 100644 --- a/src/sage/combinat/words/word_infinite_datatypes.py +++ b/src/sage/combinat/words/word_infinite_datatypes.py @@ -223,7 +223,7 @@ def __getitem__(self, key): ... ValueError: for infinite words, start and stop values cannot be negative - Out of range index (#8673):: + Out of range index (:trac:`8673`):: sage: w = Word(lambda n:n^2, length=23) sage: w[100] @@ -370,7 +370,8 @@ def __getitem__(self, key): sage: w.length() 12 - Test getitems with indexes. + Test getitems with indexes:: + sage: w[0] 'a' sage: w[4] @@ -382,41 +383,48 @@ def __getitem__(self, key): sage: [w[i] for i in range(12)] ['a', 'b', 'b', 'a', 'b', 'a', 'a', 'b', 'b', 'a', 'a', 'b'] - Slicing. + Slicing:: + sage: w[:] word: abbabaabbaab - Prefixes. + Prefixes:: + sage: w[0:] word: abbabaabbaab sage: w[1:] word: bbabaabbaab - Suffixes. + Suffixes:: + sage: w[:0] word: sage: w[:5] word: abbab - With positive steps. + With positive steps:: + sage: w[::2] word: abbaba - With a negative start position. + With a negative start position:: + sage: w[-2:] word: ab sage: w[-20:] word: abbabaabbaab - With a negative stop position. + With a negative stop position:: + sage: w[:-1] word: abbabaabbaa sage: w[:-10] word: ab - With a negative step. + With a negative step:: + sage: w[::-2] word: babaab @@ -427,9 +435,10 @@ def __getitem__(self, key): sage: w[:1:-3] word: bbab - TESTS:: + TESTS: + + For infinite words:: - For infinite words sage: f = lambda n : add(Integer(n).digits(2)) % 2 sage: tm = Word(f); tm word: 0110100110010110100101100110100110010110... @@ -437,7 +446,8 @@ def __getitem__(self, key): sage: tm.length() +Infinity - Test getitems with indexes. + Test getitems with indexes:: + sage: tm[0] 0 sage: tm[4] @@ -449,29 +459,34 @@ def __getitem__(self, key): ... IndexError: cannot use a negative index with an infinite word - Slicing. + Slicing:: + sage: tm[:] word: 0110100110010110100101100110100110010110... - Prefixes. + Prefixes:: + sage: tm[:0] word: sage: tm[:5] word: 01101 - Suffixes. + Suffixes:: + sage: tm[0:] word: 0110100110010110100101100110100110010110... sage: tm[1:] word: 1101001100101101001011001101001100101100... - With positive steps. + With positive steps:: + sage: tm[::2] word: 0110100110010110100101100110100110010110... - With a negative step. + With a negative step:: + sage: tm[20:1:-3] word: 0011101 sage: tm[10:1:-2] diff --git a/src/sage/combinat/words/words.py b/src/sage/combinat/words/words.py index fa5aa22cf5b..a1bb2e3be03 100644 --- a/src/sage/combinat/words/words.py +++ b/src/sage/combinat/words/words.py @@ -110,7 +110,6 @@ def Words(alphabet=None, length=None, finite=True, infinite=True): return FiniteWords_length_k_over_OrderedAlphabet(alphabet, length) raise ValueError("do not know how to make a combinatorial class of words from your input") -from sage.structure.unique_representation import UniqueRepresentation class Words_all(InfiniteAbstractCombinatorialClass): r""" TESTS:: @@ -1377,11 +1376,10 @@ def iter_morphisms(self, arg=None, codomain=None, min_length=1): # create an iterable of compositions (all "compositions" if arg is # None, or [arg] otherwise) if arg is None: - # TODO in #17927: use IntegerVectors(length=n, min_part=min_length) - from sage.combinat.integer_list import IntegerListsNN + from sage.combinat.integer_lists.nn import IntegerListsNN compositions = IntegerListsNN(length=n, min_part=min_length) elif isinstance(arg, tuple): - from sage.combinat.integer_list import IntegerListsLex + from sage.combinat.integer_lists import IntegerListsLex a, b = arg compositions = IntegerListsLex(min_sum=a, max_sum=b-1, length=n, min_part=min_length) diff --git a/src/sage/combinat/yang_baxter_graph.py b/src/sage/combinat/yang_baxter_graph.py index 904dc1ee4ef..1a4b7a8f254 100644 --- a/src/sage/combinat/yang_baxter_graph.py +++ b/src/sage/combinat/yang_baxter_graph.py @@ -207,6 +207,21 @@ def _digraph(self): digraph.add_edge(u, v, l) return digraph + def __hash__(self): + r""" + TESTS:: + + sage: from sage.combinat.yang_baxter_graph import SwapIncreasingOperator + sage: ops = [SwapIncreasingOperator(i) for i in range(2)] + sage: Y = YangBaxterGraph(root=(1,2,3), operators=ops) + sage: hash(Y) + 1028420699 # 32-bit + 7656306018247013467 # 64-bit + """ + # TODO: this is ugly but unavoidable: the Yang Baxter graphs are being + # used in containers but are mutable. + return hash(self._digraph.copy(immutable=True)) + def __eq__(self, other): r""" EXAMPLES:: @@ -371,7 +386,7 @@ def successors(self, v): sage: Y.successors(Y.root()) [(1, 2, 0, 1, 0)] sage: Y.successors((1, 2, 0, 1, 0)) - [(2, 1, 0, 1, 0), (1, 2, 1, 0, 0)] + [(1, 2, 1, 0, 0), (2, 1, 0, 1, 0)] """ return [a for (a,b) in self._successors(v)] @@ -759,6 +774,17 @@ def __init__(self, i): """ self._position = i + def __hash__(self): + r""" + TESTS:: + + sage: from sage.combinat.yang_baxter_graph import SwapOperator + sage: s = [SwapOperator(i) for i in range(3)] + sage: map(hash, s) + [0, 1, 2] + """ + return hash(self._position) + def __cmp__(self, other): r""" Compare two swap operators. The comparison is done by comparing the diff --git a/src/sage/data_structures/bitset.pxi b/src/sage/data_structures/bitset.pxi index 7651d11719f..1209f9555ac 100644 --- a/src/sage/data_structures/bitset.pxi +++ b/src/sage/data_structures/bitset.pxi @@ -80,9 +80,7 @@ cdef inline bint bitset_init(bitset_t bits, mp_bitcnt_t size) except -1: bits.size = size bits.limbs = (size - 1) / (8 * sizeof(mp_limb_t)) + 1 - bits.bits = sage_calloc(bits.limbs, sizeof(mp_limb_t)) - if bits.bits == NULL: - raise MemoryError + bits.bits = check_calloc(bits.limbs, sizeof(mp_limb_t)) cdef inline bint bitset_realloc(bitset_t bits, mp_bitcnt_t size) except -1: """ @@ -165,7 +163,7 @@ cdef inline bint mpn_equal_bits(mp_srcptr b1, mp_srcptr b2, mp_bitcnt_t n): cdef mp_limb_t b2h = b2[nlimbs] return (b1h ^ b2h) & mask == 0 -cdef inline bint mpn_equal_bits_shifted(mp_srcptr b1, mp_srcptr b2, mp_bitcnt_t n, mp_bitcnt_t offset): +cdef bint mpn_equal_bits_shifted(mp_srcptr b1, mp_srcptr b2, mp_bitcnt_t n, mp_bitcnt_t offset): """ Return ``True`` iff the first n bits of *b1 and the bits ranging from offset to offset+n of *b2 agree. @@ -623,7 +621,7 @@ cdef void bitset_rshift(bitset_t r, bitset_t a, mp_bitcnt_t n): if n >= a.size: mpn_zero(r.bits, r.limbs) return - + # Number of limbs on the right of a which will totally be shifted out cdef mp_size_t nlimbs = n >> index_shift # Number of limbs to be shifted assuming r is large enough diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py new file mode 100644 index 00000000000..2f30ccb4535 --- /dev/null +++ b/src/sage/data_structures/mutable_poset.py @@ -0,0 +1,3522 @@ +r""" +Mutable Poset + +This module provides a class representing a finite partially ordered +set (poset) for the purpose of being used as a data structure. Thus +the posets introduced in this module are mutable, i.e., elements can +be added and removed from a poset at any time. + +To get in touch with Sage's "usual" posets, start with the page +:mod:`Posets ` in the reference manual. + + +.. _mutable_poset_examples: + +Examples +======== + +First Steps +----------- + +We start by creating an empty poset. This is simply done by + +:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P + poset() + +A poset should contain elements, thus let us add them with + +:: + + sage: P.add(42) + sage: P.add(7) + sage: P.add(13) + sage: P.add(3) + +Let us look at the poset again:: + + sage: P + poset(3, 7, 13, 42) + +We see that they elements are sorted using `\leq` which exists on the +integers `\ZZ`. Since this is even a total order, we could have used a +more efficient data structure. Alternativly, we can write +:: + + sage: MP([42, 7, 13, 3]) + poset(3, 7, 13, 42) + +to add several elements at once on construction. + + +A less boring Example +--------------------- + +Let us continue with a less boring example. We define the class + +:: + + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + +It is equipped with a `\leq`-operation such that `a \leq b` if all +entries of `a` are at most the corresponding entry of `b`. For +example, we have + +:: + + sage: a = T((1,1)) + sage: b = T((2,1)) + sage: c = T((1,2)) + sage: a <= b, a <= c, b <= c + (True, True, False) + +The last comparison gives ``False``, since the comparison of the +first component checks whether `2 \leq 1`. + +Now, let us add such elements to a poset:: + + sage: Q = MP([T((1, 1)), T((3, 3)), T((4, 1)), + ....: T((3, 2)), T((2, 3)), T((2, 2))]); Q + poset((1, 1), (2, 2), (2, 3), (3, 2), (3, 3), (4, 1)) + +In the representation above, the elements are sorted topologically, +smallest first. This does not (directly) show more structural +information. We can overcome this and display a "wiring layout" by +typing:: + + sage: print Q.repr_full(reverse=True) + poset((3, 3), (2, 3), (3, 2), (2, 2), (4, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (3, 3), (4, 1) + +-- (3, 3) + | +-- successors: oo + | +-- predecessors: (2, 3), (3, 2) + +-- (2, 3) + | +-- successors: (3, 3) + | +-- predecessors: (2, 2) + +-- (3, 2) + | +-- successors: (3, 3) + | +-- predecessors: (2, 2) + +-- (2, 2) + | +-- successors: (2, 3), (3, 2) + | +-- predecessors: (1, 1) + +-- (4, 1) + | +-- successors: oo + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (2, 2), (4, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + +Note that we use ``reverse=True`` to let the elements appear from +largest (on the top) to smallest (on the bottom). + +If you look at the output above, you'll see two additional elements, +namely ``oo`` (`\infty`) and ``null`` (`\emptyset`). So what are these +strange animals? The answer is simple and maybe you can guess it +already. The `\infty`-element is larger than every other element, +therefore a successor of the maximal elements in the poset. Similarly, +the `\emptyset`-element is smaller than any other element, therefore a +predecessor of the poset's minimal elements. Both do not have to scare +us; they are just there and sometimes useful. + + +AUTHORS: + +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Daniel Krenn is supported by the Austrian Science Fund (FWF): P 24644-N26. + +Classes and their Methods +========================= +""" +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject + + +class MutablePosetShell(SageObject): + r""" + A shell for an element of a :class:`mutable poset `. + + INPUT: + + - ``poset`` -- the poset to which this shell belongs. + + - ``element`` -- the element which should be + contained/encapsulated in this shell. + + OUTPUT: + + A shell for the given element. + + .. NOTE:: + + If the :meth:`element` of a shell is ``None``, then this + element is considered as "special" (see :meth:`is_special`). + There are two special elements, namely + + - a ``'null'`` (an element smaller than each other element; + it has no predecessors) and + - an ``'oo'`` (an element larger than each other element; + it has no successors). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(66) + sage: P + poset(66) + sage: s = P.shell(66) + sage: type(s) + + + .. SEEALSO:: + + :class:`MutablePoset` + """ + def __init__(self, poset, element): + r""" + See :class:`MutablePosetShell` for details. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: MutablePosetShell(P, (1, 2)) + (1, 2) + """ + self._poset_ = poset + self._element_ = element + self._key_ = self.poset.get_key(element) + self._predecessors_ = set() + self._successors_ = set() + super(MutablePosetShell, self).__init__() + + + @property + def poset(self): + r""" + The poset to which this shell belongs. + + .. SEEALSO:: + + :class:`MutablePoset` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.poset is P + True + """ + return self._poset_ + + + @property + def element(self): + r""" + The element contained in this shell. + + .. SEEALSO:: + + :meth:`key`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.element + (1, 2) + """ + return self._element_ + + + @property + def key(self): + r""" + The key of the element contained in this shell. + + The key of an element is determined by the mutable poset (the + parent) via the ``key``-function (see construction of a + :class:`MutablePoset`). + + .. SEEALSO:: + + :meth:`element`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: P = MP() + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.key + (1, 2) + sage: Q = MP(key=lambda k: k[0]) + sage: f = MutablePosetShell(Q, (1, 2)) + sage: f.key + 1 + + Test the caching of the key:: + + sage: def k(k): + ....: print 'key %s' % (k,) + ....: return k + sage: R = MP(key=k) + sage: h = MutablePosetShell(R, (1, 2)) + key (1, 2) + sage: h.key; h.key + (1, 2) + (1, 2) + """ + return self._key_ + + + def predecessors(self, reverse=False): + r""" + Return the predecessors of this shell. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, then return + successors instead. + + OUTPUT: + + A set. + + .. SEEALSO:: + + :meth:`successors`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.predecessors() + set() + """ + if reverse: + return self._successors_ + return self._predecessors_ + + + def successors(self, reverse=False): + r""" + Return the successors of this shell. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, then return + predecessors instead. + + OUTPUT: + + A set. + + .. SEEALSO:: + + :meth:`predecessors`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: e.successors() + set() + """ + if reverse: + return self._predecessors_ + return self._successors_ + + + def is_special(self): + r""" + Return whether this shell contains either the null-element, i.e., the + element smaller than any possible other element or the + infinity-element, i.e., the element larger than any possible + other element. + + INPUT: + + Nothing. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_oo`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_special() + True + sage: P.oo.is_special() + True + """ + return self.element is None + + + def is_null(self): + r""" + Return whether this shell contains the null-element, i.e., the element + smaller than any possible other element. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_special`, + :meth:`is_oo`, + :meth:`MutablePoset.null`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_null() + True + sage: P.oo.is_null() + False + """ + return self.element is None and not self.predecessors() + + + def is_oo(self): + r""" + Return whether this shell contains the infinity-element, i.e., the element + larger than any possible other element. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_special`, + :meth:`MutablePoset.oo`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.null.is_oo() + False + sage: P.oo.is_oo() + True + """ + return self.element is None and not self.successors() + + + def _repr_(self): + r""" + Return the representation of this shell. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + .. NOTE:: + + If the :meth:`element` of this shell is not ``None``, + this method returns the respective representation string. + Otherwise, ``'null'`` or ``'oo'`` are returned, + depending on the non-existence of predecessors or + successors, respectively. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: repr(MutablePosetShell(P, (1, 2))) # indirect doctest + '(1, 2)' + sage: repr(P.null) # indirect doctest + 'null' + sage: repr(P.oo) # indirect doctest + 'oo' + """ + if self.is_null(): + return 'null' + elif self.is_oo(): + return 'oo' + else: + return repr(self.element) + + + def __hash__(self): + r""" + Return the hash of this shell. + + INPUT: + + Nothing. + + OUTPUT: + + A hash value. + + This returns the hash value of the key of the element + contained in this shell. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: hash(MutablePosetShell(P, (1, 2))) == hash((1, 2)) + True + """ + return hash(self.key) + + + def le(self, other, reverse=False): + r""" + Return whether this shell is less than or equal to ``other``. + + INPUT: + + - ``other`` -- a shell. + + - ``reverse`` -- (default: ``False``) if set, then return + whether this shell is greater than or equal to ``other``. + + OUTPUT: + + ``True`` or ``False``. + + .. NOTE:: + + The comparison of the shells is based on the comparison + of the keys of the elements contained in the shells, + except for special shells (see :class:`MutablePosetShell`). + + .. SEEALSO:: + + :meth:`eq`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: z = P.null + sage: oo = P.oo + sage: z <= e # indirect doctest + True + sage: e <= oo # indirect doctest + True + sage: z <= oo # indirect doctest + True + sage: oo <= z # indirect doctest + False + sage: oo <= e # indirect doctest + False + sage: e <= z # indirect doctest + False + sage: z <= z # indirect doctest + True + sage: oo <= oo # indirect doctest + True + sage: e <= e # indirect doctest + True + + :: + + sage: z.le(e, reverse=True) + False + sage: e.le(oo, reverse=True) + False + sage: z.le(oo, reverse=True) + False + sage: oo.le(z, reverse=True) + True + sage: oo.le(e, reverse=True) + True + sage: e.le(z, reverse=True) + True + sage: z.le(z, reverse=True) + True + sage: oo.le(oo, reverse=True) + True + sage: e.le(e, reverse=True) + True + """ + if reverse: + return other.le(self, reverse=False) + + if self.element is None: + if not self._predecessors_: + # null on the left + return True + else: + # oo on the left + if other.element is None: + # null or oo on the right + return not other._successors_ + else: + # not null, not oo on the right + return False + elif other.element is None: + # null/oo on the right + return not other._successors_ + + return self.key <= other.key + + + __le__ = le + + + def eq(self, other): + r""" + Return whether this shell is equal to ``other``. + + INPUT: + + - ``other`` -- a shell. + + OUTPUT: + + ``True`` or ``False``. + + .. NOTE:: + + This method compares the keys of the elements contained + in the (non-special) shells. In particular, + elements/shells with the same key are considered as equal. + + .. SEEALSO:: + + :meth:`le`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: from sage.data_structures.mutable_poset import MutablePosetShell + sage: e = MutablePosetShell(P, (1, 2)) + sage: f = MutablePosetShell(P, (2, 1)) + sage: z = P.null + sage: oo = P.oo + sage: z == z + True + sage: oo == oo + True + sage: e == e + True + sage: e == f + False + sage: z == e + False + sage: e == oo + False + sage: oo == z + False + + Comparing elements in different mutable posets is possible; their + shells are equal if their elements are:: + + sage: S = MP([42]); s = S.shell(42) + sage: T = MP([42]); t = T.shell(42) + sage: s == t + True + sage: S.oo == T.oo + True + """ + if self.element is None and other.element is None: + return self.is_null() == other.is_null() + return self.key == other.key + + + __eq__ = eq + + + def _copy_all_linked_(self, memo, poset, mapping): + r""" + Return a copy of this shell. All shells linked to this shell + are copied as well. + + This is a helper function for :meth:`MutablePoset.copy`. + + INPUT: + + - ``memo`` -- a dictionary which assigns to the id of the + calling shell to a copy of it. + + - ``poset`` -- the poset to which the newly created shells + belongs. Note that the elements are not inserted into + ``poset``; this is done in the calling method + :meth:`MutablePoset._copy_shells_`. + + - ``mapping`` -- a function which is applied on each of the elements. + + OUTPUT: + + A new shell. + + .. SEEALSO:: + + :meth:`MutablePoset.copy`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: Q = MP() + sage: memo = {} + sage: z = P.null._copy_all_linked_(memo, Q, lambda e: e) + sage: z.poset is Q + True + sage: oo = z.successors().pop() + sage: oo.is_oo() + True + + Note that :meth:`_copy_all_linked_` does not change the mutable + poset ``Q`` (this is done in the calling method + :meth:`MutablePoset._copy_shells_`). Thus we have + :: + + sage: oo is Q.oo + False + """ + try: + return memo[id(self)] + except KeyError: + pass + + new = self.__class__(poset, mapping(self.element) + if self.element is not None else None) + memo[id(self)] = new + + for reverse in (False, True): + for e in self.successors(reverse): + new.successors(reverse).add(e._copy_all_linked_(memo, poset, mapping)) + + return new + + + def lower_covers(self, shell, reverse=False): + r""" + Return the lower covers of the specified ``shell``; + the search is started at this (``self``) shell. + + A lower cover of `x` is an element `y` of the poset + such that `y < x` and there is no element `z` of the poset + so that `y < z < x`. + + INPUT: + + - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. + + - ``reverse`` -- (default: ``False``) if set, then find + the upper covers (see also :meth:`upper_covers`) + instead of the lower covers. + + OUTPUT: + + A set of :class:`shells `. + + .. NOTE:: + + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'oo'``. + Thus, only shells which are (not necessarily + direct) successors of this shell are considered. + + If ``reverse`` is ``True``, then the reverse direction is + taken. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: e = P.shell(T((2, 2))); e + (2, 2) + sage: sorted(P.null.lower_covers(e), + ....: key=lambda c: repr(c.element)) + [(1, 2), (2, 1)] + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() + True + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + + .. SEEALSO:: + + :meth:`upper_covers`, + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. + """ + if self == shell: + return set() + covers = set().union(*(e.lower_covers(shell, reverse) + for e in self.successors(reverse) + if e.le(shell, reverse))) + return covers or set([self]) + + + def upper_covers(self, shell, reverse=False): + r""" + Return the upper covers of the specified ``shell``; + the search is started at this (``self``) shell. + + An upper cover of `x` is an element `y` of the poset + such that `x < y` and there is no element `z` of the poset + so that `x < z < y`. + + INPUT: + + - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. + + - ``reverse`` -- (default: ``False``) if set, then find + the lower covers (see also :meth:`lower_covers`) + instead of the upper covers. + + OUTPUT: + + A set of :class:`shells `. + + .. NOTE:: + + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'null'``. + Thus, only shells which are (not necessarily + direct) predecessors of this shell are considered. + + If ``reverse`` is ``True``, then the reverse direction is + taken. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: e = P.shell(T((2, 2))); e + (2, 2) + sage: sorted(P.null.lower_covers(e), + ....: key=lambda c: repr(c.element)) + [(1, 2), (2, 1)] + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() + True + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + + .. SEEALSO:: + + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. + """ + return self.lower_covers(shell, not reverse) + + + def _iter_depth_first_visit_(self, marked, + reverse=False, key=None, + condition=None): + r""" + Return an iterator over all shells in depth first order. + + This is a helper function for :meth:`iter_depth_first`. + + INPUT: + + - ``marked`` -- a set in which marked shells are stored. + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: P.add(5) + sage: marked = set() + sage: list(P.oo._iter_depth_first_visit_(marked, reverse=True)) + [oo, 42, 5, null] + """ + if (condition is not None and + not self.is_special() and not condition(self)): + return + if self in marked: + return + marked.add(self) + yield self + S = self.successors(reverse) + if key is not None: + S = sorted(S, key=key) + for shell in S: + for e in shell._iter_depth_first_visit_(marked, reverse, + key, condition): + yield e + + + def iter_depth_first(self, reverse=False, key=None, condition=None): + r""" + Iterate over all shells in depth first order. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + + ALGORITHM: + + See :wikipedia:`Depth-first_search`. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.null.iter_depth_first(reverse=False, key=repr)) + [null, (1, 1), (1, 2), (1, 3), (4, 4), oo, (2, 2), (2, 1)] + sage: list(P.oo.iter_depth_first(reverse=True, key=repr)) + [oo, (4, 4), (1, 3), (1, 2), (1, 1), null, (2, 2), (2, 1)] + sage: list(P.null.iter_depth_first( + ....: condition=lambda s: s.element[0] == 1)) + [null, (1, 1), (1, 2), (1, 3)] + + .. SEEALSO:: + + :meth:`iter_topological`, + :class:`MutablePoset`. + """ + marked = set() + return self._iter_depth_first_visit_(marked, reverse, key, condition) + + + def _iter_topological_visit_(self, marked, + reverse=False, key=None, + condition=None): + r""" + Return an iterator over all shells in topological order. + + This is a helper function for :meth:`iter_topological`. + + INPUT: + + - ``marked`` -- a set in which marked shells are stored. + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct predecessors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: P.add(5) + sage: marked = set() + sage: list(P.null._iter_topological_visit_(marked, reverse=True)) + [oo, 42, 5, null] + """ + if (condition is not None and + not self.is_special() and not condition(self)): + return + if self in marked: + return + marked.add(self) + S = self.predecessors(reverse) + if key is not None: + S = sorted(S, key=key) + for shell in S: + for e in shell._iter_topological_visit_(marked, reverse, + key, condition): + yield e + yield self + + + def iter_topological(self, reverse=False, key=None, condition=None): + r""" + Iterate over all shells in topological order. + + INPUT: + + - ``reverse`` -- (default: ``False``) if set, reverses the + order, i.e., ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct predeccessors of a shell (used in case of a + tie). If this is ``None``, no sorting occurs. + + - ``condition`` -- (default: ``None``) a function mapping a + shell to ``True`` (include in iteration) or ``False`` (do + not include). ``None`` is equivalent to a function returning + always ``True``. Note that the iteration does not go beyond a + not included shell. + + OUTPUT: + + An iterator. + + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + + ALGORITHM: + + Here a simplified version of the algorithm found in [T1976]_ + and [CLRS2001]_ is used. See also + :wikipedia:`Topological_sorting`. + + .. [T1976] Robert E. Tarjan, *Edge-disjoint spanning trees and + depth-first search*, Acta Informatica 6 (2), 1976, 171-185, + :doi:`10.1007/BF00268499`. + + .. [CLRS2001] Thomas H. Cormen, Charles E. Leiserson, Ronald + L. Rivest and Clifford Stein, *Section 22.4: Topological + sort*, Introduction to Algorithms (2nd ed.), MIT Press and + McGraw-Hill, 2001, 549-552, ISBN 0-262-03293-7. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + + :: + + sage: for e in P.shells_topological(include_special=True, + ....: reverse=True): + ....: print e + ....: print list(e.iter_topological(reverse=True, key=repr)) + oo + [oo] + (4, 4) + [oo, (4, 4)] + (1, 3) + [oo, (4, 4), (1, 3)] + (2, 2) + [oo, (4, 4), (2, 2)] + (1, 2) + [oo, (4, 4), (1, 3), (2, 2), (1, 2)] + (2, 1) + [oo, (4, 4), (2, 2), (2, 1)] + (1, 1) + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)] + null + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1), null] + + :: + + sage: for e in P.shells_topological(include_special=True, + ....: reverse=True): + ....: print e + ....: print list(e.iter_topological(reverse=False, key=repr)) + oo + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4), oo] + (4, 4) + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4)] + (1, 3) + [null, (1, 1), (1, 2), (1, 3)] + (2, 2) + [null, (1, 1), (1, 2), (2, 1), (2, 2)] + (1, 2) + [null, (1, 1), (1, 2)] + (2, 1) + [null, (1, 1), (2, 1)] + (1, 1) + [null, (1, 1)] + null + [null] + + :: + + sage: list(P.null.iter_topological( + ....: reverse=True, condition=lambda s: s.element[0] == 1)) + [(1, 3), (1, 2), (1, 1), null] + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`MutablePoset.shells_topological`, + :meth:`MutablePoset.elements_topological`, + :meth:`MutablePoset.keys_topological`, + :class:`MutablePoset`. + """ + marked = set() + return self._iter_topological_visit_(marked, reverse, key, condition) + + + def merge(self, element, check=True, delete=True): + r""" + Merge the given element with the element contained in this + shell. + + INPUT: + + - ``element`` -- an element (of the poset). + + - ``check`` -- (default: ``True``) if set, then the + ``can_merge``-function of :class:`MutablePoset` determines + whether the merge is possible. ``can_merge`` is ``None`` means + that this check is always passed. + + - ``delete`` -- (default: ``True``) if set, then ``element`` + is removed from the poset after the merge. + + OUTPUT: + + Nothing. + + .. NOTE:: + + This operation depends on the parameters ``merge`` and + ``can_merge`` of the :class:`MutablePoset` this shell is + contained in. These parameters are defined when the poset + is constructed. + + .. NOTE:: + + If the ``merge`` function returns ``None``, then this shell + is removed from the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: def add(left, right): + ....: return (left[0], ''.join(sorted(left[1] + right[1]))) + sage: def can_add(left, right): + ....: return left[0] <= right[0] + sage: P = MP([(1, 'a'), (3, 'b'), (2, 'c'), (4, 'd')], + ....: key=lambda c: c[0], merge=add, can_merge=can_add) + sage: P + poset((1, 'a'), (2, 'c'), (3, 'b'), (4, 'd')) + sage: P.shell(2).merge((3, 'b')) + sage: P + poset((1, 'a'), (2, 'bc'), (4, 'd')) + + .. SEEALSO:: + + :meth:`MutablePoset.merge`, + :class:`MutablePoset`. + + TESTS:: + + sage: MP([2], merge=operator.add, + ....: can_merge=lambda _, __: False).shell(2).merge(1) + Traceback (most recent call last): + ... + RuntimeError: Cannot merge 2 with 1. + """ + poset = self.poset + if poset._merge_ is None: + # poset._merge_ is None means no merge (poset._merge_ simply + # returns its first input argument). + return + self_element = self.element + if check: + if not poset._can_merge_(self_element, element): + raise RuntimeError('Cannot merge %s with %s.' % + (self_element, element)) + new = poset._merge_(self_element, element) + if new is None: + poset.discard(poset.get_key(self.element)) + else: + self._element_ = new + if delete: + poset.remove(poset.get_key(element)) + + +# ***************************************************************************** + + +def is_MutablePoset(P): + r""" + Test whether ``P`` inherits from :class:`MutablePoset`. + + .. SEEALSO:: + + :class:`MutablePoset` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: from sage.data_structures.mutable_poset import is_MutablePoset + sage: P = MP() + sage: is_MutablePoset(P) + True + """ + return isinstance(P, MutablePoset) + + +class MutablePoset(SageObject): + r""" + A data structure that models a mutable poset (partially ordered + set). + + INPUT: + + - ``data`` -- data from which to construct the poset. It can be + any of the following: + + #. ``None`` (default), in which case an empty poset is created, + + #. a :class:`MutablePoset`, which will be copied during creation, + + #. an iterable, whose elements will be in the poset. + + - ``key`` -- a function which maps elements to keys. If ``None`` + (default), this is the identity, i.e., keys are equal to their + elements. + + Two elements with the same keys are considered as equal; so only + one of these two elements can be in the poset. + + This ``key`` is not used for sorting (in contrast to + sorting-functions, e.g. ``sorted``). + + - ``merge`` -- a function which merges its second argument (an + element) to its first (again an element) and returns the result + (as an element). If the return value is ``None``, the element is + removed from the poset. + + This hook is called by :meth:`merge`. Moreover it is used during + :meth:`add` when an element (more precisely its key) is already + in this poset. + + ``merge`` is ``None`` (default) is equivalent to ``merge`` + returning its first argument. Note that it is not allowed that the + key of the returning element differs from the key of the first + input parameter. This means ``merge`` must not change the + position of the element in the poset. + + - ``can_merge`` -- a function which checks whether its second argument + can be merged to its first. + + This hook is called by :meth:`merge`. Moreover it is used during + :meth:`add` when an element (more precisely its key) is already + in this poset. + + ``can_merge`` is ``None`` (default) is equivalent to ``can_merge`` + returning ``True`` in all cases. + + OUTPUT: + + A mutable poset. + + You can find a short introduction and examples + :mod:`here `. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + + We illustrate the different input formats + + #. No input:: + + sage: A = MP(); A + poset() + + #. A :class:`MutablePoset`:: + + sage: B = MP(A); B + poset() + sage: B.add(42) + sage: C = MP(B); C + poset(42) + + #. An iterable:: + + sage: C = MP([5, 3, 11]); C + poset(3, 5, 11) + + .. SEEALSO:: + + :class:`MutablePosetShell`. + """ + def __init__(self, data=None, key=None, merge=None, can_merge=None): + r""" + See :class:`MutablePoset` for details. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: MP() + poset() + + :: + + sage: P = MP() + sage: P.add(42) + sage: MP(P) + poset(42) + + :: + + sage: MP([3, 5, 7]) + poset(3, 5, 7) + + :: + + sage: MP(33) + Traceback (most recent call last): + ... + TypeError: 33 is not iterable; do not know what to do with it. + """ + if is_MutablePoset(data): + if key is not None: + raise TypeError('Cannot use key when data is a poset.') + self._copy_shells_(data, lambda e: e) + + else: + self.clear() + + if key is None: + self._key_ = lambda k: k + else: + self._key_ = key + + self._merge_ = merge + if can_merge is None: + self._can_merge_ = lambda _, __: True + else: + self._can_merge_ = can_merge + + if data is not None: + try: + it = iter(data) + except TypeError: + raise TypeError('%s is not iterable; do not know what to ' + 'do with it.' % (data,)) + self.union_update(it) + super(MutablePoset, self).__init__() + + + def clear(self): + r""" + Remove all elements from this poset. + + INPUT: + + Nothing. + + OUTPUT: + + Nothing. + + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42); P + poset(42) + sage: P.clear() + sage: print P.repr_full() + poset() + +-- null + | +-- no predecessors + | +-- successors: oo + +-- oo + | +-- predecessors: null + | +-- no successors + """ + self._null_ = MutablePosetShell(self, None) + self._oo_ = MutablePosetShell(self, None) + self._null_.successors().add(self._oo_) + self._oo_.predecessors().add(self._null_) + self._shells_ = {} + + + def __len__(self): + r""" + Return the number of elements contained in this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` are not counted. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: len(P) # indirect doctest + 0 + sage: bool(P) + False + sage: P.add(42) + sage: len(P) + 1 + sage: bool(P) + True + """ + return len(self._shells_) + + + @property + def null(self): + r""" + The shell `\emptyset` whose element is smaller than any + other element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: z = P.null; z + null + sage: z.is_null() + True + + .. SEEALSO:: + + :meth:`oo`, + :meth:`MutablePosetShell.is_null`, + :meth:`MutablePosetShell.is_special`. + """ + return self._null_ + + + @property + def oo(self): + r""" + The shell `\infty` whose element is larger than any other + element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: oo = P.oo; oo + oo + sage: oo.is_oo() + True + + .. SEEALSO:: + + :meth:`null`, + :meth:`MutablePosetShell.is_oo`, + :meth:`MutablePosetShell.is_special`. + """ + return self._oo_ + + + def shell(self, key): + r""" + Return the shell of the element corresponding to ``key``. + + INPUT: + + ``key`` -- the key of an object. + + OUTPUT: + + An instance of :class:`MutablePosetShell`. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: e = P.shell(42); e + 42 + sage: type(e) + + + .. SEEALSO:: + + :meth:`element`, + :meth:`get_key`. + """ + return self._shells_[key] + + + def element(self, key): + r""" + Return the element corresponding to ``key``. + + INPUT: + + ``key`` -- the key of an object. + + OUTPUT: + + An object. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(42) + sage: e = P.element(42); e + 42 + sage: type(e) + + + .. SEEALSO:: + + :meth:`shell`, + :meth:`get_key`. + """ + return self.shell(key).element + + + def get_key(self, element): + r""" + Return the key corresponding to the given element. + + INPUT: + + - ``element`` -- an object. + + OUTPUT: + + An object (the key of ``element``). + + .. SEEALSO:: + + :meth:`element`, + :meth:`shell`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.get_key(None) is None + True + sage: P.get_key((1, 2)) + (1, 2) + sage: Q = MP(key=lambda k: k[0]) + sage: Q.get_key((1, 2)) + 1 + """ + if element is None: + return None + return self._key_(element) + + + def _copy_shells_(self, other, mapping): + r""" + Copy shells from another poset. + + INPUT: + + - ``other`` -- the mutable poset from which the shells + should be copied to this poset. + + - ``mapping`` -- a function that is applied to each element. + + OUTPUT: + + Nothing. + + .. SEEALSO:: + + :meth:`copy` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP() + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: Q = MP() + sage: Q._copy_shells_(P, lambda e: e) + sage: P.repr_full() == Q.repr_full() + True + """ + from copy import copy + self._key_ = copy(other._key_) + self._merge_ = copy(other._merge_) + self._can_merge_ = copy(other._can_merge_) + memo = {} + self._null_ = other._null_._copy_all_linked_(memo, self, mapping) + self._oo_ = memo[id(other._oo_)] + self._shells_ = dict((f.key, f) for f in + iter(memo[id(e)] for e in + other._shells_.itervalues())) + + + def copy(self, mapping=None): + r""" + Create a shallow copy. + + INPUT: + + - ``mapping`` -- a function which is applied on each of the elements. + + OUTPUT: + + A poset with the same content as ``self``. + + .. SEEALSO:: + + :meth:`map`, + :meth:`mapped`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: Q = copy(P) # indirect doctest + sage: P.repr_full() == Q.repr_full() + True + """ + if mapping is None: + mapping = lambda element: element + new = self.__class__() + new._copy_shells_(self, mapping) + return new + + + __copy__ = copy + + + def shells(self, include_special=False): + r""" + Return an iterator over all shells. + + INPUT: + + - ``include_special`` -- (default: ``False``) if set, then + including shells containing a smallest element (`\emptyset`) + and a largest element (`\infty`). + + OUTPUT: + + An iterator. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: tuple(P.shells()) + () + sage: tuple(P.shells(include_special=True)) + (null, oo) + + .. SEEALSO:: + + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + if include_special: + yield self.null + for e in self._shells_.itervalues(): + yield e + if include_special: + yield self.oo + + + def shells_topological(self, include_special=False, + reverse=False, key=None): + r""" + Return an iterator over all shells in topological order. + + INPUT: + + - ``include_special`` -- (default: ``False``) if set, then + including shells containing a smallest element (`\emptyset`) + and a largest element (`\infty`). + + - ``reverse`` -- (default: ``False``) -- if set, reverses the + order, i.e., ``False`` gives smallest elements first, + ``True`` gives largest first. + + - ``key`` -- (default: ``None``) a function used for sorting + the direct successors of a shell (used in case of a tie). If + this is ``None``, then the successors are sorted according + to their representation strings. + + OUTPUT: + + An iterator. + + .. NOTE:: + + Each element is contained/encapsulated in a shell inside + the poset. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.shells_topological()) + [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4)] + sage: list(P.shells_topological(reverse=True)) + [(4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)] + sage: list(P.shells_topological(include_special=True)) + [null, (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4), oo] + sage: list(P.shells_topological( + ....: include_special=True, reverse=True)) + [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1), null] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + if key is None: + key = repr + shell = self.oo if not reverse else self.null + return iter(e for e in shell.iter_topological(reverse, key) + if include_special or not e.is_special()) + + + def elements(self, **kwargs): + r""" + Return an iterator over all elements. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]) + sage: [(v, type(v)) for v in sorted(P.elements())] + [(3, ), + (7, ), + (42, )] + + Note that + + :: + + sage: it = iter(P) + sage: sorted(it) + [3, 7, 42] + + returns all elements as well. + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells(**kwargs): + yield shell.element + + + __iter__ = elements + + + def elements_topological(self, **kwargs): + r""" + Return an iterator over all elements in topological order. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: [(v, type(v)) for v in P.elements_topological()] + [((1, 1), ), + ((1, 2), ), + ((1, 3), ), + ((2, 1), ), + ((2, 2), ), + ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells_topological(**kwargs): + yield shell.element + + + def keys(self, **kwargs): + r""" + Return an iterator over all keys of the elements. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7], key=lambda c: -c) + sage: [(v, type(v)) for v in sorted(P.keys())] + [(-42, ), + (-7, ), + (-3, )] + + sage: [(v, type(v)) for v in sorted(P.elements())] + [(3, ), + (7, ), + (42, )] + + sage: [(v, type(v)) for v in sorted(P.shells(), + ....: key=lambda c: c.element)] + [(3, ), + (7, ), + (42, )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells(**kwargs): + yield shell.key + + + def keys_topological(self, **kwargs): + r""" + Return an iterator over all keys of the elements in + topological order. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([(1, 1), (2, 1), (4, 4)], + ....: key=lambda c: c[0]) + sage: [(v, type(v)) for v in P.keys_topological()] + [(1, ), + (2, ), + (4, )] + sage: [(v, type(v)) for v in P.elements_topological()] + [((1, 1), ), + ((2, 1), ), + ((4, 4), )] + sage: [(v, type(v)) for v in P.shells_topological()] + [((1, 1), ), + ((2, 1), ), + ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. + """ + for shell in self.shells_topological(**kwargs): + yield shell.key + + + def repr(self, include_special=False, reverse=False): + r""" + Return a representation of the poset. + + INPUT: + + - ``include_special`` -- (default: ``False``) a boolean + indicating whether to include the special elements + ``'null'`` and ``'oo'`` or not. + + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. + + OUTPUT: + + A string. + + .. SEEALSO:: + + :meth:`repr_full` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: print MP().repr() + poset() + """ + s = 'poset(' + s += ', '.join(repr(shell) for shell in + self.shells_topological(include_special, reverse)) + s += ')' + return s + + + def repr_full(self, reverse=False): + r""" + Return a representation with ordering details of the poset. + + INPUT: + + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. + + OUTPUT: + + A string. + + .. SEEALSO:: + + :meth:`repr` + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: print MP().repr_full(reverse=True) + poset() + +-- oo + | +-- no successors + | +-- predecessors: null + +-- null + | +-- successors: oo + | +-- no predecessors + """ + sortedshells = tuple( + self.shells_topological(include_special=True, reverse=reverse)) + strings = [self.repr(include_special=False, reverse=reverse)] + for shell in sortedshells: + strings.append('+-- ' + repr(shell)) + for rev in (not reverse, reverse): + what = 'successors' if not rev else 'predecessors' + if shell.successors(rev): + s = '| +-- ' + what + ': ' + s += ', '.join(repr(e) for e in + sortedshells if e in shell.successors(rev)) + else: + s = '| +-- no ' + what + strings.append(s) + return '\n'.join(strings) + + + _repr_ = repr + + + def contains(self, key): + r""" + Test whether ``key`` is encapsulated by one of the poset's elements. + + INPUT: + + - ``key`` -- an object. + + OUTPUT: + + ``True`` or ``False``. + + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`keys`. + + TESTS:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP() + sage: P.add(T((1, 1))) + sage: T((1, 1)) in P # indirect doctest + True + sage: T((1, 2)) in P # indirect doctest + False + """ + return key in self._shells_ + + + __contains__ = contains + + + def add(self, element): + r""" + Add the given object as element to the poset. + + INPUT: + + - ``element`` -- an object (hashable and supporting comparison + with the operator ``<=``). + + OUTPUT: + + Nothing. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 1) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (1, 2) + | +-- successors: (1, 3) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (4, 4) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + sage: P.add(T((2, 2))) + sage: reprP = P.repr_full(reverse=True); print reprP + poset((4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2), (2, 1) + +-- (1, 2) + | +-- successors: (1, 3), (2, 2) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + + When adding an element which is already in the poset, nothing happens:: + + sage: e = T((2, 2)) + sage: P.add(e) + sage: P.repr_full(reverse=True) == reprP + True + + We can influence the behavior when an element with existing key + is to be inserted in the poset. For example, we can perform an + addition on some argument of the elements:: + + sage: def add(left, right): + ....: return (left[0], ''.join(sorted(left[1] + right[1]))) + sage: A = MP(key=lambda k: k[0], merge=add) + sage: A.add((3, 'a')) + sage: A + poset((3, 'a')) + sage: A.add((3, 'b')) + sage: A + poset((3, 'ab')) + + We can also deal with cancellations. If the return value of + our hook-function is ``None``, then the element is removed out of + the poset:: + + sage: def add_None(left, right): + ....: s = left[1] + right[1] + ....: if s == 0: + ....: return None + ....: return (left[0], s) + sage: B = MP(key=lambda k: k[0], + ....: merge=add_None) + sage: B.add((7, 42)) + sage: B.add((7, -42)) + sage: B + poset() + + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + + TESTS:: + + sage: R = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[2:3])) + sage: print R.repr_full(reverse=True) + poset((1, 1, 42), (2, 1, 7)) + +-- oo + | +-- no successors + | +-- predecessors: (1, 1, 42) + +-- (1, 1, 42) + | +-- successors: oo + | +-- predecessors: (2, 1, 7) + +-- (2, 1, 7) + | +-- successors: (1, 1, 42) + | +-- predecessors: null + +-- null + | +-- successors: (2, 1, 7) + | +-- no predecessors + + :: + + sage: P = MP() + sage: P.add(None) + Traceback (most recent call last): + ... + ValueError: None is not an allowed element. + """ + if element is None: + raise ValueError('None is not an allowed element.') + key = self.get_key(element) + + if key in self._shells_: + if self._merge_ is not None: + self.shell(key).merge(element, delete=False) + return + + new = MutablePosetShell(self, element) + new._predecessors_ = self.null.lower_covers(new) + new._successors_ = self.oo.upper_covers(new) + + for s in new.predecessors(): + for l in s.successors().intersection(new.successors()): + l.predecessors().remove(s) + s.successors().remove(l) + s.successors().add(new) + for l in new.successors(): + l.predecessors().add(new) + + self._shells_[key] = new + + + def remove(self, key, raise_key_error=True): + r""" + Remove the given object from the poset. + + INPUT: + + - ``key`` -- the key of an object. + + - ``raise_key_error`` -- (default: ``True``) switch raising + ``KeyError`` on and off. + + OUTPUT: + + Nothing. + + If the element is not a member and ``raise_key_error`` is set + (default), raise a ``KeyError``. + + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (1, 2), (2, 1) + +-- (1, 2) + | +-- successors: (1, 3), (2, 2) + | +-- predecessors: (1, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 2), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + sage: P.remove(T((1, 2))) + sage: print P.repr_full(reverse=True) + poset((4, 4), (1, 3), (2, 2), (2, 1), (1, 1)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4) + +-- (4, 4) + | +-- successors: oo + | +-- predecessors: (1, 3), (2, 2) + +-- (1, 3) + | +-- successors: (4, 4) + | +-- predecessors: (1, 1) + +-- (2, 2) + | +-- successors: (4, 4) + | +-- predecessors: (2, 1) + +-- (2, 1) + | +-- successors: (2, 2) + | +-- predecessors: (1, 1) + +-- (1, 1) + | +-- successors: (1, 3), (2, 1) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1) + | +-- no predecessors + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`pop`. + + TESTS:: + + sage: Q = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[0:2])) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 42), (1, 3, 42), (2, 2, 7), + (1, 2, 7), (2, 1, 7), (1, 1, 42)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 42) + +-- (4, 4, 42) + | +-- successors: oo + | +-- predecessors: (1, 3, 42), (2, 2, 7) + +-- (1, 3, 42) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7) + +-- (2, 2, 7) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7), (2, 1, 7) + +-- (1, 2, 7) + | +-- successors: (1, 3, 42), (2, 2, 7) + | +-- predecessors: (1, 1, 42) + +-- (2, 1, 7) + | +-- successors: (2, 2, 7) + | +-- predecessors: (1, 1, 42) + +-- (1, 1, 42) + | +-- successors: (1, 2, 7), (2, 1, 7) + | +-- predecessors: null + +-- null + | +-- successors: (1, 1, 42) + | +-- no predecessors + sage: Q.remove((1,1)) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 42), (1, 3, 42), (2, 2, 7), (1, 2, 7), (2, 1, 7)) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 42) + +-- (4, 4, 42) + | +-- successors: oo + | +-- predecessors: (1, 3, 42), (2, 2, 7) + +-- (1, 3, 42) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7) + +-- (2, 2, 7) + | +-- successors: (4, 4, 42) + | +-- predecessors: (1, 2, 7), (2, 1, 7) + +-- (1, 2, 7) + | +-- successors: (1, 3, 42), (2, 2, 7) + | +-- predecessors: null + +-- (2, 1, 7) + | +-- successors: (2, 2, 7) + | +-- predecessors: null + +-- null + | +-- successors: (1, 2, 7), (2, 1, 7) + | +-- no predecessors + + :: + + sage: P = MP() + sage: P.remove(None) + Traceback (most recent call last): + ... + ValueError: None is not an allowed key. + """ + if key is None: + raise ValueError('None is not an allowed key.') + + try: + shell = self._shells_[key] + except KeyError: + if not raise_key_error: + return + raise KeyError('Key %s is not contained in this poset.' % (key,)) + + for reverse in (False, True): + for p in shell.predecessors(reverse): + S = p.successors(reverse) + S.remove(shell) + D = set(s for s in p.iter_depth_first(reverse) + if s in shell.successors(reverse)) + S.update(shell.successors(reverse)) + S.difference_update(D) + del self._shells_[key] + + + def discard(self, key, raise_key_error=False): + r""" + Remove the given object from the poset. + + INPUT: + + - ``key`` -- the key of an object. + + - ``raise_key_error`` -- (default: ``False``) switch raising + ``KeyError`` on and off. + + OUTPUT: + + Nothing. + + If the element is not a member and ``raise_key_error`` is set + (not default), raise a ``KeyError``. + + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: P.discard(T((1, 2))) + sage: P.remove(T((1, 2))) + Traceback (most recent call last): + ... + KeyError: 'Key (1, 2) is not contained in this poset.' + sage: P.discard(T((1, 2))) + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`remove`, + :meth:`pop`. + """ + return self.remove(key, raise_key_error) + + + def pop(self, **kwargs): + r""" + Remove and return an arbitrary poset element. + + INPUT: + + - ``kwargs`` -- arguments are passed to :meth:`shells_topological`. + + OUTPUT: + + An object. + + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` cannot be popped. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP() + sage: P.add(3) + sage: P + poset(3) + sage: P.pop() + 3 + sage: P + poset() + sage: P.pop() + Traceback (most recent call last): + ... + KeyError: 'pop from an empty poset' + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`remove`. + """ + kwargs['include_special'] = False + + try: + shell = next(self.shells_topological(**kwargs)) + except StopIteration: + raise KeyError('pop from an empty poset') + self.remove(shell.key) + return shell.element + + + def union(self, *other): + r""" + Return the union of the given posets as a new poset + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + Due to keys and a ``merge`` function (see :class:`MutablePoset`) + this operation might not be commutative. + + .. TODO:: + + Use the already existing information in the other poset to speed + up this function. (At the moment each element of the other poset + is inserted one by one and without using this information.) + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.union(Q) + poset(3, 4, 7, 8, 42) + + .. SEEALSO:: + + :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.union(P, Q, Q, P) + poset(3, 4, 7, 8, 42) + """ + new = self.copy() + new.update(*other) + return new + + + def union_update(self, *other): + r""" + Update this poset with the union of itself and another poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.union_update(B)`` and ``B.union_update(A)`` might + result in different posets. + + .. TODO:: + + Use the already existing information in the other poset to speed + up this function. (At the moment each element of the other poset + is inserted one by one and without using this information.) + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.union_update(Q) + sage: P + poset(3, 4, 7, 8, 42) + + .. SEEALSO:: + + :meth:`union`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: Q.update(P) + sage: Q + poset(3, 4, 7, 8, 42) + """ + for o in other: + try: + it = o.elements() + except AttributeError: + it = iter(o) + for element in it: + self.add(element) + + + update = union_update # as in a Python set + r""" + Alias of :meth:`union_update`. + """ + + + def difference(self, *other): + r""" + Return a new poset where all elements of this poset, which are + contained in one of the other given posets, are removed. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.difference(Q) + poset(3, 7) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.difference(Q, Q) + poset(3, 7) + sage: P.difference(P) + poset() + sage: P.difference(Q, P) + poset() + """ + new = self.copy() + new.difference_update(*other) + return new + + + def difference_update(self, *other): + r""" + Remove all elements of another poset from this poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.difference_update(Q) + sage: P + poset(3, 7) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + for o in other: + try: + it = o.keys() + except AttributeError: + it = iter(o) + for key in it: + self.discard(key) + + + def intersection(self, *other): + r""" + Return the intersection of the given posets as a new poset + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.intersection(Q) + poset(42) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + + TESTS:: + + sage: P.intersection(P, Q, Q, P) + poset(42) + """ + new = self.copy() + new.intersection_update(*other) + return new + + + def intersection_update(self, *other): + r""" + Update this poset with the intersection of itself and another poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.intersection_update(B)`` and ``B.intersection_update(A)`` might + result in different posets. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.intersection_update(Q) + sage: P + poset(42) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + keys = tuple(self.keys()) + for key in keys: + if any(key not in o for o in other): + self.discard(key) + + + def symmetric_difference(self, other): + r""" + Return the symmetric difference of two posets as a new poset. + + INPUT: + + - ``other`` -- a poset. + + OUTPUT: + + A poset. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.symmetric_difference(Q) + poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + new = self.copy() + new.symmetric_difference_update(other) + return new + + + def symmetric_difference_update(self, other): + r""" + Update this poset with the symmetric difference of itself and + another poset. + + INPUT: + + - ``other`` -- a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.symmetric_difference_update(B)`` and + ``B.symmetric_difference_update(A)`` might + result in different posets. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.symmetric_difference_update(Q) + sage: P + poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + """ + T = other.difference(self) + self.difference_update(other) + self.union_update(T) + + + def is_disjoint(self, other): + r""" + Return whether another poset is disjoint to this poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_disjoint(Q) + False + sage: P.is_disjoint(Q.difference(P)) + True + + .. SEEALSO:: + + :meth:`is_subset`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + return all(key not in other for key in self.keys()) + + + isdisjoint = is_disjoint # as in a Python set + r""" + Alias of :meth:`is_disjoint`. + """ + + + def is_subset(self, other): + r""" + Return whether another poset contains this poset, i.e., whether this poset + is a subset of the other poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_subset(Q) + False + sage: Q.is_subset(P) + False + sage: P.is_subset(P) + True + sage: P.is_subset(P.union(Q)) + True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + return all(key in other for key in self.keys()) + + + issubset = is_subset # as in a Python set + r""" + Alias of :meth:`is_subset`. + """ + + + def is_superset(self, other): + r""" + Return whether this poset contains another poset, i.e., whether this poset + is a superset of the other poset. + + INPUT: + + - ``other`` -- a poset or an iterable. In the latter case the + iterated objects are seen as elements of a poset. + + OUTPUT: + + Nothing. + + .. NOTE:: + + If this poset uses a ``key``-function, then all + comparisons are performed on the keys of the elements (and + not on the elements themselves). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: P = MP([3, 42, 7]); P + poset(3, 7, 42) + sage: Q = MP([4, 8, 42]); Q + poset(4, 8, 42) + sage: P.is_superset(Q) + False + sage: Q.is_superset(P) + False + sage: P.is_superset(P) + True + sage: P.union(Q).is_superset(P) + True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. + """ + try: + it = other.keys() + except AttributeError: + it = iter(other) + return all(key in self for key in it) + + + issuperset = is_superset # as in a Python set + r""" + Alias of :meth:`is_superset`. + """ + + + def merge(self, key=None, reverse=False): + r""" + Merge the given element with its successors/predecessors. + + INPUT: + + - ``key`` -- the key specifying an element or ``None`` + (default), in which case this method is called on each + element in this poset. + + - ``reverse`` -- (default: ``False``) specifies which + direction to go first: + ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + When ``key=None``, then this also + specifies which elements are merged first. + + OUTPUT: + + Nothing. + + This method tests all (not necessarily direct) successors and + predecessors of the given element whether they can be merged with + the element itself. This is done by the ``can_merge``-function + of :class:`MutablePoset`. If this merge is possible, then it + is performed by calling :class:`MutablePoset`'s + ``merge``-function and the corresponding successor/predecessor + is removed from the poset. + + .. NOTE:: + + ``can_merge`` is applied in the sense of the condition of + depth first iteration, i.e., once ``can_merge`` fails, + the successors/predecessors are no longer tested. + + .. NOTE:: + + The motivation for such a merge behavior comes from + asymptotic expansions: `O(n^3)` merges with, for + example, `3n^2` or `O(n)` to `O(n^3)` (as `n` tends to + `\infty`; see :wikipedia:`Big_O_notation`). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: key = lambda t: T(t[0:2]) + sage: def add(left, right): + ....: return (left[0], left[1], + ....: ''.join(sorted(left[2] + right[2]))) + sage: def can_add(left, right): + ....: return key(left) >= key(right) + sage: P = MP([(1, 1, 'a'), (1, 3, 'b'), (2, 1, 'c'), + ....: (4, 4, 'd'), (1, 2, 'e'), (2, 2, 'f')], + ....: key=key, merge=add, can_merge=can_add) + sage: Q = copy(P) + sage: Q.merge(T((1, 3))) + sage: print Q.repr_full(reverse=True) + poset((4, 4, 'd'), (1, 3, 'abe'), (2, 2, 'f'), (2, 1, 'c')) + +-- oo + | +-- no successors + | +-- predecessors: (4, 4, 'd') + +-- (4, 4, 'd') + | +-- successors: oo + | +-- predecessors: (1, 3, 'abe'), (2, 2, 'f') + +-- (1, 3, 'abe') + | +-- successors: (4, 4, 'd') + | +-- predecessors: null + +-- (2, 2, 'f') + | +-- successors: (4, 4, 'd') + | +-- predecessors: (2, 1, 'c') + +-- (2, 1, 'c') + | +-- successors: (2, 2, 'f') + | +-- predecessors: null + +-- null + | +-- successors: (1, 3, 'abe'), (2, 1, 'c') + | +-- no predecessors + sage: for k in P.keys(): + ....: Q = copy(P) + ....: Q.merge(k) + ....: print 'merging %s: %s' % (k, Q) + merging (1, 2): poset((1, 2, 'ae'), (1, 3, 'b'), + (2, 1, 'c'), (2, 2, 'f'), (4, 4, 'd')) + merging (1, 3): poset((1, 3, 'abe'), (2, 1, 'c'), + (2, 2, 'f'), (4, 4, 'd')) + merging (4, 4): poset((4, 4, 'abcdef')) + merging (2, 1): poset((1, 2, 'e'), (1, 3, 'b'), + (2, 1, 'ac'), (2, 2, 'f'), (4, 4, 'd')) + merging (2, 2): poset((1, 3, 'b'), (2, 2, 'acef'), (4, 4, 'd')) + merging (1, 1): poset((1, 1, 'a'), (1, 2, 'e'), (1, 3, 'b'), + (2, 1, 'c'), (2, 2, 'f'), (4, 4, 'd')) + sage: Q = copy(P) + sage: Q.merge(); Q + poset((4, 4, 'abcdef')) + + .. SEEALSO:: + + :meth:`MutablePosetShell.merge` + + TESTS:: + + sage: copy(P).merge(reverse=False) == copy(P).merge(reverse=True) + True + + :: + + sage: P = MP(srange(4), + ....: merge=lambda l, r: l, can_merge=lambda l, r: l >= r); P + poset(0, 1, 2, 3) + sage: Q = P.copy() + sage: Q.merge(reverse=True); Q + poset(3) + sage: R = P.mapped(lambda x: x+1) + sage: R.merge(reverse=True); R + poset(4) + + :: + + sage: P = MP(srange(4), + ....: merge=lambda l, r: r, can_merge=lambda l, r: l < r) + sage: P.merge() + Traceback (most recent call last): + ... + RuntimeError: Stopping merge before started; + the can_merge-function is not reflexive. + """ + if key is None: + for shell in tuple(self.shells_topological(reverse=reverse)): + if shell.key in self._shells_: + self.merge(key=shell.key) + return + + shell = self.shell(key) + def can_merge(other): + return self._can_merge_(shell.element, other.element) + for rev in (reverse, not reverse): + to_merge = shell.iter_depth_first( + reverse=rev, condition=can_merge) + try: + next(to_merge) + except StopIteration: + raise RuntimeError('Stopping merge before started; the ' + 'can_merge-function is not reflexive.') + for m in tuple(to_merge): + if m.is_special(): + continue + shell.merge(m.element, check=False, delete=True) + + + def maximal_elements(self): + r""" + Return an iterator over the maximal elements of this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((1, 2)), T((2, 2))]) + sage: list(P.maximal_elements()) + [(1, 3), (2, 2)] + + .. SEEALSO:: + + :meth:`minimal_elements` + """ + return iter(shell.element + for shell in self.oo.predecessors() + if not shell.is_special()) + + + def minimal_elements(self): + r""" + Return an iterator over the minimal elements of this poset. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: list(P.minimal_elements()) + [(1, 2), (2, 1)] + + .. SEEALSO:: + + :meth:`maximal_elements` + """ + return iter(shell.element + for shell in self.null.successors() + if not shell.is_special()) + + + def map(self, function, topological=False, reverse=False): + r""" + Apply the given ``function`` to each element of this poset. + + INPUT: + + - ``function`` -- a function mapping an existing element to + a new element. + + - ``topological`` -- (default: ``False``) if set, then the + mapping is done in topological order, otherwise unordered. + + - ``reverse`` -- is passed on to topological ordering. + + OUTPUT: + + Nothing. + + .. NOTE:: + + Since this method works inplace, it is not allowed that + ``function`` alters the key of an element. + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))], + ....: key=lambda e: e[:2]) + sage: P.map(lambda e: e + (sum(e),)) + sage: P + poset((1, 2, 3), (1, 3, 4), (2, 1, 3), (2, 2, 4), (4, 4, 8)) + + .. SEEALSO:: + + :meth:`copy`, + :meth:`mapped`. + """ + shells = self.shells_topological(reverse=reverse) \ + if topological else self.shells() + for shell in shells: + shell._element_ = function(shell._element_) + + + def mapped(self, function): + r""" + Return a poset where on each element the given ``function`` + was applied. + + INPUT: + + - ``function`` -- a function mapping an existing element to + a new element. + + - ``topological`` -- (default: ``False``) if set, then the + mapping is done in topological order, otherwise unordered. + + - ``reverse`` -- is passed on to topological ordering. + + OUTPUT: + + A :class:`MutablePoset`. + + .. NOTE:: + + ``function`` is not allowed to change the order of the keys, + but changing the keys themselves is allowed (in contrast + to :meth:`map`). + + EXAMPLES:: + + sage: from sage.data_structures.mutable_poset import MutablePoset as MP + sage: class T(tuple): + ....: def __le__(left, right): + ....: return all(l <= r for l, r in zip(left, right)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: P.mapped(lambda e: str(e)) + poset('(1, 2)', '(1, 3)', '(2, 1)', '(2, 2)', '(4, 4)') + + .. SEEALSO:: + + :meth:`copy`, + :meth:`map`. + """ + return self.copy(mapping=function) + + +# ***************************************************************************** diff --git a/src/sage/databases/all.py b/src/sage/databases/all.py index 1ef118cb20d..56fbaaf0f5e 100644 --- a/src/sage/databases/all.py +++ b/src/sage/databases/all.py @@ -5,6 +5,8 @@ * CremonaDatabase() - Cremona's tables of elliptic curves and related data. + * findstat -- The FindStat database (http://www.findstat.org/). + * JonesDatabase() -- returns the John Jones table of number fields with bounded ramification and degree <= 6. @@ -78,3 +80,6 @@ from symbolic_data import SymbolicData from cunningham_tables import cunningham_prime_factors + +lazy_import('sage.databases.findstat', 'findstat') + diff --git a/src/sage/databases/cremona.py b/src/sage/databases/cremona.py index 6edb0e8bbcc..96194d26ec5 100644 --- a/src/sage/databases/cremona.py +++ b/src/sage/databases/cremona.py @@ -12,9 +12,9 @@ The large database includes all curves in John Cremona's tables. It also includes data related to the BSD conjecture and modular degrees for all of these curves, and generators for the Mordell-Weil -groups. To install it type the following in Sage:: +groups. To install it, run the following in the shell:: - !sage -i database_cremona_ellcurve + sage -i database_cremona_ellcurve This causes the latest version of the database to be downloaded from the internet. diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py new file mode 100644 index 00000000000..61dfb90dab1 --- /dev/null +++ b/src/sage/databases/findstat.py @@ -0,0 +1,2485 @@ +r""" +FindStat - the Combinatorial Statistic Finder. + +The FindStat database can be found at http://www.findstat.org . + +Fix the following three notions: + +- A *combinatorial collection* is a set `S` with interesting combinatorial properties, +- a *combinatorial map* is a combinatorially interesting map `f: S \to S'` between combinatorial collections, and +- a *combinatorial statistic* is a combinatorially interesting map `s: S \to \ZZ`. + +You can use the sage interface to FindStat to: + +- identify a combinatorial statistic from the values on a few small objects, +- obtain more terms, formulae, references, etc. for a given statistic, +- edit statistics and submit new statistics. + +To access the database, use :class:`findstat`:: + + sage: findstat + The Combinatorial Statistic Finder (http://www.findstat.org/) + +AUTHORS: + +- Martin Rubey (2015): initial version. + +A guided tour +------------- + +Retrieving information +^^^^^^^^^^^^^^^^^^^^^^ + +The most straightforward application of the FindStat interface is to +gather information about a combinatorial statistic. To do this, we +supply :class:`findstat` with a list of `(object, value)` +pairs. For example:: + + sage: PM8 = PerfectMatchings(8) + sage: r = findstat([(m, m.number_of_nestings()) for m in PM8]); r # optional -- internet,random + 0: (St000041: The number of nestings of a perfect matching. , [], 105) + ... + +The result of this query is a list (presented as a +:class:`sage.databases.oeis.FancyTuple`) of triples. The first +element of each triple is a :class:`FindStatStatistic` `s: S \to +\ZZ`, the second element a list of :class:`FindStatMap`'s `f_i: S_i +\to S_{i+1}`, and the third element is an integer:: + + sage: (s, list_f, quality) = r[0] # optional -- internet + +The precise meaning of the result is as follows: + + The composition `f_n \circ ... \circ f_2 \circ f_1` applied to + the objects sent to FindStat agrees with `quality` many `(object, + value)` pairs of `s` in the database. Moreover, there are no + other `(object, value)` pairs of `s` stored in the database, + i.e., there is no disagreement of values. + +Put differently, if `quality` is not too small it is likely that the +statistic sent to FindStat equals `s \circ f_n \circ ... \circ f_2 \circ f_1`. + +In the case at hand, the list of maps is empty and the integer +`quality` equals the number of `(object, value)` pairs passed to +FindStat. This means, that the set of `(object, value)` pairs of the +statistic `s` as stored in the FindStat database is a superset of the +data sent. We can now retrieve the description from the database:: + + sage: print s.description() # optional -- internet,random + The number of nestings of a perfect matching. + + + This is the number of pairs of edges $((a,b), (c,d))$ such that $a\le c\le d\le b$. i.e., the edge $(c,d)$ is nested inside $(a,b)$. + +and check the references:: + + sage: s.references() # optional -- internet,random + 0: [1] [[MathSciNet:1288802]] + 1: [2] [[MathSciNet:1418763]] + +If you prefer, you can look at this information also in your browser:: + + sage: findstat(41).browse() # optional -- webbrowser + +Another interesting possibility is to look for equidistributed +statistics. Instead of submitting a list of pairs, we pass a pair of +lists:: + + sage: r = findstat((PM8, [m.number_of_nestings() for m in PM8])); r # optional -- internet,random + 0: (St000041: The number of nestings of a perfect matching. , [], 105) + 1: (St000042: The number of crossings of a perfect matching. , [], 105) + ... + +This results tells us that the database contains another entriy that is +equidistributed with the number of nestings on perfect matchings of +length `8`, namely the number of crossings. + +Let us now look at a slightly more complicated example, where the +submitted statistic is the composition of a sequence of combinatorial +maps and a statistic known to FindStat. We use the occasion to +advertise yet another way to pass values to FindStat:: + + sage: r = findstat(Permutations(4), lambda pi: pi.saliances()[0]); r # optional -- internet,random + 0: (St000051: The size of the left subtree. , [Mp00069: complement, Mp00061: to increasing tree], 24) + ... + sage: (s, list_f, quality) = r[0] # optional -- internet + +To obtain the value of the statistic sent to FindStat on a given +object, apply the maps in the list in the given order to this object, +and evaluate the statistic on the result. For example, let us check +that the result given by FindStat agrees with our statistic on the +following permutation:: + + sage: pi = Permutation([3,1,4,5,2]); pi.saliances()[0] + 3 + +We first have to find out, what the maps and the statistic actually do:: + + sage: print s.description() # optional -- internet,random + The size of the left subtree. + + sage: print s.code() # optional -- internet,random + def statistic(T): + return T[0].node_number() + + sage: print list_f[0].code() + "\r\n" + list_f[1].code() # optional -- internet,random + def complement(elt): + n = len(elt) + return elt.__class__(elt.parent(), map(lambda x: n - x + 1, elt) ) + + def increasing_tree_shape(elt, compare=min): + return elt.increasing_tree(compare).shape() + +So, the following should coincide with what we sent FindStat:: + + sage: pi.complement().increasing_tree_shape()[0].node_number() + 3 + +Editing and submitting statistics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Of course, often a statistic will not be in the database:: + + sage: findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet + a new statistic on Cc0005: Dyck paths + +In this case, and if the statistic might be "interesting", please +consider submitting it to the database using +:meth:`FindStatStatistic.submit`. + +Also, you may notice omissions, typos or even mistakes in the +description, the code and the references. In this case, simply +replace the value by using :meth:`FindStatStatistic.set_description`, +:meth:`FindStatStatistic.set_code` or +:meth:`FindStatStatistic.set_references`, and then +:meth:`FindStatStatistic.submit` your changes for review by the +FindStat team. + +Classes and methods +------------------- +""" +#***************************************************************************** +# Copyright (C) 2015 Martin Rubey , +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.structure.element import Element +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method + +from sage.categories.sets_cat import Sets +from sage.structure.sage_object import SageObject + +from sage.misc.misc import verbose +from sage.rings.integer import Integer +from sage.databases.oeis import FancyTuple + +from string import join +from ast import literal_eval +from collections import OrderedDict +from urllib import urlencode +from urllib2 import Request, urlopen, HTTPError +import re +import webbrowser +import tempfile +import time +import inspect +import json +import cgi + + +# Combinatoral collections +from sage.combinat.alternating_sign_matrix import AlternatingSignMatrix, AlternatingSignMatrices +from sage.combinat.binary_tree import BinaryTree, BinaryTrees +from sage.combinat.core import Core, Cores +from sage.combinat.dyck_word import DyckWord, DyckWords +from sage.combinat.root_system.cartan_type import CartanType_abstract, CartanType +from sage.combinat.gelfand_tsetlin_patterns import GelfandTsetlinPattern, GelfandTsetlinPatterns +from sage.graphs.graph import Graph +from sage.combinat.composition import Composition, Compositions +from sage.combinat.partition import Partition, Partitions +from sage.combinat.ordered_tree import OrderedTree, OrderedTrees +from sage.combinat.parking_functions import ParkingFunction, ParkingFunction_class, ParkingFunctions +from sage.combinat.perfect_matching import PerfectMatching, PerfectMatchings +from sage.combinat.permutation import Permutation, Permutations +from sage.combinat.posets.posets import Poset, FinitePoset +from sage.combinat.posets.poset_examples import posets +from sage.combinat.tableau import SemistandardTableau, SemistandardTableaux, StandardTableau, StandardTableaux +from sage.combinat.set_partition import SetPartition, SetPartitions +from sage.graphs.graph_generators import graphs + +###################################################################### +# the FindStat URLs +FINDSTAT_URL = 'http://www.findstat.org/' +FINDSTAT_URL_RESULT = FINDSTAT_URL + "StatisticFinder/Result/" +FINDSTAT_URL_LOGIN = FINDSTAT_URL + "StatisticFinder?action=login" +FINDSTAT_URL_NEW = FINDSTAT_URL + 'StatisticsDatabase/NewStatistic/' +FINDSTAT_URL_EDIT = FINDSTAT_URL + 'StatisticsDatabase/EditStatistic/' +FINDSTAT_URL_BROWSE = FINDSTAT_URL + 'StatisticsDatabase/' + +FINDSTAT_URL_DOWNLOADS = 'http://downloads.findstat.org/' +FINDSTAT_URL_DOWNLOADS_STATISTICS = FINDSTAT_URL_DOWNLOADS + "statistics/%s.json" +FINDSTAT_URL_DOWNLOADS_COLLECTIONS = FINDSTAT_URL_DOWNLOADS + "collections.json" +FINDSTAT_URL_DOWNLOADS_MAPS = FINDSTAT_URL_DOWNLOADS + "maps.json" + +###################################################################### +# the number of values FindStat allows to search for at most +FINDSTAT_MAX_VALUES = 200 +# the number of maps that FindStat should compose at most to find a match +FINDSTAT_MAX_DEPTH = 5 +# the number of values FindStat allows to submit at most +FINDSTAT_MAX_SUBMISSION_VALUES = 1200 + +# the fields of the FindStat database we expect +FINDSTAT_STATISTIC_IDENTIFIER = 'StatisticIdentifier' +FINDSTAT_STATISTIC_COLLECTION = 'StatisticCollection' +FINDSTAT_STATISTIC_DATA = 'StatisticData' +FINDSTAT_STATISTIC_DESCRIPTION = 'StatisticDescription' +FINDSTAT_STATISTIC_REFERENCES = 'StatisticReferences' +FINDSTAT_STATISTIC_CODE = 'StatisticCode' +FINDSTAT_STATISTIC_ORIGINAL_AUTHOR = 'StatisticOriginalAuthor' # unused, designates a dictionary with Name, Email, Time +FINDSTAT_STATISTIC_UPDATE_AUTHOR = 'StatisticUpdateAuthor' # unused, designates a dictionary with Name, Email, Time + +FINDSTAT_POST_AUTHOR = 'StatisticAuthor' # designates the name of the author +FINDSTAT_POST_EMAIL = 'StatisticEmail' +FINDSTAT_POST_SAGE_CELL = 'SageCellField' # currently only used as post key +FINDSTAT_POST_EDIT = 'EDIT' # only used as post key + +FINDSTAT_COLLECTION_IDENTIFIER = 'CollectionIdentifier' +FINDSTAT_COLLECTION_NAME = 'CollectionName' +FINDSTAT_COLLECTION_NAME_PLURAL = 'CollectionNamePlural' +FINDSTAT_COLLECTION_NAME_WIKI = 'CollectionNameWiki' +FINDSTAT_COLLECTION_PARENT_LEVELS_PRECOMPUTED = 'CollectionLevelsPrecomputed' + +FINDSTAT_MAP_IDENTIFIER = 'MapIdentifier' # should be identical to FINDSTAT_MAP_IDENTIFIER +FINDSTAT_MAP_NAME = 'MapName' +FINDSTAT_MAP_DESCRIPTION = 'MapDescription' +FINDSTAT_MAP_DOMAIN = 'MapDomain' +FINDSTAT_MAP_CODOMAIN = 'MapCodomain' +FINDSTAT_MAP_CODE = 'MapCode' +FINDSTAT_MAP_CODE_NAME = 'MapSageName' + +FINDSTAT_QUERY_MATCHES = 'QueryMatches' +FINDSTAT_QUERY_MATCHING_DATA = 'QueryMatchingData' +FINDSTAT_QUERY_MAPS = 'QueryMaps' + +# the entries of this list are required as post arguments for submitting or editing a statistic +FINDSTAT_EDIT_FIELDS = set([FINDSTAT_STATISTIC_IDENTIFIER, + FINDSTAT_STATISTIC_COLLECTION, + FINDSTAT_STATISTIC_DATA, + FINDSTAT_STATISTIC_DESCRIPTION, + FINDSTAT_STATISTIC_REFERENCES, + FINDSTAT_STATISTIC_CODE, + FINDSTAT_POST_AUTHOR, + FINDSTAT_POST_EMAIL, + FINDSTAT_POST_SAGE_CELL, + FINDSTAT_POST_EDIT]) + +# separates name from description +FINDSTAT_SEPARATOR_NAME = "\r\n" +# separates references +FINDSTAT_SEPARATOR_REFERENCES = "\r\n" + +###################################################################### + +# the format string for using POST +# WARNING: we use cgi.escape to avoid injection problems, thus we expect double quotes as field delimiters. +FINDSTAT_POST_HEADER = """ + + + + +""" +FINDSTAT_NEWSTATISTIC_FORM_HEADER = '
' +FINDSTAT_NEWSTATISTIC_FORM_FORMAT = '' +FINDSTAT_NEWSTATISTIC_FORM_FOOTER = '
' + +###################################################################### +class FindStat(SageObject): + r""" + The Combinatorial Statistic Finder. + + :class:`FindStat` is a class representing results of queries to + the FindStat database. This class is also the entry point to + edit statistics and new submissions. Use the shorthand + :class:`findstat` to call it. + + INPUT: + + One of the following: + + - an integer or a string representing a valid FindStat identifier + (e.g. 45 or 'St000045'). The keyword argument ``statistic`` + should be ``None`` (the default), the keyword arguments + ``depth`` and ``max_values`` are ignored. + + - a list of pairs of the form (object, value), or a dictionary + from sage objects to integer values. The keyword argument + ``statistic`` should be ``None`` (the default), the keyword + arguments ``depth`` and ``max_values`` are passed to the + finder. + + - a list of pairs of the form (list of objects, list of values), + or a single pair of the form (list of objects, list of values). + In each pair there should be as many objects as values. The + keyword argument ``statistic`` should be ``None`` (the + default), the keyword arguments ``depth`` and ``max_values`` + are passed to the finder. + + - a collection and a callable. The callable is used to generate + ``max_values`` (object, value) pairs. The number of terms + generated may also be controlled by passing an iterable + collection, such as Permutations(3). The keyword arguments + ``depth`` and ``max_values`` are passed to the finder. + + OUTPUT: + + An instance of a :class:`FindStatStatistic`, represented by + + - the FindStat identifier together with its name, or + + - a list of triples, each consisting of + + - the statistic + + - a list of strings naming certain maps + + - a number which says how many of the values submitted agree + with the values in the database, when applying the maps in + the given order to the object and then computing the + statistic on the result. + + EXAMPLES: + + A particular statistic can be retrieved by its St-identifier or + number:: + + sage: findstat('St000041') # optional -- internet,random + St000041: The number of nestings of a perfect matching. + + sage: findstat(51) # optional -- internet,random + St000051: The size of the left subtree. + + The database can be searched by providing a list of pairs:: + + sage: q = findstat([(pi, pi.length()) for pi in Permutations(4)]); q # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 24) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 24) + ... + + or a dictionary:: + + sage: p = findstat({pi: pi.length() for pi in Permutations(4)}); p # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 24) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 24) + ... + + Note however, that the results of these two queries are not + necessarily the same, because we compare queries by the data + sent, and the ordering of the data might be different:: + + sage: p == q # optional -- internet + False + + Another possibility is to send a collection and a function. In + this case, the function is applied to the first few objects of + the collection:: + + sage: findstat("Permutations", lambda pi: pi.length()) # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 200) + ... + + To search for a distribution, send a list of lists, or a single pair:: + + sage: S4 = Permutations(4); findstat((S4, [pi.length() for pi in S4])) # optional -- internet,random + 0: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [], 24) + 1: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 24) + ... + + Note that there is a limit, ``FINDSTAT_MAX_DEPTH``, on the number + of elements that may be submitted to FindStat, which is currently + 200. Therefore, the interface tries to truncate queries + appropriately, but this may be impossible, especially with + distribution searches:: + + sage: S6 = Permutations(6); S6.cardinality() # optional -- internet + 720 + sage: findstat((S6, [1 for a in S6])) # optional -- internet + Traceback (most recent call last): + ... + ValueError: after discarding elements not in the range, and keeping less than 200 values, nothing remained to send to FindStat. + + """ + def __init__(self): + r""" + Initialize the database. + + In particular, we set up a cache from integers to + :class:`FindStatStatistic` instances that avoids retrieving + the same statistic over and over again. + + TESTS:: + + sage: findstat + The Combinatorial Statistic Finder (http://www.findstat.org/) + """ + self._statistic_cache = dict() + + # user credentials if provided + self._user_name = "" + self._user_email = "" + + def __call__(self, query, function=None, depth=2, max_values=FINDSTAT_MAX_VALUES): + r""" + Return an instance of a :class:`FindStatStatistic`. + + This should be the only way to access + :class:`FindStatStatistic`. We do the preprocessing of the + data here, and call the appropriate method of + :class:`FindStatStatistic` to launch the query. + + TESTS:: + + sage: findstat("Permutations", lambda x: 1, depth=100) + Traceback (most recent call last): + ... + ValueError: The depth must be a non-negative integer less than or equal to 5. + + sage: findstat("Permutations", 1) + Traceback (most recent call last): + ... + ValueError: The given arguments, Permutations and 1, cannot be used for a FindStat search. + + sage: from sage.databases.findstat import FindStatCollection + sage: findstat(FindStatCollection("Permutations"), lambda pi: pi.length()) # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 200) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 200) + 2: (St000067: The inversion number of the alternating sign matrix., [Mp00063: to alternating sign matrix], 152) + 3: (St000246: The [[/Permutations/Inversions|number of non-inversions]] of a permutation., [Mp00064: reverse], 200) + 4: (St000008: The major index of the composition., [Mp00062: inversion-number to major-index bijection, Mp00071: descent composition], 200) + + """ + try: + depth = int(depth) + assert 0 <= depth <= FINDSTAT_MAX_DEPTH + except: + raise ValueError("The depth must be a non-negative integer less than or equal to %i." %FINDSTAT_MAX_DEPTH) + + if function is None: + if isinstance(query, str): + if re.match('^St[0-9]{6}$', query): + return self._statistic_find_by_id_cached(Integer(query[2:].lstrip("0"))) + else: + raise ValueError("The value %s is not a valid statistic identifier." %query) + + elif isinstance(query, (int, Integer)): + return self._statistic_find_by_id_cached(query) + + elif isinstance(query, dict): + # we expect a dictionary from objects to integers + data = [([key], [value]) for (key, value) in query.iteritems()] + first_terms = [(key, value) for (key, value) in query.iteritems()] + collection = FindStatCollection(data[0][0][0]) + return FindStatStatistic(id=0, data=data, + first_terms=first_terms, + collection=collection, + depth=depth)._find_by_values(max_values=max_values) + + elif isinstance(query, (list, tuple)): + # either a pair (list of objects, list of integers) + # or a list of such or (object, integer) pairs + + # values must always be lists because otherwise we + # get a trailing comma when printing + if (len(query) == 2 and + isinstance(query[1], (list, tuple)) and + len(query[1]) != 0 and + isinstance(query[1][0], (int, Integer))): + # just a pair + data = [(query[0], list(query[1]))] + collection = FindStatCollection(data[0][0][0]) + return FindStatStatistic(id=0, data=data, + collection=collection, + depth=depth)._find_by_values(max_values=max_values) + else: + is_statistic = True + data = [] + for (key, value) in query: + if isinstance(value, (list, tuple)): + data += [(key, list(value))] + is_statistic = False + else: + data += [([key], [value])] + collection = FindStatCollection(data[0][0][0]) + if is_statistic: + return FindStatStatistic(id=0, data=data, + collection=collection, + first_terms=query, + depth=depth)._find_by_values(max_values=max_values) + else: + return FindStatStatistic(id=0, data=data, + collection=collection, + depth=depth)._find_by_values(max_values=max_values) + + else: + raise ValueError("The given query, %s, cannot be used for a FindStat search." %query) + + else: + if callable(function): + if isinstance(query, FindStatCollection): + collection = query + else: + collection = FindStatCollection(query) + first_terms = collection.first_terms(function, max_values=max_values) + data = [([key], [value]) for (key, value) in first_terms] + try: + code = inspect.getsource(function) + except IOError: + _ = verbose("inspect.getsource could not get code from function provided", caller_name='FindStat') + code = "" + return FindStatStatistic(id=0, first_terms=first_terms, + data=data, function=function, code=code, + collection=collection, + depth=depth)._find_by_values(max_values=max_values) + else: + raise ValueError("The given arguments, %s and %s, cannot be used for a FindStat search." %(query, function)) + + def __repr__(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: findstat + The Combinatorial Statistic Finder (http://www.findstat.org/) + """ + return "The Combinatorial Statistic Finder (%s)" % FINDSTAT_URL + + def browse(self): + r""" + Open the FindStat web page in a browser. + + EXAMPLES:: + + sage: findstat.browse() # optional -- webbrowser + """ + webbrowser.open(FINDSTAT_URL) + + def set_user(self, name=None, email=None): + r""" + Set the user for this session. + + INPUT: + + - ``name`` -- the name of the user. + + - ``email`` -- an email address of the user. + + This information is used when submitting a statistic with + :meth:`FindStatStatistic.submit`. + + EXAMPLES:: + + sage: findstat.set_user(name="Anonymous", email="invalid@org") + + .. NOTE:: + + It is usually more convenient to login into the FindStat + web page using the :meth:`login` method. + """ + if not isinstance(name, str): + raise ValueError("The given name is not a string.") + if not isinstance(email, str): + raise ValueError("The given email address is not a string.") + self._user_name = name + self._user_email = email + + def login(self): + r""" + Open the FindStat login page in a browser. + + EXAMPLES:: + + sage: findstat.login() # optional -- webbrowser + """ + webbrowser.open(FINDSTAT_URL_LOGIN) + + ###################################################################### + + def _statistic_find_by_id_cached(self, id): + r""" + INPUT: + + - ``id`` -- an integer designating the FindStat id of a statistic. + + OUTPUT: + + An instance of :class:`FindStatStatistic`. + + .. TODO:: + + this method caches the statistics. It may make sense to + provide a method that clears the cache, or reloads a + single statistic. + + TESTS:: + + sage: findstat(41).set_description("") # optional -- internet, indirect doctest + sage: findstat(41).description() == "" # optional -- internet, indirect doctest + True + """ + if id > 0: + if id not in self._statistic_cache.keys(): + self._statistic_cache[id] = FindStatStatistic(id)._find_by_id() + return self._statistic_cache[id] + else: + raise ValueError("The statistic identifier must be at least 1.") + +###################################################################### + +class FindStatStatistic(SageObject): + r""" + The class of FindStat statistics. + + Do not instantiate this class directly. Instead, use + :class:`findstat`. + """ + def __init__(self, id, first_terms=None, data=None, function=None, code="", collection=None, depth=None): + r""" + Initialize a FindStat query for a statistic from preprocessed + data. + + INPUT: + + - ``id`` -- an integer designating the FindStat id of a + statistic, or 0. + + - ``first_terms`` -- (optional) a list of (object, value) + pairs, see :meth:`first_terms`. + + - ``data`` -- (optional) a list of pairs of the form (list of + objects, list of values), see :meth:`data`. + + - ``function`` -- (optional) a function taking a sage object + as input and returning the value of the statistic on this + object, see :meth:`function`. + + - ``code`` -- (optional) a string containing code (possibly + pseudocode or code for a different computer algebra + system), see :meth:`code`. + + - ``collection`` -- (optional) an instance of + :class:`FindStatCollection`, see :meth:`collection`. + + - ``depth`` -- (optional) an integer between 0 and + FINDSTAT_MAX_DEPTH, which determines how many maps FindStat + should compose at most to find a match. + + This method by itself does not launch the query, because + there are two rather different queries possible, see + :meth:`_find_by_id` and :meth:`_find_by_values`. + + TESTS:: + + sage: from sage.databases.findstat import FindStatStatistic + sage: FindStatStatistic(1)._find_by_id() # optional -- internet + St000001: The number of ways to write a permutation as a minimal length product of simple transpositions. + """ + self._depth = depth + self._query = None + self._modified = False + + self._id = id + self._result = None + + self._first_terms = first_terms + self._data = data + self._function = function + self._code = code + self._collection = collection + + self._description = "" + self._references = "" + + def __repr__(self): + r""" + Return the representation of the FindStat query. + + OUTPUT: + + A string. If the query was by identifier, the identifier and + the name of the statistic. If the statistic was modified + (see :meth:`modified`) this is also indicated. + + If the query was by values and at least one match was found, + the list of matches. + + Otherwise, if no match was found, a message saying this. + + EXAMPLES:: + + sage: findstat(1) # optional -- internet + St000001: ... + + sage: findstat([(pi, randint(1,100)) for pi in Permutations(3)]) # optional -- internet + a new statistic on Cc0001: Permutations + + sage: findstat([(pi, pi(1)) for pi in Permutations(3)]) # optional -- internet + 0: (St000054: ... + 1: ... + 2: ... + + """ + if self._query == "ID": + if self._modified: + return "%s(modified): %s" % (self.id_str(), self.name()) + else: + return "%s: %s" % (self.id_str(), self.name()) + + elif self._query == "data": + if len(self._result) == 0: + return "a new statistic on " + self._collection.__repr__() + else: + return self._result.__repr__() + + else: + raise ValueError("self._query should be either 'ID' or 'data', but is %s." %self._query) + + def __eq__(self, other): + """Return ``True`` if ``self`` is equal to ``other`` and ``False`` + otherwise. + + INPUT: + + - ``other`` -- a FindStat query, i.e., instance of :class:`FindStatStatistic`. + + OUTPUT: + + A boolean. + + Two queries are considered equal if all of the following + applies: + + - the queries are both of the same type, i.e., both are by + identifier or both are by values, + + - if the queries are by identifier, the identifiers agree and + the statistics are both unmodified, + + - if the queries are by values, the submitted data are the + same and the statistic data are both unmodified. + + .. TODO:: + + this is *very* rudimentary + + EXAMPLES:: + + sage: findstat(1) == findstat(41) # optional -- internet + False + + sage: r1 = findstat(Permutations(3), lambda pi: pi.saliances()[0]) # optional -- internet + sage: r2 = findstat(Permutations(4), lambda pi: pi.saliances()[0]) # optional -- internet + sage: r1 == r2 # optional -- internet + False + """ + if (not isinstance(other, FindStatStatistic)): + return False + if self._query == "ID" and other._query == "ID": + if self._modified or other._modified: + return False + else: + return self._id == other._id + elif self._query == "data" and other._query == "data": + if self._modified or other._modified: + return False + else: + return self._data == other._data + else: + return False + + def __ne__(self, other): + """Determine whether ``other`` is a different query. + + INPUT: + + - ``other`` -- a FindStat query, i.e., instance of + :class:`FindStatStatistic`.. + + OUTPUT: + + A boolean. + + SEEALSO: + + :meth:`__eq__` + + EXAMPLES:: + + sage: r1 = findstat(Permutations(3), lambda pi: pi.saliances()[0]) # optional -- internet + sage: r2 = findstat(Permutations(4), lambda pi: pi.saliances()[0]) # optional -- internet + sage: r1 != r2 # optional -- internet + True + + """ + return not self.__eq__(other) + + + ###################################################################### + # query = "ID" + ###################################################################### + + def _find_by_id(self): + r""" + Retrieve the statistic matching ``self._id``. + + OUTPUT: + + The statistic ``self``. + + Expects that ``_id`` is a valid identifier. Overwrites all + variables associated with the statistic, such as + ``_description``, ``_code``, ``_references``, etc. + + TESTS:: + + sage: findstat(999999) # optional -- internet, indirect doctest + Traceback (most recent call last): + ... + ValueError: St999999 is not a FindStat statistic identifier. + """ + self._query = "ID" + + # get the database entry from FindStat + url = FINDSTAT_URL_DOWNLOADS_STATISTICS %self.id_str() + _ = verbose("Fetching URL %s ..." %url, caller_name='FindStat') + try: + self._raw = json.load(urlopen(url), object_pairs_hook=OrderedDict) + except HTTPError, error: + if error.code == 404: + raise ValueError("%s is not a FindStat statistic identifier." %self.id_str()) + else: + raise + + self._description = self._raw[FINDSTAT_STATISTIC_DESCRIPTION].encode("utf-8") + self._references = self._raw[FINDSTAT_STATISTIC_REFERENCES].encode("utf-8") + self._collection = FindStatCollection(self._raw[FINDSTAT_STATISTIC_COLLECTION]) + self._code = self._raw[FINDSTAT_STATISTIC_CODE] + + from_str = self._collection.from_string() + # we want to keep FindStat's ordering here! + if from_str is None: + self._first_terms = self._raw[FINDSTAT_STATISTIC_DATA] + else: + self._first_terms = [(from_str(obj), Integer(val)) for (obj, val) in self._raw[FINDSTAT_STATISTIC_DATA].iteritems()] + return self + + ###################################################################### + # query = "data" + ###################################################################### + + def _find_by_values(self, max_values=FINDSTAT_MAX_VALUES): + r""" + Retrieve the statistics matching ``self._data``. + + OUTPUT: + + The query ``self``. + + Expects that ``_data`` is a list of pairs of the form (list + of objects, list of values), each containing as many values + as objects, and that ``_collection`` is appropriately set. + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollection + sage: from sage.databases.findstat import FindStatStatistic + sage: query = {dw:dw.area() for dw in DyckWords(4)} + sage: data = [([key], [value]) for (key, value) in query.iteritems()] + sage: first_terms = [(key, value) for (key, value) in query.iteritems()] + sage: collection = FindStatCollection(data[0][0][0]) # optional -- internet + sage: FindStatStatistic(id=0,data=data, first_terms = first_terms, collection = collection, depth=0)._find_by_values() # optional -- internet + 0: (St000012: The area of a Dyck path., [], 14) + + """ + self._query = "data" + + # FindStat allows to search for at most FINDSTAT_MAX_VALUES + # values. For the user's convenience, from data, we take the + # first min(max_values, FINDSTAT_MAX_VALUES) such that all + # elements are in the precomputed range + data = [] + total = min(max_values, FINDSTAT_MAX_VALUES) + for (elements, values) in self._data: + if len(elements) != len(values): + raise ValueError("FindStat expects the same number of objects as values!") + try: + values = [int(v) for v in values] + except ValueError: + raise ValueError("FindStat expects integer values as statistics!") + + if total >= len(elements): + if all(self._collection.in_range(e) for e in elements): + data += [(elements, values)] + total -= len(elements) + + # this might go wrong: + try: + assert data != [] + except: + raise ValueError("after discarding elements not in the range, and keeping less than %s values, nothing remained to send to FindStat." %FINDSTAT_MAX_VALUES) + + url = FINDSTAT_URL_RESULT + self._collection._url_name + "/" + + to_str = self._collection.to_string() + stat = [(map(to_str, keys), str(values)[1:-1]) for (keys, values) in data] + + stat_str = join([join(keys, "\n") + "\n====> " + values for (keys, values) in stat], "\n") + _ = verbose("Sending the following data to FindStat\r\n %s" %stat_str, caller_name='FindStat') + + values = urlencode({"freedata": stat_str, "depth": str(self._depth), "caller": "Sage"}) + _ = verbose("Fetching URL %s with encoded data %s" %(url, values), caller_name='FindStat') + + request = Request(url, data=values) + _ = verbose("Requesting %s" %request, caller_name='FindStat') + + response = urlopen(request) + _ = verbose("Response was %s" %response.info(), caller_name='FindStat') + + try: + result = json.load(response) + self._result = FancyTuple((findstat(match[FINDSTAT_STATISTIC_IDENTIFIER]), + [FindStatMap(mp[FINDSTAT_MAP_IDENTIFIER]) for mp in match[FINDSTAT_QUERY_MAPS]], + len(match[FINDSTAT_QUERY_MATCHING_DATA])) + for match in result[FINDSTAT_QUERY_MATCHES]) + return self + except Exception as e: + raise IOError("FindStat did not answer with a json response: %s" %e) + + def _raise_error_modifying_statistic_with_perfect_match(self): + r""" + Raise an error when there is a result with depth 0. + + TESTS:: + + sage: s = findstat([(pi, pi[0]) for pi in Permutations(3)]) # optional -- internet + sage: s._raise_error_modifying_statistic_with_perfect_match() # optional -- internet + Traceback (most recent call last): + ... + ValueError: Your input data matches St000054. Consider modifying this statistic instead. + + sage: s = findstat(1) # optional -- internet + sage: s._raise_error_modifying_statistic_with_perfect_match() # optional -- internet + + sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet + sage: s._raise_error_modifying_statistic_with_perfect_match() # optional -- internet + """ + if self._query == "data" and len(self._result) > 0 and len(self._result[0][1]) == 0: + raise ValueError("Your input data matches %s. Consider modifying this statistic instead." %self._result[0][0].id_str()) + + ###################################################################### + + def __getitem__(self, key): + r""" + Return the match corresponding to ``key``. + + INPUT: + + - ``key`` -- an integer. + + OUTPUT: + + The match corresponding to ``key``. Raise an error if the + query was by identifier. + + EXAMPLES:: + + sage: q = findstat([(pi, pi.length()) for pi in Permutations(4)]); q # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 24) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 24) + ... + + sage: q[1] # optional -- internet,random + (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 24) + + """ + if self._query == "ID": + raise TypeError("Use 'first_terms' to access the values of the statistic.") + + elif self._query == "data": + return self._result[key] + + else: + raise ValueError("self._query should be either 'ID' or 'data', but is %s." %self._query) + + def id(self): + r""" + Return the FindStat identifier of the statistic. + + OUTPUT: + + The FindStat identifier of the statistic (or 0), as an integer. + + EXAMPLES:: + + sage: findstat(1).id() # optional -- internet + 1 + """ + return self._id + + def id_str(self): + r""" + Return the FindStat identifier of the statistic. + + OUTPUT: + + The FindStat identifier of the statistic (or 'St000000'), as a string. + + EXAMPLES:: + + sage: findstat(1).id_str() # optional -- internet + 'St000001' + """ + id = str(self._id) + return 'St000000'[:-len(id)] + id + + def data(self): + r""" + Return the data used for querying the FindStat database. + + OUTPUT: + + The data provided by the user to query the FindStat database. + When the database was searched using an identifier, ``data`` + is ``None``. + + EXAMPLES:: + + sage: S4 = Permutations(4); findstat((S4, [pi.length() for pi in S4])).data() # optional -- internet + [(Standard permutations of 4, + [0, 1, 1, 2, 2, 3, 1, 2, 2, 3, 3, 4, 2, 3, 3, 4, 4, 5, 3, 4, 4, 5, 5, 6])] + """ + return self._data + + def modified(self): + r""" + Return whether the statistic was modified. + + OUTPUT: + + True, if the statistic was modified using + :meth:`set_description`, :meth:`set_code`, + :meth:`set_references`, etc. False otherwise. + + EXAMPLES:: + + sage: findstat(41).set_description("") # optional -- internet + sage: findstat(41).modified() # optional -- internet + True + + """ + return self._modified + + def collection(self): + r""" + Return the FindStat collection of the statistic. + + OUTPUT: + + The FindStat collection of the statistic as an instance of + :class:`FindStatCollection`. + + EXAMPLES:: + + sage: findstat(1).collection() # optional -- internet + Cc0001: Permutations + """ + return self._collection + + def function(self): + r""" + Return the function used to compute the values of the statistic. + + OUTPUT: + + The function used to compute the values of the statistic, or + ``None``. + + EXAMPLES:: + + sage: findstat("Permutations", lambda pi: pi.length()).function() # optional -- internet + ... + at ...> + """ + return self._function + + def first_terms(self): + r""" + Return the first terms of the statistic. + + OUTPUT: + + A list of pairs of the form ``(object, value)`` where + ``object`` is a sage object representing an element of the + appropriate collection and ``value`` is an integer. If the + statistic is in the FindStat database, the list contains + exactly the pairs in the database. + + EXAMPLES:: + + sage: findstat(1).first_terms() # optional -- internet,random + [([1], 1), + ([1, 2], 1), + ([2, 1], 1), + ([1, 2, 3], 1), + ([1, 3, 2], 1), + ([2, 1, 3], 1), + ... + + TESTS:: + + sage: r = findstat({d: randint(1,1000) for d in DyckWords(4)}); r # optional -- internet + a new statistic on Cc0005: Dyck paths + + sage: isinstance(r.first_terms(), list) # optional -- internet + True + sage: all(isinstance(e, tuple) and len(e)==2 and isinstance(e[1], (ZZ, int)) for e in r.first_terms()) # optional -- internet + True + """ + return self._first_terms + + def first_terms_str(self): + r""" + Return the first terms of the statistic in the format needed + for a FindStat query. + + OUTPUT: + + A string, where each line is of the form ``object => value``, + where ``object`` is the string representation of an element + of the appropriate collection as used by FindStat and value + is an integer. + + EXAMPLES:: + + sage: findstat(1).first_terms_str()[:10] # optional -- internet,random + '[1] => 1\r\n' + + """ + if self._first_terms != None: + to_str = self._collection.to_string() + return join([to_str(key) + " => " + str(val) + for (key, val) in self._first_terms], "\r\n") + else: + return "" + + def description(self): + r""" + Return the description of the statistic. + + OUTPUT: + + A string, whose first line is used as the name of the + statistic. + + EXAMPLES:: + + sage: print findstat(1).description() # optional -- internet,random + The number of ways to write a permutation as a minimal length product of simple transpositions. + + That is, the number of reduced words for the permutation. E.g., there are two reduced words for $[3,2,1] = (1,2)(2,3)(1,2) = (2,3)(1,2)(2,3)$. + """ + return self._description + + def set_description(self, value): + r""" + Set the description of the statistic. + + INPUT: + + - a string -- the name of the statistic followed by its + description on a separate line. + + OUTPUT: + + - Raise an error, if the query has a match with no + intermediate combinatorial maps. + + This information is used when submitting the statistic with + :meth:`FindStatStatistic.submit`. + + EXAMPLES:: + + sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]); s # optional -- internet + a new statistic on Cc0005: Dyck paths + sage: s.set_description("Random values on Dyck paths.\r\nNot for submssion.") # optional -- internet + sage: s # optional -- internet + a new statistic on Cc0005: Dyck paths + sage: s.name() # optional -- internet + 'Random values on Dyck paths.' + sage: print s.description() # optional -- internet + Random values on Dyck paths. + Not for submssion. + """ + self._raise_error_modifying_statistic_with_perfect_match() + + if value != self._description: + self._modified = True + self._description = value + + def name(self): + r""" + Return the name of the statistic. + + OUTPUT: + + A string, which is just the first line of the description of + the statistic. + + EXAMPLES:: + + sage: findstat(1).name() # optional -- internet,random + u'The number of ways to write a permutation as a minimal length product of simple transpositions.' + """ + return self._description.partition(FINDSTAT_SEPARATOR_NAME)[0] + + def references(self): + r""" + Return the references associated with the statistic. + + OUTPUT: + + An instance of :class:`sage.databases.oeis.FancyTuple`, each + item corresponds to a reference. + + .. TODO:: + + Since the references in the database are sometimes not + formatted properly, this method is unreliable. The + string representation can be obtained via + :attr:`_references`. + + EXAMPLES:: + + sage: findstat(1).references() # optional -- internet,random + 0: P. Edelman and C. Greene, Balanced tableaux, Adv. in Math., 63 (1987), pp. 42-99. + 1: [[OEIS:A005118]] + 2: [[oeis:A246865]] + """ + l = [ref.strip() for ref in self._references.split(FINDSTAT_SEPARATOR_REFERENCES)] + return FancyTuple([ref for ref in l if ref != ""]) + + def set_references(self, value): + r""" + Set the references associated with the statistic. + + INPUT: + + - a string -- the individual references should be separated + by FINDSTAT_SEPARATOR_REFERENCES, which is "\\r\\n". + + OUTPUT: + + - Raise an error, if the query has a match with no + intermediate combinatorial maps. + + This information is used when submitting the statistic with + :meth:`FindStatStatistic.submit`. + + EXAMPLES:: + + sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]); s # optional -- internet + a new statistic on Cc0005: Dyck paths + sage: s.set_references("[1] The wonders of random Dyck paths, Anonymous Coward, [[arXiv:1102.4226]].\r\n[2] [[oeis:A000001]]") # optional -- internet + sage: s.references() # optional -- internet + 0: [1] The wonders of random Dyck paths, Anonymous Coward, [[arXiv:1102.4226]]. + 1: [2] [[oeis:A000001]] + + """ + self._raise_error_modifying_statistic_with_perfect_match() + + if value != self._references: + self._modified = True + self._references = value + + def code(self): + r""" + Return the code associated with the statistic. + + OUTPUT: + + A string. Contributors are encouraged to submit sage code in the form:: + + def statistic(x): + ... + + but the string may also contain code for other computer + algebra systems. + + EXAMPLES:: + + sage: print findstat(1).code() # optional -- internet,random + def statistic(x): + return len(x.reduced_words()) + + sage: print findstat(118).code() # optional -- internet,random + (* in Mathematica *) + tree = {{{{}, {}}, {{}, {}}}, {{{}, {}}, {{}, {}}}}; + Count[tree, {{___}, {{___}, {{___}, {___}}}}, {0, Infinity}] + """ + return self._code + + def set_code(self, value): + r""" + Set the code associated with the statistic. + + INPUT: + + - a string -- contributors are encouraged to submit sage code + in the form:: + + def statistic(x): + ... + + OUTPUT: + + - Raise an error if the query has a match with no + intermediate combinatorial maps. + + This information is used when submitting the statistic with + :meth:`FindStatStatistic.submit`. + + EXAMPLES:: + + sage: s = findstat([(d, randint(1,1000)) for d in DyckWords(4)]) # optional -- internet + sage: s.set_code("def statistic(x):\r\n return randint(1,1000)") # optional -- internet + sage: print s.code() # optional -- internet + def statistic(x): + return randint(1,1000) + """ + self._raise_error_modifying_statistic_with_perfect_match() + + if value != self._code: + self._modified = True + self._code = value + + ###################################################################### + # browse current statistic + ###################################################################### + + def browse(self): + r""" + Open the FindStat web page of the statistic in a browser. + + EXAMPLES:: + + sage: findstat(41).browse() # optional -- webbrowser + """ + if self._query == "ID": + webbrowser.open(FINDSTAT_URL_BROWSE + self.id_str()) + else: + raise NotImplementedError("Would be nice to show the result of the query in the webbrowser.") + + ###################################################################### + # submit current (possibly incompletely defined) statistic + ###################################################################### + + def submit(self, max_values=FINDSTAT_MAX_SUBMISSION_VALUES): + r""" + Open the FindStat web page for editing the statistic in a browser. + + INPUT: + + - ``max_values`` -- integer (default: + ``FINDSTAT_MAX_SUBMISSION_VALUES``); if :meth:`function` is + defined and the statistic is a new statistic, use + :meth:`FindStatCollection.first_terms` to produce at most + ``max_values`` terms. + + OUTPUT: + + - Raise an error if the query has a match with no + intermediate combinatorial maps. + + EXAMPLES:: + + sage: s = findstat(DyckWords(4), lambda x: randint(1,1000)); s # optional -- internet + a new statistic on Cc0005: Dyck paths + + The following uses ``lambda x: randint(1,1000)`` to produce + 14 terms, because ``min(DyckWords(4).cardinality(), + FINDSTAT_MAX_SUBMISSION_VALUES)`` is 14:: + + sage: s.submit() # optional -- webbrowser + + """ + self._raise_error_modifying_statistic_with_perfect_match() + + # if the statistic is given as a function, and we have a new + # statistic then update first_terms + + # it is not clear whether we want to do this also for old statistics. + if self.function() and self.id() == 0: + self._first_terms = self.collection().first_terms(self.function(), + max_values=max_values) + + args = dict() + args[FINDSTAT_STATISTIC_IDENTIFIER] = self._id + args[FINDSTAT_STATISTIC_COLLECTION] = str(self.collection().id()) + args[FINDSTAT_STATISTIC_DATA] = self.first_terms_str() + args[FINDSTAT_STATISTIC_DESCRIPTION] = self._description + args[FINDSTAT_STATISTIC_REFERENCES] = self._references + args[FINDSTAT_STATISTIC_CODE] = self.code() + args[FINDSTAT_POST_SAGE_CELL] = "" + args[FINDSTAT_POST_EDIT] = "" + args[FINDSTAT_POST_AUTHOR] = findstat._user_name + args[FINDSTAT_POST_EMAIL] = findstat._user_email + + assert set(args.keys()) == FINDSTAT_EDIT_FIELDS, "It appears that the list of required post variables for editing a statistic has changed. Please update FindStatStatistic.submit()." + + # write the file + f = tempfile.NamedTemporaryFile(delete=False) + _ = verbose("Created temporary file %s" %f.name, caller_name='FindStat') + f.write(FINDSTAT_POST_HEADER) + if self.id() == 0: + f.write(FINDSTAT_NEWSTATISTIC_FORM_HEADER %FINDSTAT_URL_NEW) + else: + f.write(FINDSTAT_NEWSTATISTIC_FORM_HEADER %(FINDSTAT_URL_EDIT+self.id_str())) + for key, value in args.iteritems(): + _ = verbose("writing argument %s" %key, caller_name='FindStat') + value_encoded = cgi.escape(str(value), quote=True) + _ = verbose("%s" %value_encoded, caller_name='FindStat') + f.write((FINDSTAT_NEWSTATISTIC_FORM_FORMAT %(key, value_encoded))) + f.write(FINDSTAT_NEWSTATISTIC_FORM_FOOTER) + f.close() + _ = verbose("Opening file with webbrowser", caller_name='FindStat') + _ = webbrowser.open(f.name) + + _ = verbose("Waiting a little before deleting the temporary file", caller_name='FindStat') + time.sleep(1) + + f.unlink(f.name) + + # editing and submitting is really the same thing + edit = submit + + +# helper for generation of CartanTypes +def _finite_irreducible_cartan_types_by_rank(n): + """ + Return the Cartan types of rank n. + + INPUT: + + - n -- an integer. + + OUTPUT: + + The list of Cartan types of rank n. + + TESTS:: + + sage: from sage.databases.findstat import _finite_irreducible_cartan_types_by_rank + sage: _finite_irreducible_cartan_types_by_rank(2) + [['A', 2], ['B', 2], ['G', 2]] + """ + cartan_types = [ CartanType(['A',n]) ] + if n >= 2: + cartan_types += [ CartanType(['B',n]) ] + if n >= 3: + cartan_types += [ CartanType(['C',n]) ] + if n >= 4: + cartan_types += [ CartanType(['D',n]) ] + if n in [6,7,8]: + cartan_types += [ CartanType(['E',n]) ] + if n == 4: + cartan_types += [ CartanType(['F',n]) ] + if n == 2: + cartan_types += [ CartanType(['G',n]) ] + return cartan_types + +class FindStatCollection(Element): + r""" + A FindStat collection. + + :class:`FindStatCollection` is a class representing a + combinatorial collection available in the FindStat database. + + Its main use is to allow easy specification of the combinatorial + collection when using :class:`findstat`. It also + provides methods to quickly access its FindStat web page + (:meth:`browse`), check whether a particular element is actually + in the range considered by FindStat (:meth:`in_range`), etc. + + INPUT: + + One of the following: + + - a string eg. 'Dyck paths' or 'DyckPaths', case-insensitive, or + + - an integer designating the FindStat id of the collection, or + + - a sage object belonging to a collection, or + + - an iterable producing a sage object belonging to a collection. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection("Dyck paths") # optional -- internet + Cc0005: Dyck paths + + sage: FindStatCollection(5) # optional -- internet + Cc0005: Dyck paths + + sage: FindStatCollection(DyckWord([1,0,1,0])) # optional -- internet + Cc0005: Dyck paths + + sage: FindStatCollection(DyckWords(2)) # optional -- internet + Cc0005: Dyck paths + + SEEALSO: + + :class:`FindStatCollections` + + """ + __metaclass__ = InheritComparisonClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, entry): + """ + Retrieve a collection from the database. + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection(0) # optional -- internet + Traceback (most recent call last): + ... + ValueError: Could not find FindStat collection for 0. + """ + return FindStatCollections()(entry) + + def __init__(self, parent, id, c, sageconstructor_overridden): + """Initialize the collection. + + This should only be called in + :meth:`FindStatCollections()._element_constructor_` via + `element_class`. + + INPUT: + + - ``parent`` -- :class:`FindStatCollections`. + + - ``id`` -- the FindStat identifier of the collection. + + - ``c`` -- a tuple containing the properties of the + collection, such as its name, the corresponding class in + sage, and so on. + + - ``sageconstructor_overridden`` -- either ``None`` or an + iterable which yields a subset of the elements of the + collection. + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection(5).parent() # optional -- internet + Set of combinatorial collections used by FindStat + + """ + self._id = id + (self._name, self._name_plural, self._url_name, + self._sageclass, self._sageconstructor, self._range, + self._in_range, self._to_str, self._from_str) = c + self._sageconstructor_overridden = sageconstructor_overridden + + Element.__init__(self, parent) + + def __reduce__(self): + """Return a function and its arguments needed to create this + collection. + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("Permutations") # optional -- internet + sage: loads(dumps(c)) == c # optional -- internet + True + + """ + return (FindStatCollection, (self.id(),)) + + def __cmp__(self, other): + """ + TESTS:: + + sage: from sage.databases.findstat import FindStatCollection, FindStatCollections + sage: FindStatCollection("Permutations") == FindStatCollection("Permutations") # optional -- internet + True + + sage: FindStatCollection("Permutations") == FindStatCollection("Integer Partitions") # optional -- internet + False + + sage: FindStatCollection("Permutations") != FindStatCollection("Permutations") # optional -- internet + False + + sage: FindStatCollection("Permutations") != FindStatCollection("Integer Partitions") # optional -- internet + True + + sage: FindStatCollection("Permutations") == 1 # optional -- internet + False + + sage: FindStatCollection("Permutations") != 1 # optional -- internet + True + + sage: sorted(c for c in FindStatCollections())[0] # optional -- internet + Cc0001: Permutations + """ + return self.id().__cmp__(other.id()) + + def in_range(self, element): + r""" + Check whether an element of the collection is in FindStat's precomputed range. + + INPUT: + + - ``element`` -- a sage object that belongs to the collection. + + OUTPUT: + + ``True``, if ``element`` is used by the FindStat search + engine, and ``False`` if it is ignored. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet + sage: c.in_range(GelfandTsetlinPattern([[2, 1], [1]])) # optional -- internet + True + sage: c.in_range(GelfandTsetlinPattern([[3, 1], [1]])) # optional -- internet + True + sage: c.in_range(GelfandTsetlinPattern([[4, 1], [1]])) # optional -- internet,random + False + + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollections + sage: l = FindStatCollections() # optional -- internet + sage: long = [9, 12, 14, 20] + sage: for c in l: # optional -- internet + ....: if c.id() not in long: + ....: f = c.first_terms(lambda x: 1, max_values=10000) + ....: print c, len(f), all(c.in_range(e) for e, _ in f) + ....: + Cc0001: Permutations 10000 True + Cc0002: Integer partitions 270 True + Cc0005: Dyck paths 2054 True + Cc0006: Integer compositions 510 True + Cc0007: Standard tableaux 3734 True + Cc0010: Binary trees 2054 True + Cc0013: Cores 100 True + Cc0017: Alternating sign matrices 7916 True + Cc0018: Gelfand-Tsetlin patterns 934 True + Cc0019: Semistandard tableaux 2100 True + Cc0021: Ordered trees 2055 True + Cc0022: Finite Cartan types 31 True + Cc0023: Parking functions 10000 True + + """ + return self._in_range(element, self._range) + + def first_terms(self, statistic, max_values=FINDSTAT_MAX_SUBMISSION_VALUES): + r""" + Compute the first few terms of the given statistic. + + INPUT: + + - ``statistic`` -- a callable. + + - ``max_values`` -- the number of terms to compute at most. + + OUTPUT: + + A list of pairs of the form (object, value). + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet + sage: c.first_terms(lambda x: 1, max_values=10) # optional -- internet,random + [([[0]], 1), + ([[1]], 1), + ([[2]], 1), + ([[3]], 1), + ([[0, 0], [0]], 1), + ([[1, 0], [0]], 1), + ([[1, 0], [1]], 1), + ([[1, 1], [1]], 1), + ([[2, 0], [0]], 1), + ([[2, 0], [1]], 1)] + """ + if self._sageconstructor_overridden is None: + g = (x for n in self._range for x in self._sageconstructor(n)) + else: + g = self._sageconstructor_overridden + + return [(x, statistic(x)) for (x,_) in zip(g, xrange(max_values))] + + def id(self): + r""" + Return the FindStat identifier of the collection. + + OUTPUT: + + The FindStat identifier of the collection as an integer. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet + sage: c.id() # optional -- internet + 18 + """ + return self._id + + def id_str(self): + r""" + Return the FindStat identifier of the collection. + + OUTPUT: + + The FindStat identifier of the collection as a string. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("GelfandTsetlinPatterns") # optional -- internet + sage: c.id_str() # optional -- internet + 'Cc0018' + """ + id = str(self.id()) + return 'Cc0000'[:-len(id)] + id + + def browse(self): + r""" + Open the FindStat web page of the collection in a browser. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection("Permutations").browse() # optional -- webbrowser + """ + webbrowser.open(FINDSTAT_URL + self._url_name) + + def to_string(self): + r""" + Return a function that returns the FindStat normal + representation given an object. + + OUTPUT: + + The function that produces the string representation as + needed by the FindStat search webpage. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: p = Poset((range(3), [[0, 1], [1, 2]])) # optional -- internet + sage: c = FindStatCollection("Posets") # optional -- internet + sage: c.to_string()(p) # optional -- internet + '([(0, 2), (2, 1)], 3)' + """ + return self._to_str + + def from_string(self): + r""" + Return a function that returns the object given the FindStat + normal representation. + + OUTPUT: + + The function that produces the sage object given its FindStat + normal representation as a string. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: c = FindStatCollection("Posets") # optional -- internet + sage: p = c.from_string()('([(0, 2), (2, 1)], 3)') # optional -- internet + sage: p.cover_relations() # optional -- internet + [[0, 2], [2, 1]] + """ + return self._from_str + + def _repr_(self): + r""" + Return the representation of the FindStat collection. + + OUTPUT: + + The representation, including the identifier and the name. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection("Binary trees") # optional -- internet + Cc0010: Binary trees + """ + return "%s: %s" %(self.id_str(), self._name_plural) + + def name(self): + r""" + Return the name of the FindStat collection. + + OUTPUT: + + The name of the FindStat collection, in singular. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollection + sage: FindStatCollection("Binary trees").name() # optional -- internet + u'Binary tree' + """ + return self._name + +class FindStatCollections(Parent, UniqueRepresentation): + r""" + The class of FindStat collections. + + The elements of this class are combinatorial collections in + FindStat as of August 2015. If a new collection was added to the + web service since then, the dictionary ``_findstat_collections`` + in this class has to be updated accordingly. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollections + sage: sorted(c for c in FindStatCollections()) # optional -- internet + [Cc0001: Permutations, + Cc0002: Integer partitions, + Cc0005: Dyck paths, + Cc0006: Integer compositions, + Cc0007: Standard tableaux, + Cc0009: Set partitions, + Cc0010: Binary trees, + Cc0012: Perfect matchings, + Cc0013: Cores, + Cc0014: Posets, + Cc0017: Alternating sign matrices, + Cc0018: Gelfand-Tsetlin patterns, + Cc0019: Semistandard tableaux, + Cc0020: Graphs, + Cc0021: Ordered trees, + Cc0022: Finite Cartan types, + Cc0023: Parking functions] + """ + + # we set up a dict of FindStat collections containing, with key + # being the FINDSTAT_COLLECTION_IDENTIFIER to tuples, containing in this order: + + # * the FindStat name (FINDSTAT_COLLECTION_NAME) + # * the FindStat name plural (FINDSTAT_COLLECTION_NAME_PLURAL) + # * url's as needed by FindStat (FINDSTAT_COLLECTION_NAME_WIKI) + # * sage element constructor + # * sage constructor (would be parent_initializer) + # * list of arguments for constructor (FINDSTAT_COLLECTION_PARENT_LEVELS_PRECOMPUTED) + # * a function to check whether an object is produced by applying the constructor to some element in the list + # * the (FindStat) string representations of the sage object (would be element_repr) + # * sage constructors of the FindStat string representation (would be element_constructor) + + # when adding a collection, note the following: + + # to recognize a collection given a sage object, + # :meth:`_element_constructor_` checks whether the object is an + # instance of one of the sage element constructors. + + # sage objects are normalized using the method :meth:`to_string`. + # This method should apply to objects produced by + # :meth:`first_terms` as well as to objects produced by + # :meth:`from_string`. + + # several fields are initialised with 'None', they are updated + # upon the first call to this class + _findstat_collections = { + 17: [None, None, None, AlternatingSignMatrix, AlternatingSignMatrices, None, + lambda x, l: x.to_matrix().nrows() in l, + lambda x: str(map(list, list(x._matrix))), + lambda x: AlternatingSignMatrix(literal_eval(x))], + 10: [None, None, None, BinaryTree, BinaryTrees, None, + lambda x, l: x.node_number() in l, + str, + lambda x: BinaryTree(str(x))], + 13: [None, None, None, Core, lambda x: Cores(x[1], x[0]), + None, + lambda x, l: (x.length(), x.k()) in l, + lambda X: "( "+X._repr_()+", "+str(X.k())+" )", + lambda x: (lambda pi, k: Core(pi, k))(*literal_eval(x))], + 5: [None, None, None, DyckWord, DyckWords, None, + lambda x, l: (x.length()/2) in l, + lambda x: str(list(DyckWord(x))), + lambda x: DyckWord(literal_eval(x))], + 22: [None, None, None, CartanType_abstract, _finite_irreducible_cartan_types_by_rank, + None, + lambda x, l: x.rank() in l, + str, + lambda x: CartanType(*literal_eval(str(x)))], + 18: [None, None, None, GelfandTsetlinPattern, lambda x: GelfandTsetlinPatterns(*x), + None, + lambda x, l: any(len(x) == s and max([0] + [max(row) for row in x]) <= m for s, m in l), + str, + lambda x: GelfandTsetlinPattern(literal_eval(x))], + 20: [None, None, None, Graph, graphs, + None, + lambda x, l: x.num_verts() in l, + lambda X: str((sorted(X.canonical_label().edges(False)), X.num_verts())), + lambda x: (lambda E, V: Graph([range(V), lambda i,j: (i,j) in E or (j,i) in E], immutable=True))(*literal_eval(x))], + 6: [None, None, None, Composition, Compositions, None, + lambda x, l: x.size() in l, + str, + lambda x: Composition(literal_eval(x))], + 2: [None, None, None, Partition, Partitions, None, + lambda x, l: x.size() in l, + str, + lambda x: Partition(literal_eval(x))], + 21: [None, None, None, OrderedTree, OrderedTrees, None, + lambda x, l: x.node_number() in l, + str, + lambda x: OrderedTree(literal_eval(x))], + 23: [None, None, None, ParkingFunction_class, ParkingFunctions, None, + lambda x, l: len(x) in l, + str, + lambda x: ParkingFunction(literal_eval(x))], + 12: [None, None, None, PerfectMatching, PerfectMatchings, None, + lambda x, l: x.size() in l, + str, + lambda x: PerfectMatching(literal_eval(x))], + 1: [None, None, None, Permutation, Permutations, None, + lambda x, l: x.size() in l, + str, + lambda x: Permutation(literal_eval(x))], + 14: [None, None, None, FinitePoset, posets, None, + lambda x, l: x.cardinality() in l, + lambda X: str((sorted(X._hasse_diagram.canonical_label().cover_relations()), len(X._hasse_diagram.vertices()))), + lambda x: (lambda R, E: Poset((range(E), R)))(*literal_eval(x))], + 19: [None, None, None, SemistandardTableau, lambda x: SemistandardTableaux(x, max_entry=4), + None, + lambda x, l: x.size() in l, + str, + lambda x: SemistandardTableau(literal_eval(x))], + 9: [None, None, None, SetPartition, SetPartitions, None, + lambda x, l: x.size() in l, + str, + lambda x: SetPartition(literal_eval(x.replace('{','[').replace('}',']')))], + 7: [None, None, None, StandardTableau, StandardTableaux, None, + lambda x, l: x.size() in l, + str, + lambda x: StandardTableau(literal_eval(x))]} + + def __init__(self): + """ + Fetch the collections from FindStat. + + TESTS:: + + sage: from sage.databases.findstat import FindStatCollections + sage: C = FindStatCollections() # optional -- internet + sage: TestSuite(C).run() # optional -- internet + """ + for j in json.load(urlopen(FINDSTAT_URL_DOWNLOADS_COLLECTIONS)): + c = self._findstat_collections[j[FINDSTAT_COLLECTION_IDENTIFIER]] + c[0] = j[FINDSTAT_COLLECTION_NAME] + c[1] = j[FINDSTAT_COLLECTION_NAME_PLURAL] + c[2] = j[FINDSTAT_COLLECTION_NAME_WIKI] + c[5] = literal_eval(j[FINDSTAT_COLLECTION_PARENT_LEVELS_PRECOMPUTED]) + + Parent.__init__(self, category=Sets()) + + def _element_constructor_(self, entry): + """Retrieve a FindStat collection from the database. + + INPUT: + + see :class:`FindStatCollection`. + + TESTS: + + Create an object and find its collection:: + + sage: from sage.databases.findstat import FindStatCollection, FindStatCollections + sage: [FindStatCollection(c.first_terms(lambda x: 0, max_values=1)[0][0]) for c in FindStatCollections()] # optional -- internet + [Cc0001: Permutations, + Cc0002: Integer partitions, + Cc0005: Dyck paths, + Cc0006: Integer compositions, + Cc0007: Standard tableaux, + Cc0009: Set partitions, + Cc0010: Binary trees, + Cc0012: Perfect matchings, + Cc0013: Cores, + Cc0014: Posets, + Cc0017: Alternating sign matrices, + Cc0018: Gelfand-Tsetlin patterns, + Cc0019: Semistandard tableaux, + Cc0020: Graphs, + Cc0021: Ordered trees, + Cc0022: Finite Cartan types, + Cc0023: Parking functions] + + """ + if isinstance(entry, FindStatCollection): + return entry + + if isinstance(entry, (str, unicode)): + # find by name in _findstat_collections + for (id, c) in self._findstat_collections.iteritems(): + if entry.upper() in (c[0].upper(), c[1].upper(), c[2].upper()): + return self.element_class(self, id, c, None) + + elif isinstance(entry, (int, Integer)): + # find by id in _findstat_collections + for (id, c) in self._findstat_collections.iteritems(): + if entry == id: + return self.element_class(self, id, c, None) + + else: + # find collection given an object or a constructor + + # unfortunately, we cannot test with + # isinstance(_, SageObject), since this is True for + # CartanType. + + # TODO: entry == c[4] will work rarely because c[4] might be a function! + # also, the error handling is only necessary because of this... + for (id, c) in self._findstat_collections.iteritems(): + try: + if isinstance(entry, c[3]) or entry == c[4]: + return self.element_class(self, id, c, None) + + except TypeError: + # examples are + # graphs: + # TypeError: cannot compare graph to non-graph () + # perfect matchings: + # TypeError: descriptor 'parent' of 'sage.structure.sage_object.SageObject' object needs an argument + pass + + # check whether entry is iterable (it's not a string!) + try: + obj = iter(entry).next() + for (id, c) in self._findstat_collections.iteritems(): + if isinstance(obj, c[3]): + return self.element_class(self, id, c, entry) + + except TypeError: + pass + + raise ValueError("Could not find FindStat collection for %s." %str(entry)) + + def _repr_(self): + """ + Return the representation of the set of FindStat collections. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollections + sage: FindStatCollections() # optional -- internet + Set of combinatorial collections used by FindStat + """ + return "Set of combinatorial collections used by FindStat" + + def __iter__(self): + """ + Return an iterator over all FindStat collections. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatCollections + sage: [m for m in FindStatCollections()][0] # optional -- internet + Cc0001: Permutations + """ + for c in self._findstat_collections: + yield FindStatCollection(c) + + Element = FindStatCollection + + +class FindStatMap(Element): + r""" + A FindStat map. + + :class:`FindStatMap` is a class representing a combinatorial + map available in the FindStat database. + + The result of a :class:`findstat` query contains a + (possibly empty) list of such maps. This class provides methods + to inspect various properties of these maps, in particular + :meth:`code`. + + INPUT: + + - a string containing the FindStat name of the map, or an integer + representing its FindStat id. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap + sage: FindStatMap(71) # optional -- internet + Mp00071: descent composition + sage: FindStatMap("descent composition") # optional -- internet + Mp00071: descent composition + + SEEALSO: + + :class:`FindStatMaps` + + """ + __metaclass__ = InheritComparisonClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, entry): + """ + Retrieve a map from the database. + + TESTS:: + + sage: from sage.databases.findstat import FindStatMap + sage: FindStatMap("abcdefgh") # optional -- internet + Traceback (most recent call last): + ... + ValueError: Could not find FindStat map for abcdefgh. + """ + return FindStatMaps()(entry) + + def __init__(self, parent, entry): + """Initialize the map. + + This should only be called in + :meth:`FindStatMaps()._element_constructor_` via + `element_class`. + + INPUT: + + - ``parent`` -- :class:`FindStatMaps`. + + - ``entry`` -- a dictionary containing the properties of the + map, such as its name, code, and so on. + + TESTS:: + + sage: from sage.databases.findstat import FindStatMap + sage: FindStatMap(62).parent() # optional -- internet + Set of combinatorial maps used by FindStat + + """ + self._map = entry + Element.__init__(self, parent) + + def __reduce__(self): + """Return a function and its arguments needed to create this + map. + + TESTS:: + + sage: from sage.databases.findstat import FindStatMap + sage: c = FindStatMap(62) # optional -- internet + sage: loads(dumps(c)) == c # optional -- internet + True + + """ + return (FindStatMap, (self.id(),)) + + def id(self): + r""" + Return the FindStat identifier of the map. + + OUTPUT: + + The FindStat identifier of the map as an integer. + + EXAMPLES:: + + sage: m = findstat("Permutations", lambda pi: pi.length())[1][1][0] # optional -- internet + sage: m.id() # optional -- internet + 62 + """ + return self._map[FINDSTAT_MAP_IDENTIFIER] + + def id_str(self): + r""" + Return the FindStat identifier of the map. + + OUTPUT: + + The FindStat identifier of the map as a string. + + EXAMPLES:: + + sage: m = findstat("Permutations", lambda pi: pi.length())[1][1][0] # optional -- internet + sage: m.id_str() # optional -- internet + 'Mp00062' + """ + + id = str(self.id()) + return 'Mp00000'[:-len(id)] + id + + def _repr_(self): + r""" + Return the representation of the FindStat map. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap + sage: FindStatMap(71) # optional -- internet + Mp00071: descent composition + """ + return "%s: %s" %(self.id_str(), self._map[FINDSTAT_MAP_NAME]) + + def __cmp__(self, other): + """ + TESTS:: + + sage: from sage.databases.findstat import FindStatMap, FindStatMaps + sage: FindStatMap(71) == FindStatMap(71) # optional -- internet + True + + sage: FindStatMap(62) == FindStatMap(71) # optional -- internet + False + + sage: FindStatMap(71) != FindStatMap(71) # optional -- internet + False + + sage: FindStatMap(62) != FindStatMap(71) # optional -- internet + True + + sage: FindStatMap(62) == 1 # optional -- internet + False + + sage: FindStatMap(62) != 1 # optional -- internet + True + + sage: sorted(c for c in FindStatMaps())[0] # optional -- internet + Mp00001: to semistandard tableau + """ + return self.id().__cmp__(other.id()) + + def name(self): + r""" + Return the FindStat name of the map. + + OUTPUT: + + The name of the map as a string, as used by FindStat. + + EXAMPLES:: + + sage: m = findstat("Permutations", lambda pi: pi.length())[1][1][0] # optional -- internet + sage: m.name() # optional -- internet + u'inversion-number to major-index bijection' + """ + return self._map[FINDSTAT_MAP_NAME] + + def description(self): + r""" + Return the FindStat description of the map. + + OUTPUT: + + The description as a string. + + EXAMPLES:: + + sage: m = findstat("Permutations", lambda pi: pi.length())[1][1][0] # optional -- internet + sage: print m.description() # optional -- internet,random + Let $\sigma \in \mathcal{S}_n$ be a permutation. + + Maps $\sigma$ to the permutation $\tau$ such that the major code of $\tau$ is given by the Lehmer code of $\sigma$. + + In particular, the number of inversions of $\sigma$ equals the major index of $\tau$. + + EXAMPLES: + + $[3,4,1,2] \mapsto [3,1,4,2]$ + """ + return self._map[FINDSTAT_MAP_DESCRIPTION] + + def domain(self): + r""" + Return the FindStat collection which is the domain of the map. + + OUTPUT: + + The domain of the map as a :class:`FindStatCollection`. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap # optional -- internet + sage: FindStatMap(71).domain() # optional -- internet + Cc0001: Permutations + """ + return FindStatCollection(self._map[FINDSTAT_MAP_DOMAIN]) + + def codomain(self): + r""" + Return the FindStat collection which is the codomain of the map. + + OUTPUT: + + The codomain of the map as a :class:`FindStatCollection`. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap # optional -- internet + sage: FindStatMap(71).codomain() # optional -- internet + Cc0006: Integer compositions + """ + return FindStatCollection(self._map[FINDSTAT_MAP_CODOMAIN]) + + def code(self): + r""" + Return the code associated with the map. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap # optional -- internet + sage: print FindStatMap(71).code() # optional -- internet + def descents_composition(elt): + if len(elt) == 0: + return Composition([]) + d = [-1] + elt.descents() + [len(elt)-1] + return Composition([ d[i+1]-d[i] for i in range(len(d)-1)]) + """ + return self._map[FINDSTAT_MAP_CODE] + + def code_name(self): + r""" + Return the name of the function defined by :meth:`code`. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap # optional -- internet + sage: print FindStatMap(71).code_name() # optional -- internet + descents_composition + """ + return self._map[FINDSTAT_MAP_CODE_NAME] + +class FindStatMaps(Parent, UniqueRepresentation): + r""" + The class of FindStat maps. + + The elements of this class are combinatorial maps currently in + FindStat. + + EXAMPLES: + + We can print a nice list of maps currently in FindStat, sorted by + domain and codomain:: + + sage: from sage.databases.findstat import FindStatMap, FindStatMaps + sage: for m in sorted(FindStatMaps(), key=lambda m: (m.domain(), m.codomain)): # optional -- internet,random + ....: print m.domain().name().ljust(30), m.codomain().name().ljust(30), m.name() + ....: + Permutation Standard tableau Robinson-Schensted insertion tableau + Permutation Integer partition Robinson-Schensted tableau shape + Permutation Binary tree to increasing tree + ... + + """ + def __init__(self): + """ + Fetch all the maps from FindStat. + + TESTS:: + + sage: from sage.databases.findstat import FindStatMaps + sage: M = FindStatMaps() # optional -- internet + sage: TestSuite(M).run() # optional -- internet + """ + self._findstat_maps = json.load(urlopen(FINDSTAT_URL_DOWNLOADS_MAPS)) + Parent.__init__(self, category=Sets()) + + def _element_constructor_(self, entry): + """Initialize a FindStat map. + + INPUT: + + - ``entry`` -- a string containing the FindStat name of the + map, or an integer giving its id, or a dict containing all + the information. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMap + sage: FindStatMap(71) # optional -- internet + Mp00071: descent composition + + """ + if isinstance(entry, FindStatMap): + return entry + + elif entry in self._findstat_maps: + return self.element_class(self, entry) + + elif isinstance(entry, (str, unicode)): + # find by name in _findstat_maps + for c in self._findstat_maps: + if entry.upper() == c[FINDSTAT_MAP_NAME].upper(): + return self.element_class(self, c) + + elif isinstance(entry, (int, Integer)): + # find by id in _findstat_maps + for c in self._findstat_maps: + if entry == c[FINDSTAT_MAP_IDENTIFIER]: + return self.element_class(self, c) + + raise ValueError("Could not find FindStat map for %s." %str(entry)) + + def _repr_(self): + """ + Return the representation of the set of FindStat maps. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMaps + sage: FindStatMaps() # optional -- internet + Set of combinatorial maps used by FindStat + """ + return "Set of combinatorial maps used by FindStat" + + def __iter__(self): + """ + Return an iterator over all FindStat maps. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMaps + sage: [m for m in FindStatMaps()][0] # optional -- internet + Mp00072: binary search tree: left to right + + """ + for m in self._findstat_maps: + yield FindStatMap(m) + + Element = FindStatMap + + +findstat = FindStat() diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index 653468acff0..43b8005e188 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -12,7 +12,7 @@ - Thierry Monteil (2012-02-10 -- 2013-06-21): initial version. -- Vincent Delecroix (2014): modifies continued fractions because of trac:`14567` +- Vincent Delecroix (2014): modifies continued fractions because of :trac:`14567` EXAMPLES:: @@ -156,7 +156,7 @@ from sage.misc.flatten import flatten from sage.misc.unknown import Unknown from sage.misc.misc import embedded -from sage.misc.html import html +from sage.misc.html import HtmlFragment from collections import defaultdict from urllib import urlopen, urlencode import re @@ -1430,10 +1430,12 @@ def links(self, browse=None, format='guess'): sage: s.links(format='url')[3] 'http://oeis.org/A000024' - sage: s.links(format="html") - 0: Wikipedia,
42 (number) + sage: HTML = s.links(format="html"); HTML + 0: Wikipedia, 42 (number) 1: See. also trac ticket #42 ... + sage: type(HTML) + """ url_absolute = lambda s: re.sub('\"\/', '\"' + oeis_url, s) if browse is None: @@ -1445,7 +1447,7 @@ def links(self, browse=None, format='guess'): elif format == 'raw': return FancyTuple(self._fields['H']) elif format == 'html': - html(FancyTuple([url_absolute(_) for _ in self._fields['H']])) + return HtmlFragment(FancyTuple([url_absolute(_) for _ in self._fields['H']])) elif format == 'url': url_list = flatten([_urls(url_absolute(string)) for string in self._fields['H']]) return FancyTuple(url_list) @@ -1783,6 +1785,7 @@ def __repr__(self): index of the value in ``self``. EXAMPLES:: + sage: from sage.databases.oeis import FancyTuple sage: t = FancyTuple(['zero', 'one', 'two', 'three', 4]) ; t 0: zero diff --git a/src/sage/databases/sloane.py b/src/sage/databases/sloane.py index fe17144efad..6ad95500021 100644 --- a/src/sage/databases/sloane.py +++ b/src/sage/databases/sloane.py @@ -377,6 +377,7 @@ def parse_sequence(text=''): which is now deprecated. TESTS:: + sage: from sage.databases.sloane import parse_sequence sage: parse_sequence() doctest:...: DeprecationWarning: The function parse_sequence is not used anymore (2012-01-01). diff --git a/src/sage/dev/git_interface.py b/src/sage/dev/git_interface.py index 8f8148987b9..3cebbe75805 100644 --- a/src/sage/dev/git_interface.py +++ b/src/sage/dev/git_interface.py @@ -211,16 +211,7 @@ def _execute(self, cmd, *args, **kwds): sage: git._execute('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -251,16 +242,7 @@ def _execute_silent(self, cmd, *args, **kwds): sage: git._execute_silent('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -546,9 +528,7 @@ def get_state(self): sage: git._execute_supersilent('rebase', 'branch2') Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (1) for - "git -c user.email=doc@test.test -c user.name=doctest rebase branch2". - ... + GitError: git returned with non-zero exit code ... sage: git.get_state() ('rebase',) sage: git.super_silent.rebase(abort=True) diff --git a/src/sage/dev/saving_dict.py b/src/sage/dev/saving_dict.py index 4dcd4043da2..b7cbffd9013 100644 --- a/src/sage/dev/saving_dict.py +++ b/src/sage/dev/saving_dict.py @@ -158,7 +158,7 @@ def unset_pairing(self): def set_paired(self, other): r""" - Set another class:`SavingDict` to be updated with the reverse of this + Set another :class:`SavingDict` to be updated with the reverse of this one and vice versa. EXAMPLES:: diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 423964b3049..9d0efe01f4d 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -701,6 +701,7 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): ....: filename = os.path.join(path, F) ....: FDS = FileDocTestSource(filename, DocTestDefaults(long=True,optional=True)) ....: FDS._test_enough_doctests(verbose=False) + There are 7 tests in sage/combinat/diagram_algebras.py that are not being run There are 7 tests in sage/combinat/dyck_word.py that are not being run There are 7 tests in sage/combinat/finite_state_machine.py that are not being run There are 6 tests in sage/combinat/interval_posets.py that are not being run diff --git a/src/sage/dynamics/interval_exchanges/constructors.py b/src/sage/dynamics/interval_exchanges/constructors.py index 9e9dc83370b..0a3206c3255 100644 --- a/src/sage/dynamics/interval_exchanges/constructors.py +++ b/src/sage/dynamics/interval_exchanges/constructors.py @@ -155,9 +155,8 @@ def _two_lists(a): -- two lists - TESTS: + TESTS:: - :: sage: from sage.dynamics.interval_exchanges.constructors import _two_lists sage: _two_lists(('a1 a2','b1 b2')) [['a1', 'a2'], ['b1', 'b2']] diff --git a/src/sage/dynamics/interval_exchanges/labelled.py b/src/sage/dynamics/interval_exchanges/labelled.py index 95d67c3d4c2..e138d9d836b 100644 --- a/src/sage/dynamics/interval_exchanges/labelled.py +++ b/src/sage/dynamics/interval_exchanges/labelled.py @@ -774,6 +774,7 @@ def __cmp__(self, other): The order is lexicographic on intervals[0] + intervals[1] TESTS:: + sage: list_of_p2 = [] sage: p0 = iet.Permutation('1 2', '1 2') sage: p1 = iet.Permutation('1 2', '2 1') @@ -806,6 +807,7 @@ def _twin(self): The twin relations of the permutation. TESTS:: + sage: p = iet.Permutation('a b','a b') sage: p._twin [[0, 1], [0, 1]] @@ -1123,6 +1125,7 @@ def __cmp__(self, other): Order is lexicographic on length of intervals and on intervals. TESTS:: + sage: p0 = iet.GeneralizedPermutation('0 0','1 1 2 2') sage: p1 = iet.GeneralizedPermutation('0 0','1 2 1 2') sage: p2 = iet.GeneralizedPermutation('0 0','1 2 2 1') diff --git a/src/sage/dynamics/interval_exchanges/reduced.py b/src/sage/dynamics/interval_exchanges/reduced.py index 44e0dfebbe6..25a4ab99f92 100644 --- a/src/sage/dynamics/interval_exchanges/reduced.py +++ b/src/sage/dynamics/interval_exchanges/reduced.py @@ -536,6 +536,7 @@ def __hash__(self): Returns a hash value (does not depends of the alphabet). TESTS:: + sage: p = iet.Permutation([1,2],[1,2], reduced=True) sage: q = iet.Permutation([1,2],[2,1], reduced=True) sage: r = iet.Permutation([2,1],[1,2], reduced=True) @@ -969,6 +970,7 @@ def __ne__(self, other) : Tests difference. TESTS:: + sage: p = iet.GeneralizedPermutation('a b b', 'c c a', reduced = True) sage: q = iet.GeneralizedPermutation('b b a', 'c c a', reduced = True) sage: r = iet.GeneralizedPermutation('i j j', 'k k i', reduced = True) @@ -1437,9 +1439,8 @@ def list(self, flips=False): - ``flips`` - boolean (default: False) if True the output contains 2-uple of (label, flip) - EXAMPLES: + EXAMPLES:: - :: sage: p = iet.Permutation('a b','b a',reduced=True,flips='b') sage: p.list(flips=True) [[('a', 1), ('b', -1)], [('b', -1), ('a', 1)]] diff --git a/src/sage/dynamics/interval_exchanges/template.py b/src/sage/dynamics/interval_exchanges/template.py index ded494ef6d0..8d9ee2fd7f9 100644 --- a/src/sage/dynamics/interval_exchanges/template.py +++ b/src/sage/dynamics/interval_exchanges/template.py @@ -251,7 +251,7 @@ class Permutation(SageObject): r""" Template for all permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -790,7 +790,7 @@ class PermutationIET(Permutation): """ Template for permutation from Interval Exchange Transformation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1582,7 +1582,7 @@ class PermutationLI(Permutation): r""" Template for quadratic permutation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1857,7 +1857,7 @@ class FlippedPermutation(Permutation): r""" Template for flipped generalized permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1916,7 +1916,7 @@ class FlippedPermutationIET(FlippedPermutation, PermutationIET): r""" Template for flipped Abelian permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1945,7 +1945,7 @@ class FlippedPermutationLI(FlippedPermutation, PermutationLI): r""" Template for flipped quadratic permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1981,7 +1981,7 @@ class RauzyDiagram(SageObject): r""" Template for Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! @@ -2600,27 +2600,30 @@ def __init__(self, p, top_bottom_inversion=False, symmetric=False): r""" - self._succ contains successors - self._pred contains predecessors + - ``self._succ`` contains successors - self._element_class is the class of elements of self - self._element is an instance of this class (hence contains the alphabet, - the representation mode, ...). It is used to store data about property - of permutations and also as a fast iterator. + - ``self._pred`` contains predecessors - INPUT: + - ``self._element_class`` is the class of elements of ``self`` - - ``right_induction`` - boolean or 'top' or 'bottom': consider the - right induction + - ``self._element`` is an instance of this class (hence + contains the alphabet, the representation mode, ...). It is + used to store data about property of permutations and also as + a fast iterator. - - ``left_induction`` - boolean or 'top' or 'bottom': consider the - left induction + INPUT: + + - ``right_induction`` - boolean or 'top' or 'bottom': consider the + right induction + + - ``left_induction`` - boolean or 'top' or 'bottom': consider the + left induction - - ``left_right_inversion`` - consider the left right inversion + - ``left_right_inversion`` - consider the left right inversion - - ``top_bottom_inversion`` - consider the top bottom inversion + - ``top_bottom_inversion`` - consider the top bottom inversion - - ``symmetric`` - consider the symmetric + - ``symmetric`` - consider the symmetric TESTS:: @@ -3529,7 +3532,7 @@ class FlippedRauzyDiagram(RauzyDiagram): r""" Template for flipped Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! diff --git a/src/sage/ext/interrupt/implementation.c b/src/sage/ext/interrupt/implementation.c index 020eca572cd..7b96199d744 100644 --- a/src/sage/ext/interrupt/implementation.c +++ b/src/sage/ext/interrupt/implementation.c @@ -42,6 +42,7 @@ Interrupt and signal handling for Sage #ifdef __linux__ #include #endif +#include #include "interrupt/struct_signals.h" #include "interrupt/interrupt.h" @@ -110,7 +111,7 @@ static void sage_interrupt_handler(int sig) if (_signals.sig_on_count > 0) { - if (!_signals.block_sigint) + if (!_signals.block_sigint && !PARI_SIGINT_block) { /* Raise an exception so Python can see it */ do_raise_exception(sig); @@ -132,7 +133,10 @@ static void sage_interrupt_handler(int sig) * we store the signal number for later use. But make sure we * don't overwrite a SIGHUP or SIGTERM which we already received. */ if (_signals.interrupt_received != SIGHUP && _signals.interrupt_received != SIGTERM) + { _signals.interrupt_received = sig; + PARI_SIGINT_pending = sig; + } } /* Handler for SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGBUS, SIGSEGV @@ -239,8 +243,9 @@ static void _sig_on_interrupt_received() sigprocmask(SIG_BLOCK, &sigmask_with_sigint, &oldset); do_raise_exception(_signals.interrupt_received); - _signals.interrupt_received = 0; _signals.sig_on_count = 0; + _signals.interrupt_received = 0; + PARI_SIGINT_pending = 0; sigprocmask(SIG_SETMASK, &oldset, NULL); } @@ -250,8 +255,10 @@ static void _sig_on_interrupt_received() static void _sig_on_recover() { _signals.block_sigint = 0; + PARI_SIGINT_block = 0; _signals.sig_on_count = 0; _signals.interrupt_received = 0; + PARI_SIGINT_pending = 0; /* Reset signal mask */ sigprocmask(SIG_SETMASK, &default_sigmask, NULL); diff --git a/src/sage/ext/interrupt/interrupt.pyx b/src/sage/ext/interrupt/interrupt.pyx index c41db11eaca..b928bc27739 100644 --- a/src/sage/ext/interrupt/interrupt.pyx +++ b/src/sage/ext/interrupt/interrupt.pyx @@ -1,3 +1,5 @@ +# Needed for PARI_SIGINT_block in implementation.c: +# distutils: libraries = pari gmp r""" Interrupt and signal handling @@ -155,6 +157,8 @@ def init_interrupts(): This is normally done exactly once during Sage startup when importing this module. + + OUTPUT: the old Python-level interrupt handler """ # Set the Python-level interrupt handler. When a SIGINT occurs, # this will not be called directly. Instead, a SIGINT is caught by @@ -165,10 +169,12 @@ def init_interrupts(): # now). This handler issues a sig_check() which finally raises the # KeyboardInterrupt exception. import signal - signal.signal(signal.SIGINT, sage_python_check_interrupt) + old = signal.signal(signal.SIGINT, sage_python_check_interrupt) setup_sage_signal_handler() + return old + def sig_on_reset(): """ diff --git a/src/sage/ext/interrupt/tests.pyx b/src/sage/ext/interrupt/tests.pyx index da8abf5fa92..87b027b278d 100644 --- a/src/sage/ext/interrupt/tests.pyx +++ b/src/sage/ext/interrupt/tests.pyx @@ -18,6 +18,9 @@ AUTHORS: import signal +from libc.signal cimport (SIGHUP, SIGINT, SIGABRT, SIGILL, SIGSEGV, + SIGFPE, SIGBUS, SIGQUIT) +from libc.stdlib cimport abort cdef extern from 'interrupt/tests_helper.c': void ms_sleep(long ms) nogil @@ -28,7 +31,6 @@ cdef extern from *: ctypedef int volatile_int "volatile int" -include 'sage/ext/signals.pxi' include 'sage/ext/interrupt.pxi' include 'sage/ext/stdsage.pxi' from cpython cimport PyErr_SetString diff --git a/src/sage/ext/pselect.pyx b/src/sage/ext/pselect.pyx index 87ff200abcb..d72a2a46848 100644 --- a/src/sage/ext/pselect.pyx +++ b/src/sage/ext/pselect.pyx @@ -60,8 +60,9 @@ Now using the ``multiprocessing`` module:: # http://www.gnu.org/licenses/ #***************************************************************************** -include "signals.pxi" cimport libc.errno +from posix.signal cimport * +from posix.select cimport * cpdef int get_fileno(f) except -1: diff --git a/src/sage/ext/signals.pxi b/src/sage/ext/signals.pxi deleted file mode 100644 index cdf37694f68..00000000000 --- a/src/sage/ext/signals.pxi +++ /dev/null @@ -1,39 +0,0 @@ -# Declare system calls related to signal handling - -cdef extern from "": - void abort() nogil - -cdef extern from "": - ctypedef void *sigset_t - # Renaming of this struct is necessary because Cython folds the - # "struct" namespace into the normal namespace. - struct Sigaction "sigaction": - void (*sa_handler)(int) - sigset_t sa_mask - int sa_flags - - int SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK - int SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGKILL - int SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGBUS, SIGCHLD - int signal_raise "raise"(int signum) - int sigprocmask(int how, sigset_t *set, sigset_t *oldset) - int sigemptyset(sigset_t *set) - int sigaddset(sigset_t *set, int signum) - int sigaction(int signum, Sigaction *act, Sigaction *oldact) - -cdef extern from "": - struct timespec: - long tv_sec - long tv_nsec - -cdef extern from "": - ctypedef void *fd_set - void FD_CLR(int fd, fd_set *set) - bint FD_ISSET(int fd, fd_set *set) - void FD_SET(int fd, fd_set *set) - void FD_ZERO(fd_set *set) - int FD_SETSIZE - - int pselect(int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, timespec *timeout, - sigset_t *sigmask) diff --git a/src/sage/ext/stdsage.pxi b/src/sage/ext/stdsage.pxi index d2d32609e9d..79f1dffec8b 100644 --- a/src/sage/ext/stdsage.pxi +++ b/src/sage/ext/stdsage.pxi @@ -13,5 +13,8 @@ Standard C helper code for Cython modules include "sage/ext/interrupt.pxi" from sage.ext.stdsage cimport PY_NEW, HAS_DICTIONARY -from sage.ext.memory cimport sage_free, sage_realloc, sage_malloc, sage_calloc +from sage.ext.memory cimport ( + sage_free, sage_realloc, sage_malloc, sage_calloc, + check_allocarray, check_reallocarray, + check_malloc, check_realloc, check_calloc) from sage.ext.memory import init_memory_functions diff --git a/src/sage/functions/airy.py b/src/sage/functions/airy.py index b4bffde3848..797c9aa9cb7 100644 --- a/src/sage/functions/airy.py +++ b/src/sage/functions/airy.py @@ -216,7 +216,7 @@ def _evalf_(self, x, **kwargs): 0.33149330543214117 - 0.3174498589684438*I TESTS:: - + sage: parent(airy_ai_simple(3).n(algorithm='scipy')) Real Field with 53 bits of precision sage: airy_ai_simple(3).n(algorithm='scipy', prec=200) @@ -309,7 +309,7 @@ def _evalf_(self, x, **kwargs): -0.43249265984180707 + 0.09804785622924324*I TESTS:: - + sage: parent(airy_ai_prime(3).n(algorithm='scipy')) Real Field with 53 bits of precision sage: airy_ai_prime(3).n(algorithm='scipy', prec=200) @@ -356,7 +356,8 @@ def airy_ai(alpha, x=None, hold_derivative=True, **kwds): solutions to the Airy differential equation `f''(x) - x f(x) = 0`. It is defined by the initial conditions: - .. math :: + .. math:: + \operatorname{Ai}(0)=\frac{1}{2^{2/3} \Gamma\left(\frac{2}{3}\right)}, \operatorname{Ai}'(0)=-\frac{1}{2^{1/3}\Gamma\left(\frac{1}{3}\right)}. @@ -364,6 +365,7 @@ def airy_ai(alpha, x=None, hold_derivative=True, **kwds): Another way to define the Airy Ai function is: .. math:: + \operatorname{Ai}(x)=\frac{1}{\pi}\int_0^\infty \cos\left(\frac{1}{3}t^3+xt\right) dt. @@ -375,7 +377,7 @@ def airy_ai(alpha, x=None, hold_derivative=True, **kwds): `\operatorname{Ai}^{(n)}(z)`, and for `\alpha = -n = -1,-2,-3,\ldots` this gives the `n`-fold iterated integral. - .. math :: + .. math:: f_0(z) = \operatorname{Ai}(z) @@ -491,7 +493,7 @@ def __init__(self): `\operatorname{Bi}^{(n)}(z)`, and for `\alpha = -n = -1,-2,-3,\ldots` this gives the `n`-fold iterated integral. - .. math :: + .. math:: f_0(z) = \operatorname{Bi}(z) @@ -637,7 +639,7 @@ def _evalf_(self, x, **kwargs): 0.648858208330395 + 0.34495863476804844*I TESTS:: - + sage: parent(airy_bi_simple(3).n(algorithm='scipy')) Real Field with 53 bits of precision sage: airy_bi_simple(3).n(algorithm='scipy', prec=200) @@ -730,7 +732,7 @@ def _evalf_(self, x, **kwargs): 0.135026646710819 - 0.1288373867812549*I TESTS:: - + sage: parent(airy_bi_prime(3).n(algorithm='scipy')) Real Field with 53 bits of precision sage: airy_bi_prime(3).n(algorithm='scipy', prec=200) @@ -777,7 +779,8 @@ def airy_bi(alpha, x=None, hold_derivative=True, **kwds): solutions to the Airy differential equation `f''(x) - x f(x) = 0`. It is defined by the initial conditions: - .. math :: + .. math:: + \operatorname{Bi}(0)=\frac{1}{3^{1/6} \Gamma\left(\frac{2}{3}\right)}, \operatorname{Bi}'(0)=\frac{3^{1/6}}{ \Gamma\left(\frac{1}{3}\right)}. @@ -785,6 +788,7 @@ def airy_bi(alpha, x=None, hold_derivative=True, **kwds): Another way to define the Airy Bi function is: .. math:: + \operatorname{Bi}(x)=\frac{1}{\pi}\int_0^\infty \left[ \exp\left( xt -\frac{t^3}{3} \right) +\sin\left(xt + \frac{1}{3}t^3\right) \right ] dt. diff --git a/src/sage/functions/bessel.py b/src/sage/functions/bessel.py index e0472302827..10fc61764fb 100644 --- a/src/sage/functions/bessel.py +++ b/src/sage/functions/bessel.py @@ -616,7 +616,7 @@ class Function_Bessel_I(BuiltinFunction): handled by a combination of Maxima and Sage (Ginac/Pynac). TESTS:: - + sage: N(bessel_I(1,1),500) 0.565159103992485027207696027609863307328899621621092009480294489479255640964371134092664997766814410064677886055526302676857637684917179812041131208121 diff --git a/src/sage/functions/exp_integral.py b/src/sage/functions/exp_integral.py index f10babd87cf..eba684007c7 100644 --- a/src/sage/functions/exp_integral.py +++ b/src/sage/functions/exp_integral.py @@ -1310,7 +1310,7 @@ class Function_exp_integral(BuiltinFunction): The precision for the result is deduced from the precision of the input. Convert the input to a higher precision explicitly if a result with higher precision is desired:: - + sage: Ei(RealField(300)(1.1)) 2.16737827956340282358378734233807621497112737591639704719499002090327541763352339357795426 diff --git a/src/sage/functions/hyperbolic.py b/src/sage/functions/hyperbolic.py index 939a4eaf952..575e4e2fae2 100644 --- a/src/sage/functions/hyperbolic.py +++ b/src/sage/functions/hyperbolic.py @@ -667,6 +667,7 @@ def _eval_numpy_(self, x): def _derivative_(self, *args, **kwds): """ EXAMPLES:: + sage: bool(diff(acoth(x), x) == diff(atanh(x), x)) True sage: diff(acoth(x), x) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index 68fbb7387a1..602f9d24e2f 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -206,7 +206,7 @@ def __init__(self): Initialize class. EXAMPLES:: - + sage: maxima(hypergeometric) hypergeometric """ @@ -227,7 +227,7 @@ def __call__(self, a, b, z, **kwargs): - ``z`` -- a number or symbolic expression EXAMPLES:: - + sage: hypergeometric([], [], 1) hypergeometric((), (), 1) sage: hypergeometric([], [1], 1) @@ -265,7 +265,7 @@ def _print_latex_(self, a, b, z): def _eval_(self, a, b, z, **kwargs): """ EXAMPLES:: - + sage: hypergeometric([], [], 0) 1 """ diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index ac1f2e0fd00..a25b85f5976 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -565,8 +565,10 @@ def __init__(self): 0.567143290409784 """ BuiltinFunction.__init__(self, "lambert_w", nargs=2, - conversions={'mathematica':'ProductLog', - 'maple':'LambertW'}) + conversions={'mathematica': 'ProductLog', + 'maple': 'LambertW', + 'matlab': 'lambertw', + 'maxima': 'generalized_lambert_w'}) def __call__(self, *args, **kwds): r""" @@ -718,21 +720,25 @@ def _maxima_init_evaled_(self, n, z): sage: lambert_w(0, x)._maxima_() lambert_w(_SAGE_VAR_x) sage: lambert_w(1, x)._maxima_() - Traceback (most recent call last): - ... - NotImplementedError: Non-principal branch lambert_w[1](x) is not implemented in Maxima + generalized_lambert_w(1,_SAGE_VAR_x) + + TESTS:: + + sage: lambert_w(x)._maxima_()._sage_() + lambert_w(x) + sage: lambert_w(2, x)._maxima_()._sage_() + lambert_w(2, x) """ + if isinstance(z, str): + maxima_z = z + elif hasattr(z, '_maxima_init_'): + maxima_z = z._maxima_init_() + else: + maxima_z = str(z) if n == 0: - if isinstance(z,str): - maxima_z=z - elif hasattr(z,'_maxima_init_'): - maxima_z=z._maxima_init_() - else: - maxima_z=str(z) return "lambert_w(%s)" % maxima_z else: - raise NotImplementedError("Non-principal branch lambert_w[%s](%s) is not implemented in Maxima" % (n, z)) - + return "generalized_lambert_w(%s,%s)" % (n, maxima_z) def _print_(self, n, z): """ @@ -759,15 +765,17 @@ def _print_latex_(self, n, z): EXAMPLES:: sage: latex(lambert_w(1)) - \operatorname{W_0}(1) + \operatorname{W}({1}) sage: latex(lambert_w(0,x)) - \operatorname{W_0}(x) + \operatorname{W}({x}) sage: latex(lambert_w(1,x)) - \operatorname{W_{1}}(x) + \operatorname{W_{1}}({x}) + sage: latex(lambert_w(1,x+exp(x))) + \operatorname{W_{1}}({x + e^{x}}) """ if n == 0: - return r"\operatorname{W_0}(%s)" % z + return r"\operatorname{W}({%s})" % z._latex_() else: - return r"\operatorname{W_{%s}}(%s)" % (n, z) + return r"\operatorname{W_{%s}}({%s})" % (n, z._latex_()) lambert_w = Function_lambert_w() diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index 2939b99d3d1..d84d9a90ee4 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -1,15 +1,6 @@ r""" Orthogonal Polynomials -This module wraps some of the orthogonal/special functions in the -Maxima package "orthopoly". This package was written by Barton -Willis of the University of Nebraska at Kearney. It is released -under the terms of the General Public License (GPL). Send -Maxima-related bug reports and comments on this module to -willisb@unk.edu. In your report, please include Maxima and specfun -version information. - - - The Chebyshev polynomial of the first kind arises as a solution to the differential equation @@ -255,17 +246,9 @@ in the notation of Ronald L. Graham, Donald E. Knuth and Oren Patashnik in their book Concrete Mathematics. -.. note:: - - The first call of any of these will usually cost a bit extra - (it loads "specfun", but I'm not sure if that is the real reason). - The next call is usually faster but not always. - .. TODO:: - Implement associated Legendre polynomials and Zernike - polynomials. (Neither is in Maxima.) - :wikipedia:`Associated_Legendre_polynomials` + Implement Zernike polynomials. :wikipedia:`Zernike_polynomials` REFERENCES: @@ -296,6 +279,12 @@ - David Joyner (2006-06) - Stefan Reiterer (2010-) +- Ralf Stephan (2015-) + +The original module wrapped some of the orthogonal/special functions +in the Maxima package "orthopoly" and was was written by Barton +Willis of the University of Nebraska at Kearney. + """ #***************************************************************************** @@ -318,14 +307,18 @@ import warnings from sage.misc.sage_eval import sage_eval -from sage.rings.all import ZZ, RR, CC +from sage.rings.all import ZZ, QQ, RR, CC +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.real_mpfr import is_RealField from sage.rings.complex_field import is_ComplexField from sage.calculus.calculus import maxima +from sage.symbolic.ring import SR, is_SymbolicVariable from sage.symbolic.function import BuiltinFunction -from sage.symbolic.expression import is_Expression +from sage.symbolic.expression import Expression from sage.functions.other import factorial, binomial from sage.structure.all import parent @@ -345,11 +338,11 @@ def _init(): Then after using one of these functions, it changes:: - sage: from sage.functions.orthogonal_polys import legendre_P - sage: legendre_P(2,x) - 3/2*(x - 1)^2 + 3*x - 2 + sage: from sage.functions.orthogonal_polys import laguerre + sage: laguerre(2,x) + 1/2*x^2 - 2*x + 1 sage: sage.functions.orthogonal_polys._done - True + False Note that because here we use a Pynac variable ``x``, @@ -367,7 +360,7 @@ def _init(): _done = True -class OrthogonalPolynomial(BuiltinFunction): +class OrthogonalFunction(BuiltinFunction): """ Base class for orthogonal polynomials. @@ -381,13 +374,13 @@ class OrthogonalPolynomial(BuiltinFunction): """ def __init__(self, name, nargs=2, latex_name=None, conversions={}): """ - :class:`OrthogonalPolynomial` class needs the same input parameter as + :class:`OrthogonalFunction` class needs the same input parameter as it's parent class. EXAMPLES:: - sage: from sage.functions.orthogonal_polys import OrthogonalPolynomial - sage: new = OrthogonalPolynomial('testo_P') + sage: from sage.functions.orthogonal_polys import OrthogonalFunction + sage: new = OrthogonalFunction('testo_P') sage: new testo_P """ @@ -396,7 +389,7 @@ def __init__(self, name, nargs=2, latex_name=None, conversions={}): except KeyError: self._maxima_name = None - super(OrthogonalPolynomial,self).__init__(name=name, nargs=nargs, + super(OrthogonalFunction,self).__init__(name=name, nargs=nargs, latex_name=latex_name, conversions=conversions) def _maxima_init_evaled_(self, *args): @@ -406,8 +399,8 @@ def _maxima_init_evaled_(self, *args): EXAMPLES:: - sage: from sage.functions.orthogonal_polys import OrthogonalPolynomial - sage: P = OrthogonalPolynomial('testo_P') + sage: from sage.functions.orthogonal_polys import OrthogonalFunction + sage: P = OrthogonalFunction('testo_P') sage: P._maxima_init_evaled_(2, 5) is None True """ @@ -419,8 +412,8 @@ def eval_formula(self, *args): EXAMPLES:: - sage: from sage.functions.orthogonal_polys import OrthogonalPolynomial - sage: P = OrthogonalPolynomial('testo_P') + sage: from sage.functions.orthogonal_polys import OrthogonalFunction + sage: P = OrthogonalFunction('testo_P') sage: P.eval_formula(1,2.0) Traceback (most recent call last): ... @@ -455,7 +448,7 @@ def _eval_(self, n, *args): """ return None - def __call__(self, n, *args, **kwds): + def __call__(self, *args, **kwds): """ This overides the call method from SageObject to avoid problems with coercions, since the _eval_ method is able to handle more data types than symbolic functions @@ -469,9 +462,18 @@ def __call__(self, n, *args, **kwds): sage: chebyshev_T(5, a) 16*a^2 + a - 4 """ - return super(OrthogonalPolynomial,self).__call__(n, *args, **kwds) + algorithm = kwds.get('algorithm', None) + if algorithm == 'pari': + return self.eval_pari(*args, **kwds) + elif algorithm == 'recursive': + return self.eval_recursive(*args, **kwds) + elif algorithm == 'maxima': + return self._maxima_init_evaled_(*args, **kwds) + + return super(OrthogonalFunction,self).__call__(*args, **kwds) + -class ChebyshevPolynomial(OrthogonalPolynomial): +class ChebyshevFunction(OrthogonalFunction): """ Abstract base class for Chebyshev polynomials of the first and second kind. @@ -513,7 +515,7 @@ def __call__(self, n, *args, **kwds): except Exception: pass - return super(ChebyshevPolynomial,self).__call__(n, *args, **kwds) + return super(ChebyshevFunction,self).__call__(n, *args, **kwds) def _eval_(self, n, x): """ @@ -556,11 +558,11 @@ def _eval_(self, n, x): if n in ZZ: n = ZZ(n) # Expanded symbolic expression only for small values of n - if is_Expression(x) and n.abs() < 32: + if isinstance(x, Expression) and n.abs() < 32: return self.eval_formula(n, x) return self.eval_algebraic(n, x) - if is_Expression(x) or is_Expression(n): + if isinstance(x, Expression) or isinstance(n, Expression): # Check for known identities try: return self._eval_special_values_(n, x) @@ -582,7 +584,7 @@ def _eval_(self, n, x): return None -class Func_chebyshev_T(ChebyshevPolynomial): +class Func_chebyshev_T(ChebyshevFunction): """ Chebyshev polynomials of the first kind. @@ -611,7 +613,7 @@ def __init__(self): sage: chebyshev_T2(1,x) x """ - ChebyshevPolynomial.__init__(self, "chebyshev_T", nargs=2, + ChebyshevFunction.__init__(self, "chebyshev_T", nargs=2, conversions=dict(maxima='chebyshev_t', mathematica='ChebyshevT')) @@ -882,7 +884,8 @@ def _derivative_(self, n, x, diff_param): chebyshev_T = Func_chebyshev_T() -class Func_chebyshev_U(ChebyshevPolynomial): + +class Func_chebyshev_U(ChebyshevFunction): """ Class for the Chebyshev polynomial of the second kind. @@ -909,7 +912,7 @@ def __init__(self): sage: chebyshev_U2(1,x) 2*x """ - ChebyshevPolynomial.__init__(self, "chebyshev_U", nargs=2, + ChebyshevFunction.__init__(self, "chebyshev_U", nargs=2, conversions=dict(maxima='chebyshev_u', mathematica='ChebyshevU')) @@ -1156,51 +1159,7 @@ def _derivative_(self, n, x, diff_param): chebyshev_U = Func_chebyshev_U() -def gen_laguerre(n,a,x): - """ - Returns the generalized Laguerre polynomial for integers `n > -1`. - Typically, `a = 1/2` or `a = -1/2`. - - REFERENCES: - - - Table on page 789 in [ASHandbook]_. - - EXAMPLES:: - - sage: x = PolynomialRing(QQ, 'x').gen() - sage: gen_laguerre(2,1,x) - 1/2*x^2 - 3*x + 3 - sage: gen_laguerre(2,1/2,x) - 1/2*x^2 - 5/2*x + 15/8 - sage: gen_laguerre(2,-1/2,x) - 1/2*x^2 - 3/2*x + 3/8 - sage: gen_laguerre(2,0,x) - 1/2*x^2 - 2*x + 1 - sage: gen_laguerre(3,0,x) - -1/6*x^3 + 3/2*x^2 - 3*x + 1 - - Check that :trac:`17192` is fixed:: - sage: x = PolynomialRing(QQ, 'x').gen() - sage: gen_laguerre(0,1,x) - 1 - - sage: gen_laguerre(-1,1,x) - Traceback (most recent call last): - ... - ValueError: n must be greater than -1, got n = -1 - - sage: gen_laguerre(-7,1,x) - Traceback (most recent call last): - ... - ValueError: n must be greater than -1, got n = -7 - """ - if not (n > -1): - raise ValueError("n must be greater than -1, got n = {0}".format(n)) - - _init() - return sage_eval(maxima.eval('gen_laguerre(%s,%s,x)'%(ZZ(n),a)), locals={'x':x}) - -def gen_legendre_P(n,m,x): +def gen_legendre_P(n, m, x): r""" Returns the generalized (or associated) Legendre function of the first kind. @@ -1225,7 +1184,7 @@ def gen_legendre_P(n,m,x): sage: gen_legendre_P(3, 1, t) -3/2*(5*t^2 - 1)*sqrt(-t^2 + 1) sage: gen_legendre_P(4, 3, t) - 105*(t^2 - 1)*sqrt(-t^2 + 1)*t + 105*(t^3 - t)*sqrt(-t^2 + 1) sage: gen_legendre_P(7, 3, I).expand() -16695*sqrt(2) sage: gen_legendre_P(4, 1, 2.5) @@ -1238,7 +1197,8 @@ def gen_legendre_P(n,m,x): else: return sqrt(1-x**2)*(((n-m+1)*x*gen_legendre_P(n,m-1,x)-(n+m-1)*gen_legendre_P(n-1,m-1,x))/(1-x**2)) -def gen_legendre_Q(n,m,x): + +def gen_legendre_Q(n, m, x): """ Returns the generalized (or associated) Legendre function of the second kind. @@ -1277,7 +1237,8 @@ def gen_legendre_Q(n,m,x): else: return ((n-m+1)*x*gen_legendre_Q(n,m-1,x)-(n+m-1)*gen_legendre_Q(n-1,m-1,x))/sqrt(1-x**2) -def hermite(n,x): + +def hermite(n, x): """ Returns the Hermite polynomial for integers `n > -1`. @@ -1305,6 +1266,7 @@ def hermite(n,x): 8*(8*w^2 - 3)*w Check that :trac:`17192` is fixed:: + sage: x = PolynomialRing(QQ, 'x').gen() sage: hermite(0,x) 1 @@ -1325,7 +1287,8 @@ def hermite(n,x): _init() return sage_eval(maxima.eval('hermite(%s,x)'%ZZ(n)), locals={'x':x}) -def jacobi_P(n,a,b,x): + +def jacobi_P(n, a, b, x): r""" Returns the Jacobi polynomial `P_n^{(a,b)}(x)` for integers `n > -1` and a and b symbolic or `a > -1` @@ -1347,6 +1310,7 @@ def jacobi_P(n,a,b,x): 5.009999999999998 Check that :trac:`17192` is fixed:: + sage: x = PolynomialRing(QQ, 'x').gen() sage: jacobi_P(0,0,0,x) 1 @@ -1367,46 +1331,8 @@ def jacobi_P(n,a,b,x): _init() return sage_eval(maxima.eval('jacobi_p(%s,%s,%s,x)'%(ZZ(n),a,b)), locals={'x':x}) -def laguerre(n,x): - """ - Return the Laguerre polynomial for integers `n > -1`. - - REFERENCE: - - - [ASHandbook]_ 22.5.16, page 778 and page 789. - EXAMPLES:: - - sage: x = PolynomialRing(QQ, 'x').gen() - sage: laguerre(2,x) - 1/2*x^2 - 2*x + 1 - sage: laguerre(3,x) - -1/6*x^3 + 3/2*x^2 - 3*x + 1 - sage: laguerre(2,2) - -1 - - Check that :trac:`17192` is fixed:: - sage: x = PolynomialRing(QQ, 'x').gen() - sage: laguerre(0,x) - 1 - - sage: laguerre(-1,x) - Traceback (most recent call last): - ... - ValueError: n must be greater than -1, got n = -1 - - sage: laguerre(-7,x) - Traceback (most recent call last): - ... - ValueError: n must be greater than -1, got n = -7 - """ - if not (n > -1): - raise ValueError("n must be greater than -1, got n = {0}".format(n)) - - _init() - return sage_eval(maxima.eval('laguerre(%s,x)'%ZZ(n)), locals={'x':x}) - -def legendre_P(n,x): +def legendre_P(n, x): """ Returns the Legendre polynomial of the first kind. @@ -1432,7 +1358,8 @@ def legendre_P(n,x): _init() return sage_eval(maxima.eval('legendre_p(%s,x)'%ZZ(n)), locals={'x':x}) -def legendre_Q(n,x): + +def legendre_Q(n, x): """ Returns the Legendre function of the second kind. @@ -1453,7 +1380,8 @@ def legendre_Q(n,x): _init() return sage_eval(maxima.eval('legendre_q(%s,x)'%ZZ(n)), locals={'x':x}) -def ultraspherical(n,a,x): + +def ultraspherical(n, a, x): """ Returns the ultraspherical (or Gegenbauer) polynomial for integers `n > -1`. @@ -1478,6 +1406,7 @@ def ultraspherical(n,a,x): 32*t^3 - 12*t Check that :trac:`17192` is fixed:: + sage: x = PolynomialRing(QQ, 'x').gen() sage: ultraspherical(0,1,x) 1 @@ -1499,3 +1428,296 @@ def ultraspherical(n,a,x): return sage_eval(maxima.eval('ultraspherical(%s,%s,x)'%(ZZ(n),a)), locals={'x':x}) gegenbauer = ultraspherical + + +class Func_laguerre(OrthogonalFunction): + """ + REFERENCE: + + - [ASHandbook]_ 22.5.16, page 778 and page 789. + """ + def __init__(self): + r""" + Init method for the Laguerre polynomials. + + EXAMPLES:: + + sage: loads(dumps(laguerre)) + laguerre + """ + OrthogonalFunction.__init__(self, "laguerre", nargs=2, latex_name=r"L", + conversions={'maxima':'laguerre', 'mathematica':'LaguerreL', + 'maple':'LaguerreL'}) + + def _maxima_init_evaled_(self, n, x): + """ + Evaluate the Laguerre polynomial ``self`` with maxima. + + EXAMPLES:: + + sage: var('n, x') + (n, x) + sage: laguerre._maxima_init_evaled_(1,x) + '1-_SAGE_VAR_x' + sage: maxima(laguerre(n, laguerre(n, x))) + laguerre(_SAGE_VAR_n,laguerre(_SAGE_VAR_n,_SAGE_VAR_x)) + """ + return maxima.eval('laguerre({0},{1})'.format(n._maxima_init_(), x._maxima_init_())) + + def _eval_(self, n, x, *args, **kwds): + r""" + Return an evaluation of this Laguerre polynomial expression. + + EXAMPLES:: + + sage: x = PolynomialRing(QQ, 'x').gen() + sage: laguerre(2,x) + 1/2*x^2 - 2*x + 1 + sage: laguerre(3,x) + -1/6*x^3 + 3/2*x^2 - 3*x + 1 + sage: laguerre(2,2) + -1 + sage: laguerre(-1, x) + e^x + sage: laguerre(-6, x) + 1/120*(x^5 + 25*x^4 + 200*x^3 + 600*x^2 + 600*x + 120)*e^x + sage: laguerre(-9,2) + 66769/315*e^2 + """ + from sage.rings.integer import Integer + from sage.functions.log import exp + ret = self._eval_special_values_(n, x) + if ret is not None: + return ret + if isinstance(n, (Integer, int)): + if n >= 0 and not hasattr(x, 'prec'): + return self._pol_laguerre(n, x) + elif n < 0: + return exp(x)*laguerre(-n-1, -x) + + def _eval_special_values_(self, n, x): + """ + Special values known. + + EXAMPLES:: + + sage: laguerre(0, 0) + 1 + sage: laguerre(1, x) + -x + 1 + """ + if n == 0 or x == 0: + return ZZ(1) + if n == 1: + return ZZ(1) - x + + def _pol_laguerre(self, n, x): + """ + Fast creation of Laguerre polynomial. + + EXAMPLES:: + + sage: laguerre(3,sin(x)) + -1/6*sin(x)^3 + 3/2*sin(x)^2 - 3*sin(x) + 1 + sage: R. = PolynomialRing(QQ, 'x') + sage: laguerre(4,x) + 1/24*x^4 - 2/3*x^3 + 3*x^2 - 4*x + 1 + sage: laguerre(4,x+1) + 1/24*(x + 1)^4 - 2/3*(x + 1)^3 + 3*(x + 1)^2 - 4*x - 3 + sage: laguerre(10,1+I) + 142511/113400*I + 95867/22680 + """ + if hasattr(x, 'pyobject'): + try: + x = x.pyobject() + except TypeError: + pass + return SR(sum([binomial(n,k)*(-1)**k/factorial(k)*x**k for k in range(n+1)])) + + def _evalf_(self, n, x, **kwds): + """ + Return the evaluation of `laguerre(n,x)` with floating point `x`. + + EXAMPLES:: + + sage: laguerre(100,RealField(300)(pi)) + -0.638322077840648311606324... + sage: laguerre(10,1.+I) + 4.22694003527337 + 1.25671075837743*I + sage: laguerre(-9, 2.) + 1566.22186244286 + """ + the_parent = kwds.get('parent', None) + if the_parent is None: + the_parent = parent(x) + import mpmath + from sage.libs.mpmath.all import call as mpcall + if n<0: + # work around mpmath issue 307 + from sage.functions.log import exp + return exp(x) * mpcall(mpmath.laguerre, -n-1, 0, -x, parent=the_parent) + else: + return mpcall(mpmath.laguerre, n, 0, x, parent=the_parent) + + def _derivative_(self, n, x, *args,**kwds): + """ + Return the derivative of `laguerre(n,x)`. + + EXAMPLES:: + + sage: n=var('n') + sage: diff(laguerre(n,x), x) + -gen_laguerre(n - 1, 1, x) + + TESTS:: + + sage: diff(laguerre(x,x)) + Traceback (most recent call last): + ... + NotImplementedError: Derivative w.r.t. to the index is not supported. + """ + diff_param = kwds['diff_param'] + if diff_param == 0: + raise NotImplementedError("Derivative w.r.t. to the index is not supported.") + else: + return -gen_laguerre(n-1,1,x) + +laguerre = Func_laguerre() + +class Func_gen_laguerre(OrthogonalFunction): + """ + REFERENCE: + + - [ASHandbook]_ 22.5.16, page 778 and page 789. + """ + def __init__(self): + r""" + Init method for the Laguerre polynomials. + + EXAMPLES:: + + sage: loads(dumps(gen_laguerre)) + gen_laguerre + """ + OrthogonalFunction.__init__(self, "gen_laguerre", nargs=3, latex_name=r"L", + conversions={'maxima':'gen_laguerre', 'mathematica':'LaguerreL', + 'maple':'LaguerreL'}) + + def _maxima_init_evaled_(self, n, a, x): + """ + Evaluate the Laguerre polynomial ``self`` with maxima. + + EXAMPLES:: + + sage: a,n,x = var('a, n, x') + sage: gen_laguerre._maxima_init_evaled_(1,2,x) + '3*(1-_SAGE_VAR_x/3)' + sage: maxima(gen_laguerre(n, a, gen_laguerre(n, a, x))) + gen_laguerre(_SAGE_VAR_n,_SAGE_VAR_a,gen_laguerre(_SAGE_VAR_n,_SAGE_VAR_a,_SAGE_VAR_x)) + """ + return maxima.eval('gen_laguerre({0},{1},{2})'.format(n._maxima_init_(), a._maxima_init_(), x._maxima_init_())) + + def _eval_(self, n, a, x, *args, **kwds): + r""" + Return an evaluation of this Laguerre polynomial expression. + + EXAMPLES:: + + sage: gen_laguerre(2, 1, x) + 1/2*x^2 - 3*x + 3 + sage: gen_laguerre(2, 1/2, x) + 1/2*x^2 - 5/2*x + 15/8 + sage: gen_laguerre(2, -1/2, x) + 1/2*x^2 - 3/2*x + 3/8 + sage: gen_laguerre(2, 0, x) + 1/2*x^2 - 2*x + 1 + sage: gen_laguerre(3, 0, x) + -1/6*x^3 + 3/2*x^2 - 3*x + 1 + """ + from sage.rings.integer import Integer + ret = self._eval_special_values_(n, a, x) + if ret is not None: + return ret + if isinstance(n, Integer): + if n >= 0 and not hasattr(x, 'prec'): + return self._pol_gen_laguerre(n, a, x) + + def _eval_special_values_(self, n, a, x): + """ + Special values known. + + EXAMPLES:: + + sage: gen_laguerre(0, 1, pi) + 1 + sage: gen_laguerre(1, 2, x) + -x + 3 + sage: gen_laguerre(3, 4, 0) + 35 + """ + if n == 0: + return ZZ(1) + if n == 1: + return ZZ(1) + a - x + if a == 0: + return laguerre(n, x) + if x == 0: + from sage.rings.arith import binomial + return binomial(n+a, n) + + def _pol_gen_laguerre(self, n, a, x): + """ + EXAMPLES:: + + sage: gen_laguerre(3, 1/2, sin(x)) + -1/6*sin(x)^3 + 7/4*sin(x)^2 - 35/8*sin(x) + 35/16 + sage: R. = PolynomialRing(QQ, 'x') + sage: gen_laguerre(4, -1/2, x) + 1/24*x^4 - 7/12*x^3 + 35/16*x^2 - 35/16*x + 35/128 + sage: gen_laguerre(4, -1/2, x+1) + 1/24*(x + 1)^4 - 7/12*(x + 1)^3 + 35/16*(x + 1)^2 - 35/16*x - 245/128 + sage: gen_laguerre(10, 1, 1+I) + 25189/2100*I + 11792/2835 + """ + return sum([binomial(n+a,n-k)*(-1)**k/factorial(k)*x**k for k in xrange(n+1)]) + + def _evalf_(self, n, a, x, **kwds): + """ + EXAMPLES:: + + sage: gen_laguerre(100,1,RealField(300)(pi)) + -0.89430788373354541911... + sage: gen_laguerre(10,1/2,1.+I) + 5.34469635574906 + 5.23754057922902*I + """ + the_parent = kwds.get('parent', None) + if the_parent is None: + the_parent = parent(x) + import mpmath + from sage.libs.mpmath.all import call as mpcall + return mpcall(mpmath.laguerre, n, a, x, parent=the_parent) + + def _derivative_(self, n, a, x, *args,**kwds): + """ + Return the derivative of `gen_laguerre(n,a,x)`. + + EXAMPLES:: + + sage: (a,n)=var('a,n') + sage: diff(gen_laguerre(n,a,x), x) + -gen_laguerre(n - 1, a + 1, x) + + TESTS:: + + sage: diff(gen_laguerre(n,a,x), n) + Traceback (most recent call last): + ... + NotImplementedError: Derivative w.r.t. to the index is not supported. + """ + diff_param = kwds['diff_param'] + if diff_param == 0: + raise NotImplementedError("Derivative w.r.t. to the index is not supported.") + else: + return -gen_laguerre(n - 1, a + 1, x) + +gen_laguerre = Func_gen_laguerre() diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index a405d4202ad..8cb74664a95 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -283,6 +283,47 @@ def __init__(self): sage: loads(dumps(abs(x))) abs(x) + + TESTS: + + Check that :trac:`12588` is fixed:: + + sage: abs(pi*I) + pi + sage: abs(pi*I*catalan) + catalan*pi + sage: abs(pi*catalan*x) + catalan*pi*abs(x) + sage: abs(pi*I*catalan*x) + catalan*pi*abs(x) + sage: abs(1.0j*pi) + 1.00000000000000*pi + sage: abs(I*x) + abs(x) + sage: abs(I*pi) + pi + sage: abs(I*log(2)) + log(2) + sage: abs(I*e^5) + e^5 + sage: abs(log(1/2)) + -log(1/2) + sage: abs(log(3/2)) + log(3/2) + sage: abs(log(1/2)*log(1/3)) + log(1/2)*log(1/3) + sage: abs(log(1/2)*log(1/3)*log(1/4)) + -log(1/2)*log(1/3)*log(1/4) + sage: abs(log(1/2)*log(1/3)*log(1/4)*i) + -log(1/2)*log(1/3)*log(1/4) + sage: abs(log(x)) + abs(log(x)) + sage: abs(zeta(I)) + abs(zeta(I)) + sage: abs(e^2*x) + abs(x)*e^2 + sage: abs((pi+e)*x) + (pi + e)*abs(x) """ GinacFunction.__init__(self, "abs", latex_name=r"\mathrm{abs}", conversions=dict(sympy='Abs')) @@ -400,6 +441,8 @@ def __call__(self, x, maximum_bits=20000): 100000000000000000000000000000000000000000000000000 sage: ceil(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: ceil((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -2 """ try: return x.ceil() @@ -412,39 +455,29 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.ceil(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the ceiling at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. + bits = 53 + while bits < maximum_bits: + try: + x_interval = RealIntervalField(bits)(x) + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_ceil() + except ValueError: + bits *= 2 + try: - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + return ceil(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass - while upper_ceil != lower_ceil and bits < maximum_bits: - bits += 100 - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + raise ValueError("computing ceil(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) - if bits < maximum_bits: - return lower_ceil - else: - try: - return ceil(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its ceiling"%(x, maximum_bits)) - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) def _eval_(self, x): """ @@ -571,6 +604,8 @@ def __call__(self, x, maximum_bits=20000): 99999999999999999999999999999999999999999999999999 sage: floor(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: floor((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -3 """ try: return x.floor() @@ -583,40 +618,27 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.floor(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the floor at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. bits = 53 - try: - x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() - - while upper_floor != lower_floor and bits < maximum_bits: - bits += 100 + while bits < maximum_bits: + try: x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_floor() + except ValueError: + bits *= 2 - if bits < maximum_bits: - return lower_floor - else: - try: - return floor(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its floor"%(x, maximum_bits)) - - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) + try: + return floor(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass + + raise ValueError("computing floor(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) def _eval_(self, x): """ diff --git a/src/sage/functions/piecewise.py b/src/sage/functions/piecewise.py index 28bcf977c93..d417ab40077 100644 --- a/src/sage/functions/piecewise.py +++ b/src/sage/functions/piecewise.py @@ -491,7 +491,7 @@ def trapezoid(self,N): sage: Q = tf.plot(rgbcolor=(0.7,0.6,0.6), plot_points=40) sage: L = add([line([[a,0],[a,f(a)]],rgbcolor=(0.7,0.6,0.6)) for (a,b),f in tf.list()]) sage: P+Q+L - Graphics object consisting of 14 graphics primitives + Graphics object consisting of 13 graphics primitives TESTS: diff --git a/src/sage/functions/special.py b/src/sage/functions/special.py index 8af6b566954..d7f6fbb868f 100644 --- a/src/sage/functions/special.py +++ b/src/sage/functions/special.py @@ -1,5 +1,5 @@ r""" -Special Functions +Miscellaneous Special Functions AUTHORS: diff --git a/src/sage/functions/transcendental.py b/src/sage/functions/transcendental.py index 24c888c73b2..6f1327c6e1f 100644 --- a/src/sage/functions/transcendental.py +++ b/src/sage/functions/transcendental.py @@ -1,5 +1,5 @@ """ -Transcendental Functions +Number-Theoretic Functions """ #***************************************************************************** diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index 5cae8414152..a5a75454b47 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -39,6 +39,43 @@ def __init__(self): sage: sin(complex(1,1)) # rel tol 1e-15 (1.2984575814159773+0.6349639147847361j) + sage: sin(pi/5) + 1/4*sqrt(-2*sqrt(5) + 10) + sage: sin(pi/8) + 1/2*sqrt(-sqrt(2) + 2) + sage: sin(pi/24) + 1/4*sqrt(-2*sqrt(6) - 2*sqrt(2) + 8) + sage: sin(pi/30) + -1/8*sqrt(5) + 1/4*sqrt(-3/2*sqrt(5) + 15/2) - 1/8 + sage: cos(pi/8) + 1/2*sqrt(sqrt(2) + 2) + sage: cos(pi/10) + 1/2*sqrt(1/2*sqrt(5) + 5/2) + sage: cos(pi/12) + 1/12*sqrt(6)*(sqrt(3) + 3) + sage: cos(pi/15) + 1/8*sqrt(5) + 1/4*sqrt(3/2*sqrt(5) + 15/2) - 1/8 + sage: cos(pi/24) + 1/4*sqrt(2*sqrt(6) + 2*sqrt(2) + 8) + sage: tan(pi/5) + sqrt(-2*sqrt(5) + 5) + sage: tan(pi/8) + sqrt(2) - 1 + sage: tan(pi/10) + sqrt(-2/5*sqrt(5) + 1) + sage: tan(pi/16) + -sqrt(2) + sqrt(2*sqrt(2) + 4) - 1 + sage: tan(pi/20) + sqrt(5) - 1/2*sqrt(8*sqrt(5) + 20) + 1 + sage: tan(pi/24) + sqrt(6) - sqrt(3) + sqrt(2) - 2 + + sage: all(sin(rat*pi).n(200)-sin(rat*pi,hold=True).n(200) < 1e-30 for rat in [1/5,2/5,1/30,7/30,11/30,13/30,1/8,3/8,1/24,5/24,7/24,11/24]) + True + sage: all(cos(rat*pi).n(200)-cos(rat*pi,hold=True).n(200) < 1e-30 for rat in [1/10,3/10,1/12,5/12,1/15,2/15,4/15,7/15,1/8,3/8,1/24,5/24,11/24]) + True + sage: all(tan(rat*pi).n(200)-tan(rat*pi,hold=True).n(200) < 1e-30 for rat in [1/5,2/5,1/10,3/10,1/20,3/20,7/20,9/20,1/8,3/8,1/16,3/16,5/16,7/16,1/24,5/24,7/24,11/24]) + True """ GinacFunction.__init__(self, "sin", latex_name=r"\sin", conversions=dict(maxima='sin',mathematica='Sin')) diff --git a/src/sage/functions/wigner.py b/src/sage/functions/wigner.py index 04fa72830de..a53540323ae 100644 --- a/src/sage/functions/wigner.py +++ b/src/sage/functions/wigner.py @@ -647,6 +647,7 @@ def gaunt(l_1, l_2, l_3, m_1, m_2, m_3, prec=None): - invariant under any permutation of the columns .. math:: + Y(j_1,j_2,j_3,m_1,m_2,m_3) =Y(j_3,j_1,j_2,m_3,m_1,m_2) =Y(j_2,j_3,j_1,m_2,m_3,m_1) @@ -657,6 +658,7 @@ def gaunt(l_1, l_2, l_3, m_1, m_2, m_3, prec=None): - invariant under space inflection, i.e. .. math:: + Y(j_1,j_2,j_3,m_1,m_2,m_3) =Y(j_1,j_2,j_3,-m_1,-m_2,-m_3) diff --git a/src/sage/game_theory/catalog_normal_form_games.py b/src/sage/game_theory/catalog_normal_form_games.py index 3a2d2748774..dd0e91ebd55 100644 --- a/src/sage/game_theory/catalog_normal_form_games.py +++ b/src/sage/game_theory/catalog_normal_form_games.py @@ -904,7 +904,7 @@ def TravellersDilemma(max_value=10): ....: (3, 4): [4, 8], (0, 2): [6, 10], (8, 4): [4, 0]} sage: g == d True - sage: g.obtain_nash() # optional - lrs + sage: g.obtain_nash() # optional - lrslib [[(0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 0, 1)]] Note that this command can be used to create travellers dilemma for a diff --git a/src/sage/game_theory/gambit_docs.py b/src/sage/game_theory/gambit_docs.py index b8da63740df..a8f059dd340 100644 --- a/src/sage/game_theory/gambit_docs.py +++ b/src/sage/game_theory/gambit_docs.py @@ -6,14 +6,14 @@ To install gambit as an optional package run (from root of Sage):: - $ ./sage -i gambit + $ sage -i gambit The `python API documentation for gambit `_ shows various examples that can be run easily in IPython. To run the IPython packaged with Sage run (from root of Sage):: - $ ./sage -ipython + $ ./sage --ipython Here is an example that constructs the Prisoner's Dilemma:: diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index f769179aeb3..827363f716c 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -204,16 +204,16 @@ When obtaining Nash equilibrium there are 3 algorithms currently available: * ``'lrs'``: Reverse search vertex enumeration for 2 player games. This - algorithm uses the optional 'lrslib' package. To install it type ``sage -i - lrslib`` at the command line. For more information see [A2000]_. + algorithm uses the optional 'lrslib' package. To install it, type + ``sage -i lrslib`` in the shell. For more information, see [A2000]_. * ``'LCP'``: Linear complementarity program algorithm for 2 player games. This algorithm uses the open source game theory package: `Gambit `_ [MMAT2014]_. At present this is the only gambit algorithm available in sage but further development will hope to implement more algorithms - (in particular for games with more than 2 players). To install it - type ``sage -i gambit`` at the command line. + (in particular for games with more than 2 players). To install it, + type ``sage -i gambit`` in the shell. * ``'enumeration'``: Support enumeration for 2 player games. This algorithm is hard coded in Sage and checks through all potential @@ -452,6 +452,74 @@ to 2. The equilibrium strategy is thus for both players to state that the value of their suitcase is 2. +Several standard Normal Form Games have also been implemented. +For more information on how to access these, see: +:mod:`Game Theory Catalog`. +Included is information on the situation each Game models. +For example:: + + sage: g = game_theory.normal_form_games.PrisonersDilemma() + sage: g + Prisoners dilemma - Normal Form Game with the following utilities: ... + sage: d = {(0, 1): [-5, 0], (1, 0): [0, -5], + ....: (0, 0): [-2, -2], (1, 1): [-4, -4]} + sage: g == d + True + sage: g.obtain_nash() + [[(0, 1), (0, 1)]] + +We can easily obtain the best response for a player to a given strategy. In +this example we obtain the best responses for Player 1, when Player 2 uses two +different strategies:: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((1/2, 1/2), player=0) + [0, 1, 2] + sage: g.best_responses((3/4, 1/4), player=0) + [0] + +Here we do the same for player 2:: + + sage: g.best_responses((4/5, 1/5, 0), player=1) + [0, 1] + +We see that for the game `Rock-Paper-Scissors-Lizard-Spock +`_ any pure strategy has two best +responses:: + + sage: g = game_theory.normal_form_games.RPSLS() + sage: A, B = g.payoff_matrices() + sage: A, B + ( + [ 0 -1 1 1 -1] [ 0 1 -1 -1 1] + [ 1 0 -1 -1 1] [-1 0 1 1 -1] + [-1 1 0 1 -1] [ 1 -1 0 -1 1] + [-1 1 -1 0 1] [ 1 -1 1 0 -1] + [ 1 -1 1 -1 0], [-1 1 -1 1 0] + ) + sage: g.best_responses((1, 0, 0, 0, 0), player=0) + [1, 4] + sage: g.best_responses((0, 1, 0, 0, 0), player=0) + [2, 3] + sage: g.best_responses((0, 0, 1, 0, 0), player=0) + [0, 4] + sage: g.best_responses((0, 0, 0, 1, 0), player=0) + [0, 2] + sage: g.best_responses((0, 0, 0, 0, 1), player=0) + [1, 3] + sage: g.best_responses((1, 0, 0, 0, 0), player=1) + [1, 4] + sage: g.best_responses((0, 1, 0, 0, 0), player=1) + [2, 3] + sage: g.best_responses((0, 0, 1, 0, 0), player=1) + [0, 4] + sage: g.best_responses((0, 0, 0, 1, 0), player=1) + [0, 2] + sage: g.best_responses((0, 0, 0, 0, 1), player=1) + [1, 3] + Note that degenerate games can cause problems for most algorithms. The following example in fact has an infinite quantity of equilibria which is evidenced by the various algorithms returning different solutions:: @@ -468,6 +536,11 @@ sage: degenerate_game.obtain_nash(algorithm='enumeration') [[(0, 1/3, 2/3), (1/3, 2/3)], [(1, 0, 0), (1, 0)]] +We can check the cause of this by using ``is_degenerate()``:: + + sage: degenerate_game.is_degenerate() + True + Note the 'negative' `-0.0` output by gambit. This is due to the numerical nature of the algorithm used. @@ -491,22 +564,6 @@ A good description of degenerate games can be found in [NN2007]_. -Several standard Normal Form Games have also been implemented. -For more information on how to access these, see: -:mod:`Game Theory Catalog`. -Included is information on the situation each Game models. -For example:: - - sage: g = game_theory.normal_form_games.PrisonersDilemma() - sage: g - Prisoners dilemma - Normal Form Game with the following utilities: ... - sage: d = {(0, 1): [-5, 0], (1, 0): [0, -5], - ....: (0, 0): [-2, -2], (1, 1): [-4, -4]} - sage: g == d - True - sage: g.obtain_nash() - [[(0, 1), (0, 1)]] - REFERENCES: .. [N1950] John Nash. @@ -549,7 +606,6 @@ from collections import MutableMapping from itertools import product from parser import Parser -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.latex import latex from sage.misc.misc import powerset from sage.rings.all import QQ @@ -915,9 +971,9 @@ def payoff_matrices(self): obtain payoff matrices:: sage: g = NormalFormGame() - sage: g.add_player(2) # Adding first player with 2 strategies - sage: g.add_player(2) # Adding second player with 2 strategies - sage: g.add_player(2) # Adding third player with 2 strategies + sage: g.add_player(2) # adding first player with 2 strategies + sage: g.add_player(2) # adding second player with 2 strategies + sage: g.add_player(2) # adding third player with 2 strategies sage: g.payoff_matrices() Traceback (most recent call last): ... @@ -951,8 +1007,8 @@ def payoff_matrices(self): m1 = matrix(QQ, self.players[0].num_strategies, self.players[1].num_strategies) m2 = matrix(QQ, self.players[0].num_strategies, self.players[1].num_strategies) for strategy_profile in self.utilities: - m1[strategy_profile] = self[strategy_profile][0] - m2[strategy_profile] = self[strategy_profile][1] + m1[strategy_profile] = self[strategy_profile][0] + m2[strategy_profile] = self[strategy_profile][1] return m1, m2 def add_player(self, num_strategies): @@ -1105,7 +1161,7 @@ def obtain_nash(self, algorithm=False, maximization=True): .. MATH:: - u_1(s_1,\rho_2) = u_2(s_2, \rho_2) + u_1(s_1,\rho_2) = u_1(s_2, \rho_2) for all `s_1, s_2` in the support of `\rho_1`. This corresponds to: @@ -1492,7 +1548,7 @@ def _solve_enumeration(self, maximization=True): powerset(range(player.num_strategies))] for player in self.players] - potential_support_pairs = [pair for pair in CartesianProduct(*potential_supports) if len(pair[0]) == len(pair[1])] + potential_support_pairs = [pair for pair in product(*potential_supports) if len(pair[0]) == len(pair[1])] equilibria = [] for pair in potential_support_pairs: @@ -1500,9 +1556,11 @@ def _solve_enumeration(self, maximization=True): if (self._row_cond_dominance(pair[0], pair[1], M1) # Check if any supports are dominated for col player and self._row_cond_dominance(pair[1], pair[0], M2.transpose())): - result = self._solve_indifference(pair[0], pair[1], M1, M2) - if result: - equilibria.append([tuple(result[0]), tuple(result[1])]) + a = self._solve_indifference(pair[0], pair[1], M2) + b = self._solve_indifference(pair[1], pair[0], M1.transpose()) + if a and b and self._is_NE(a, b, pair[0], pair[1], M1, M2): + equilibria.append([tuple(a), tuple(b)]) + return sorted(equilibria) def _row_cond_dominance(self, p1_sup, p2_sup, matrix): @@ -1540,18 +1598,18 @@ def _row_cond_dominance(self, p1_sup, p2_sup, matrix): return False return True - def _solve_indifference(self, p1_support, p2_support, M1, M2): + def _solve_indifference(self, support1, support2, M): r""" - For a support pair obtains vector pair that ensures indifference - amongst support strategies. + For support1, retrns the strategy with support: support2 that makes the + column player indifferent for the utilities given by M. This is done by building the corresponding linear system. - If `\rho_1, \rho_2` are the supports player 1 and 2 respectively. - Then, indifference implies: + If `\rho_1, \rho_2` are the supports of player 1 and 2 respectively. + Then, indifference for player 1 implies: .. MATH:: - u_1(s_1,\rho_2) = u_2(s_2, \rho_2) + u_1(s_1,\rho_2) = u_1(s_2, \rho_2) for all `s_1, s_2` in the support of `\rho_1`. This corresponds to: @@ -1584,71 +1642,54 @@ def _solve_indifference(self, p1_support, p2_support, M1, M2): sage: A = matrix([[1, 1, 5], [2, 2, 0]]) sage: g = NormalFormGame([A]) - sage: g._solve_indifference((0, 1), (0, 2), A, -A) - [(1/3, 2/3), (5/6, 0, 1/6)] + sage: g._solve_indifference((0, 1), (0, 2), A) + (1/3, 2/3) + sage: g._solve_indifference((0, 2), (0, 1), -A.transpose()) + (5/6, 0, 1/6) When a support pair has a dominated strategy there is no solution to the indifference equation:: - sage: g._solve_indifference((0, 1), (0, 1), A, -A) + sage: g._solve_indifference((0, 1), (0, 1), -A.transpose()) Particular case of a game with 1 strategy for each for each player:: sage: A = matrix([[10]]) sage: g = NormalFormGame([A]) - sage: g._solve_indifference((0,), (0,), A, -A) - [(1), (1)] + sage: g._solve_indifference((0,), (0,), -A.transpose()) + (1) """ - linearsystem1 = matrix(QQ, len(p2_support)+1, self.players[0].num_strategies) - linearsystem2 = matrix(QQ, len(p1_support)+1, self.players[1].num_strategies) + linearsystem = matrix(QQ, len(support2)+1, M.nrows()) # Build linear system for player 1 - for p1_strategy in p1_support: - # Checking particular case of supports of pure strategies - if len(p2_support) == 1: - for p2_strategy in range(self.players[1].num_strategies): - if M2[p1_strategy][p2_support[0]] < \ - M2[p1_strategy][p2_strategy]: - return False - else: - for p2_strategy_pair in range(len(p2_support)): - # Coefficients of linear system that ensure indifference between two consecutive strategies of the support of p1 - linearsystem1[p2_strategy_pair, p1_strategy] = \ - M2[p1_strategy][p2_support[p2_strategy_pair]] -\ - M2[p1_strategy][p2_support[p2_strategy_pair-1]] - linearsystem1[-1, p1_strategy] = 1 # Coefficients of linear system to ensure that vector is probability - - # Build linear system for player 2 - for p2_strategy in p2_support: + for strategy1 in support1: # Checking particular case of supports of pure strategies - if len(p1_support) == 1: - for p1_strategy in range(self.players[0].num_strategies): - if M1[p1_support[0]][p2_strategy] < \ - M1[p1_strategy][p2_strategy]: + if len(support2) == 1: + for strategy2 in range(M.ncols()): + if M[strategy1][support2[0]] < \ + M[strategy1][strategy2]: return False else: - for p1_strategy_pair in range(len(p1_support)): - # Coefficients of linear system that ensure indifference between two consecutive strategies of the support of p1 - linearsystem2[p1_strategy_pair, p2_strategy] = \ - M1[p1_support[p1_strategy_pair]][p2_strategy] -\ - M1[p1_support[p1_strategy_pair-1]][p2_strategy] - linearsystem2[-1, p2_strategy] = 1 # Coefficients of linear system that ensure that vector is probability - + for strategy_pair2 in range(len(support2)): + # Coefficients of linear system that ensure indifference + # between two consecutive strategies of the support + linearsystem[strategy_pair2, strategy1] = \ + M[strategy1][support2[strategy_pair2]] -\ + M[strategy1][support2[strategy_pair2 - 1]] + # Coefficients of linear system that ensure the vector is + # a probability vecotor. ie. sum to 1 + linearsystem[-1, strategy1] = 1 # Create rhs of linear systems - linearsystemrhs1 = vector([0 for i in range(len(p2_support))] + [1]) - linearsystemrhs2 = vector([0 for i in range(len(p1_support))] + [1]) + linearsystem_rhs = vector([0 for i in range(len(support2))] + [1]) # Solve both linear systems try: - a = linearsystem1.solve_right(linearsystemrhs1) - b = linearsystem2.solve_right(linearsystemrhs2) + result = linearsystem.solve_right(linearsystem_rhs) except ValueError: return None - if self._is_NE(a, b, p1_support, p2_support, M1, M2): - return [a, b] - return None + return result def _is_NE(self, a, b, p1_support, p2_support, M1, M2): r""" @@ -1792,6 +1833,418 @@ def _Hrepresentation(self, m1, m2): t += 'end\n' return s, t + def is_degenerate(self, certificate=False): + """ + A function to check whether the game is degenerate or not. + Will return a boolean. + + A two-player game is called nondegenerate if no mixed strategy of + support size `k` has more than `k` pure best responses [NN2007]_. In a + degenerate game, this definition is violated, for example if there + is a pure strategy that has two pure best responses. + + The implementation here transforms the search over mixed strategies to a + search over supports which is a discrete search. A full explanation of + this is given in [CK2015]_. This problem is known to be NP-Hard + [D2009]_. Another possible implementation is via best response + polytopes, see :trac:`18958`. + + The game Rock-Paper-Scissors is an example of a non-degenerate game,:: + + sage: g = game_theory.normal_form_games.RPS() + sage: g.is_degenerate() + False + + whereas `Rock-Paper-Scissors-Lizard-Spock + `_ is degenerate because + for every pure strategy there are two best responses.:: + + sage: g = game_theory.normal_form_games.RPSLS() + sage: g.is_degenerate() + True + + EXAMPLES: + + Here is an example of a degenerate game given in [DGRB2010]_:: + + sage: A = matrix([[3, 3], [2, 5], [0, 6]]) + sage: B = matrix([[3, 3], [2, 6], [3, 1]]) + sage: degenerate_game = NormalFormGame([A,B]) + sage: degenerate_game.is_degenerate() + True + + Here is an example of a degenerate game given in [NN2007]_:: + + sage: A = matrix([[0, 6], [2, 5], [3, 3]]) + sage: B = matrix([[1, 0], [0, 2], [4, 4]]) + sage: d_game = NormalFormGame([A, B]) + sage: d_game.is_degenerate() + True + + Here are some other examples of degenerate games:: + + sage: M = matrix([[2, 1], [1, 1]]) + sage: N = matrix([[1, 1], [1, 2]]) + sage: game = NormalFormGame([M, N]) + sage: game.is_degenerate() + True + + If more information is required, it may be useful to use + ``certificate=True``. This will return a boolean of whether the game is + degenerate or not, and if True; a tuple containing the strategy where + degeneracy was found and the player it belongs to. ``0`` is the row + player and ``1`` is the column player.:: + + sage: M = matrix([[2, 1], [1, 1]]) + sage: N = matrix([[1, 1], [1, 2]]) + sage: g = NormalFormGame([M, N]) + sage: test, certificate = g.is_degenerate(certificate=True) + sage: test, certificate + (True, ((1, 0), 0)) + + Using the output, we see that the opponent has more best responses than + the size of the support of the strategy in question ``(1, 0)``. (We + specify the player as ``(player + 1) % 2`` to ensure that we have the + opponent's index.):: + + sage: g.best_responses(certificate[0], (certificate[1] + 1) % 2) + [0, 1] + + Another example with a mixed strategy causing degeneracy.:: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: test, certificate = g.is_degenerate(certificate=True) + sage: test, certificate + (True, ((1/2, 1/2), 1)) + + Again, we see that the opponent has more best responses than the size of + the support of the strategy in question ``(1/2, 1/2)``.:: + + sage: g.best_responses(certificate[0], (certificate[1] + 1) % 2) + [0, 1, 2] + + Sometimes, the different algorithms for obtaining nash_equilibria don't + agree with each other. This can happen when games are degenerate:: + + sage: a = matrix([[-75, 18, 45, 33], + ....: [42, -8, -77, -18], + ....: [83, 18, 11, 40], + ....: [-10, -38, 76, -9]]) + sage: b = matrix([[62, 64, 87, 51], + ....: [-41, -27, -69, 52], + ....: [-17, 25, -97, -82], + ....: [30, 31, -1, 50]]) + sage: d_game = NormalFormGame([a, b]) + sage: d_game.obtain_nash(algorithm='lrs') # optional - lrslib + [[(0, 0, 1, 0), (0, 1, 0, 0)], + [(17/29, 0, 0, 12/29), (0, 0, 42/73, 31/73)], + [(122/145, 0, 23/145, 0), (0, 1, 0, 0)]] + sage: d_game.obtain_nash(algorithm='LCP') # optional - gambit + [[(0.5862068966, 0.0, 0.0, 0.4137931034), + (0.0, 0.0, 0.5753424658, 0.4246575342)]] + sage: d_game.obtain_nash(algorithm='enumeration') + [[(0, 0, 1, 0), (0, 1, 0, 0)], [(17/29, 0, 0, 12/29), (0, 0, 42/73, 31/73)]] + sage: d_game.is_degenerate() + True + + TESTS:: + + sage: g = NormalFormGame() + sage: g.add_player(3) # Adding first player with 3 strategies + sage: g.add_player(3) # Adding second player with 3 strategies + sage: for key in g: + ....: g[key] = [0, 0] + sage: g.is_degenerate() + True + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.is_degenerate() + True + + sage: A = matrix([[1, -1], [-1, 1]]) + sage: B = matrix([[-1, 1], [1, -1]]) + sage: matching_pennies = NormalFormGame([A, B]) + sage: matching_pennies.is_degenerate() + False + + sage: A = matrix([[2, 5], [0, 4]]) + sage: B = matrix([[2, 0], [5, 4]]) + sage: prisoners_dilemma = NormalFormGame([A, B]) + sage: prisoners_dilemma.is_degenerate() + False + + sage: g = NormalFormGame() + sage: g.add_player(2) + sage: g.add_player(2) + sage: g.add_player(2) + sage: g.is_degenerate() + Traceback (most recent call last): + ... + NotImplementedError: Tests for Degeneracy is not yet implemented for games with more than two players. + + REFERENCES: + + .. [D2009] Du Ye. + *On the Complexity of Deciding Degeneracy in Games* + http://arxiv.org/pdf/0905.3012v1.pdf + (2009) + + .. [DGRB2010] David Avis, Gabriel D. Rosenberg, Rahul Savani, Bernhard von Stengel. + *Enumeration of Nash equilibria for two-player games.* + http://www.maths.lse.ac.uk/personal/stengel/ETissue/ARSvS.pdf (2010) + + .. [AH2002] R. J. Aumann and S. Hart, Elsevier, eds. + *Computing equilibria for two-person games* + http://www.maths.lse.ac.uk/personal/stengel/TEXTE/nashsurvey.pdf + (2002) + + .. [CK2015] J. Campbell and V. Knight. + *On testing degeneracy of bi-matrix games* + http://vknight.org/unpeudemath/code/2015/06/25/on_testing_degeneracy_of_games/ + (2015) + """ + if len(self.players) > 2: + raise NotImplementedError("Tests for Degeneracy is not yet " + "implemented for games with more than " + "two players.") + + d = self._is_degenerate_pure(certificate) + if d: + return d + + M1, M2 = self.payoff_matrices() + potential_supports = [[tuple(support) for support in + powerset(range(player.num_strategies))] + for player in self.players] + + # filter out all supports that are pure or empty + potential_supports = [[i for i in k if len(i) > 1] for k in + potential_supports] + + potential_support_pairs = [pair for pair in + product(*potential_supports) if + len(pair[0]) != len(pair[1])] + + # Sort so that solve small linear systems first + potential_support_pairs.sort(key=lambda x: sum([len(k) for k in x])) + + for pair in potential_support_pairs: + if len(pair[0]) < len(pair[1]): + strat = self._solve_indifference(pair[0], pair[1], M2) + if strat and len(self.best_responses(strat, player=0)) > len(pair[0]): + if certificate: + return True, (strat, 0) + else: + return True + elif len(pair[1]) < len(pair[0]): + strat = self._solve_indifference(pair[1], pair[0], M1.transpose()) + if strat and len(self.best_responses(strat, player=0)) > len(pair[1]): + if certificate: + return True, (strat, 1) + else: + return True + + if certificate: + return False, () + else: + return False + + def best_responses(self, strategy, player): + """ + For a given strategy for a player and the index of the opponent, + computes the payoff for the opponent and returns a list of the indices + of the best responses. Only implemented for two player games + + INPUT: + + - ``strategy`` -- a probability distribution vector + + - ``player`` -- the index of the opponent, ``0`` for the row player, + ``1`` for the column player. + + EXAMPLES:: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + + Now we can obtain the best responses for Player 1, when Player 2 uses + different strategies:: + + sage: g.best_responses((1/2, 1/2), player=0) + [0, 1, 2] + sage: g.best_responses((3/4, 1/4), player=0) + [0] + + To get the best responses for Player 2 we pass the argument :code:`player=1` + + sage: g.best_responses((4/5, 1/5, 0), player=1) + [0, 1] + + sage: A = matrix([[1, 0], [0, 1], [0, 0]]) + sage: B = matrix([[1, 0], [0, 1], [0.7, 0.8]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((0, 1, 0), player=1) + [1] + + sage: A = matrix([[3,3],[2,5],[0,6]]) + sage: B = matrix([[3,3],[2,6],[3,1]]) + sage: degenerate_game = NormalFormGame([A,B]) + sage: degenerate_game.best_responses((1, 0, 0), player=1) + [0, 1] + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((1/3, 1/3, 1/3), player=1) + [1] + + Note that this has only been implemented for 2 player games:: + + sage: g = NormalFormGame() + sage: g.add_player(2) # adding first player with 2 strategies + sage: g.add_player(2) # adding second player with 2 strategies + sage: g.add_player(2) # adding third player with 2 strategies + sage: g.best_responses((1/2, 1/2), player=2) + Traceback (most recent call last): + ... + ValueError: Only available for 2 player games + + If the strategy is not of the correct dimension for the given player + then an error is returned:: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((1/2, 1/2), player=1) + Traceback (most recent call last): + ... + ValueError: Strategy is not of correct dimension + + sage: g.best_responses((1/3, 1/3, 1/3), player=0) + Traceback (most recent call last): + ... + ValueError: Strategy is not of correct dimension + + If the strategy is not a true probability vector then an error is + passed: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((1/3, 1/2, 0), player=1) + Traceback (most recent call last): + ... + ValueError: Strategy is not a probability distribution vector + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((3/2, -1/2), player=0) + Traceback (most recent call last): + ... + ValueError: Strategy is not a probability distribution vector + + If the player specified is not `0` or `1`, an error is raised:: + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g.best_responses((1/2, 1/2), player='Player1') + Traceback (most recent call last): + ... + ValueError: Player1 is not an index of the oponent, must be 0 or 1 + """ + if len(self.players) != 2: + raise ValueError('Only available for 2 player games') + + if player != 0 and player != 1: + raise ValueError('%s is not an index of the oponent, must be 0 or 1' % player) + + strategy = vector(strategy) + + if sum(strategy) != 1 or min(strategy) < 0: + raise ValueError('Strategy is not a probability distribution vector') + + if player == 0: + payoff_matrix = self.payoff_matrices()[0] + elif player == 1: + payoff_matrix = self.payoff_matrices()[1].transpose() + + if len(strategy) != payoff_matrix.dimensions()[1]: + raise ValueError('Strategy is not of correct dimension') + + payoffs = list(payoff_matrix * strategy) + indices = [i for i, j in enumerate(payoffs) if j == max(payoffs)] + + return indices + + def _is_degenerate_pure(self, certificate=False): + """ + Checks whether a game is degenerate in pure strategies. + + TESTS:: + + sage: A = matrix([[3,3],[2,5],[0,6]]) + sage: B = matrix([[3,3],[2,6],[3,1]]) + sage: degenerate_game = NormalFormGame([A,B]) + sage: degenerate_game._is_degenerate_pure() + True + + sage: A = matrix([[1, 0], [0, 1], [0, 0]]) + sage: B = matrix([[1, 0], [0, 1], [0.7, 0.8]]) + sage: g = NormalFormGame([A, B]) + sage: g._is_degenerate_pure() + False + + sage: A = matrix([[2, 5], [0, 4]]) + sage: B = matrix([[2, 0], [5, 4]]) + sage: prisoners_dilemma = NormalFormGame([A, B]) + sage: prisoners_dilemma._is_degenerate_pure() + False + + sage: A = matrix([[0, -1, 1, 1, -1], + ....: [1, 0, -1, -1, 1], + ....: [-1, 1, 0, 1 , -1], + ....: [-1, 1, -1, 0, 1], + ....: [1, -1, 1, -1, 0]]) + sage: g = NormalFormGame([A]) + sage: g._is_degenerate_pure() + True + + Whilst this game is not degenerate in pure strategies, it is + actually degenerate, but only in mixed strategies. + + sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) + sage: B = matrix([[4, 3], [2, 6], [3, 1]]) + sage: g = NormalFormGame([A, B]) + sage: g._is_degenerate_pure() + False + """ + M1, M2 = self.payoff_matrices() + for i, row in enumerate(M2.rows()): + if list(row).count(max(row)) > 1: + if certificate: + strat = [0 for k in range(M1.nrows())] + strat[i] = 1 + return True, (tuple(strat), 0) + else: + return True + + for j, col in enumerate(M1.columns()): + if list(col).count(max(col)) > 1: + if certificate: + strat = [0 for k in range(M1.ncols())] + strat[j] = 1 + return True, (tuple(strat), 1) + else: + return True + return False + class _Player(): def __init__(self, num_strategies): diff --git a/src/sage/games/hexad.py b/src/sage/games/hexad.py index d0ae2b29652..30911094693 100644 --- a/src/sage/games/hexad.py +++ b/src/sage/games/hexad.py @@ -1,7 +1,7 @@ r""" Hexads in S(5,6,12) -This module completes a 5-element subset of as 12-set X +This module completes a 5-element subset of a 12-set X into a hexad in a Steiner system S(5,6,12) using Curtis and Conway's "kitten method". The labeling is either the "modulo 11" labeling or the "shuffle" labeling. @@ -32,13 +32,13 @@ The corresponding MINIMOG is:: - _________________ - | 6 | 3 | 0 | 9 | - |---|---|---|---| - | 5 | 2 | 7 | 10 | - |---|---|---|---| - | 4 | 1 | 8 | 11 | - |___|____|___|____| + +-----+-----+-----+-----+ + | 6 | 3 | 0 | 9 | + +-----+-----+-----+-----+ + | 5 | 2 | 7 | 10 | + +-----+-----+-----+-----+ + | 4 | 1 | 8 | 11 | + +-----+-----+-----+-----+ which is specified by the global variable "minimog_shuffle". See the docstrings for find_hexad and blackjack_move for @@ -50,21 +50,20 @@ REFERENCES: -R. Curtis, The Steiner system `S(5,6,12)`, the Mathieu group `M_{12}`, -and the kitten, in *Computational group theory*, ed. M. Atkinson, -Academic Press, 1984. +.. [Cur84] R. Curtis, The Steiner system `S(5,6,12)`, the Mathieu + group `M_{12}`, and the kitten, in *Computational group theory*, + ed. M. Atkinson, Academic Press, 1984. -J. Conway, Hexacode and tetracode - MINIMOG and MOG, in *Computational -group theory*, ed. M. Atkinson, Academic Press, 1984. +.. [Con84] J. Conway, Hexacode and tetracode - MINIMOG and MOG, + in *Computational group theory*, ed. M. Atkinson, Academic Press, 1984. -J. Conway and N. Sloane, *Lexicographic codes: error-correcting codes from -game theory*, IEEE Trans. Infor. Theory 32 (1986) 337-348. +.. [ConSlo86] J. Conway and N. Sloane, *Lexicographic codes: error-correcting + codes from game theory*, IEEE Trans. Infor. Theory 32 (1986) 337-348. -J. Kahane and A. Ryba, The hexad game, *Electronic Journal of Combinatorics*, 8 (2001) -http://www.combinatorics.org/Volume_8/Abstracts/v8i2r11.html +.. [KahRyb01] J. Kahane and A. Ryba, The hexad game, *Electronic Journal of + Combinatorics*, 8 (2001) http://www.combinatorics.org/Volume_8/Abstracts/v8i2r11.html Some details are also online at: http://www.permutationpuzzles.org/hexad/ - """ #***************************************************************************** # Copyright (C) 2005 David Joyner @@ -85,6 +84,7 @@ from sage.calculus.calculus import SR #SR = SymbolicRing() + def view_list(L): """ This provides a printout of the lines, crosses and squares of the MINIMOG, @@ -109,7 +109,6 @@ def view_list(L): [0 0 0] [1 1 0] [1 1 0] - """ MS = MatrixSpace(QQ,3,3) box = [(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)] @@ -119,7 +118,8 @@ def view_list(L): A[x] = 1 return A -def picture_set(A,L): + +def picture_set(A, L): """ This is needed in the find_hexad function below. @@ -131,10 +131,10 @@ def picture_set(A,L): {5, 7, 8, 9, 10} sage: picture_set(M.picture02, M.square[7]) {2, 3, 5, 8} - """ return set([A[x] for x in L]) + class Minimog(): """ This implements the Conway/Curtis minimog idea for describing the Steiner @@ -227,12 +227,13 @@ def __init__(self, type="shuffle"): self.tet7 = MS34_GF3([[0,1,0,0],[0,0,0,1],[1,0,1,0]]) self.tet8 = MS34_GF3([[0,0,1,0],[0,1,0,0],[1,0,0,1]]) self.tet9 = MS34_GF3([[0,0,0,1],[0,0,1,0],[1,1,0,0]]) - self.col = [ self.col1, self.col2, self.col3, self.col4] - self.tet = [ self.tet1, self.tet2, self.tet3, self.tet4, self.tet5, self.tet6, self.tet7, self.tet8, self.tet9] + self.col = [self.col1, self.col2, self.col3, self.col4] + self.tet = [self.tet1, self.tet2, self.tet3, self.tet4, + self.tet5, self.tet6, self.tet7, self.tet8, self.tet9] # return picture00,picture02,picture21,line,cross,square,col,tet def __repr__(self): - return "Minimog of type %s"%self.type + return "Minimog of type %s" % self.type def __str__(self): """ @@ -247,7 +248,8 @@ def __str__(self): [ 4 1 6 7] """ - return "Minimog of type %s associated to\n %s"%(self.type,self.minimog) + return "Minimog of type %s associated to\n %s" % (self.type, + self.minimog) def _latex_(self): """ @@ -265,7 +267,8 @@ def _latex_(self): \end{array}\right)$ """ from sage.misc.latex import latex - return "Minimog of type %s associated to\n $%s$"%(self.type, latex(self.minimog)) + return "Minimog of type %s associated to\n $%s$" % (self.type, + latex(self.minimog)) def print_kitten(self): """ @@ -310,7 +313,7 @@ def print_kitten(self): Kitten8 = [' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '] Kitten9 = [str(MINIMOG[0][0]),' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',str(MINIMOG[2][1])] Kitten = [Kitten1, Kitten2, Kitten3, Kitten4, Kitten5, Kitten6, Kitten7, Kitten8, Kitten9] - kitten = "".join(Kitten1)+"\n"+"".join(Kitten2)+"\n"+"".join(Kitten3)+"\n"+"".join(Kitten4)+"\n"+"".join(Kitten5)+"\n"+"".join(Kitten6)+"\n"+"".join(Kitten7)+"\n"+"".join(Kitten8)+"\n"+"".join(Kitten9) + kitten = "\n".join("".join(u) for u in Kitten) print kitten def find_hexad0(self, pts): @@ -340,19 +343,19 @@ def find_hexad0(self, pts): L = set(pts) H = set([MINIMOG[0][2], MINIMOG[2][1], MINIMOG[0][0]]) for i in range(12): - if L <= picture_set(self.picture02,self.line[i]): - WHAT = ["line "+str(i),"picture "+str(1)] - H = H | picture_set(self.picture02,self.line[i]) - return list(H),WHAT - if L <= picture_set(self.picture21,self.line[i]): - WHAT = ["line "+str(i),"picture "+str(0)] - H = H | picture_set(self.picture21,self.line[i]) - return list(H),WHAT - if L <= picture_set(self.picture00,self.line[i]): - WHAT = ["line "+str(i),"picture "+str(6)] - H = H | picture_set(self.picture00,self.line[i]) - return list(H),WHAT - return [],[] + if L <= picture_set(self.picture02, self.line[i]): + WHAT = ["line " + str(i), "picture " + str(1)] + H = H | picture_set(self.picture02, self.line[i]) + return list(H), WHAT + if L <= picture_set(self.picture21, self.line[i]): + WHAT = ["line " + str(i), "picture " + str(0)] + H = H | picture_set(self.picture21, self.line[i]) + return list(H), WHAT + if L <= picture_set(self.picture00, self.line[i]): + WHAT = ["line " + str(i), "picture " + str(6)] + H = H | picture_set(self.picture00, self.line[i]) + return list(H), WHAT + return [], [] def find_hexad1(self, pts): """ @@ -373,26 +376,26 @@ def find_hexad1(self, pts): sage: M = Minimog(type="shuffle") sage: M.find_hexad1(set([2,3,4,5,8])) ([2, 3, 4, 5, 8, 11], ['lines (1, 2)', 'picture 1']) - """ H = set(pts) L = set(pts) - linez = [(1,2),(1,3),(2,3),(4,5),(4,6),(5,6),(7,8),(7,9),(8,9),(10,11),(10,12),(11,12)] + linez = [(1, 2), (1, 3), (2, 3), (4, 5), (4, 6), (5, 6), + (7, 8), (7, 9), (8, 9), (10, 11), (10, 12), (11, 12)] for x in linez: - x1 = int(x[0]-1) - x2 = int(x[1]-1) ## (recall | is union) - if L <= (picture_set(self.picture02,self.line[x1]) | picture_set(self.picture02,self.line[x2])): - WHAT = ["lines "+str(x),"picture "+str(1)] - H = picture_set(self.picture02,self.line[x1]) | picture_set(self.picture02,self.line[x2]) - return list(H),WHAT - if L <= (picture_set(self.picture21,self.line[x1]) | picture_set(self.picture21,self.line[x2])): - WHAT = ["lines "+str(x),"picture "+str(0)] - H = picture_set(self.picture21,self.line[x1]) | picture_set(self.picture21,self.line[x2]) - return list(H),WHAT - if L <= (picture_set(self.picture00,self.line[x1]) | picture_set(self.picture00,self.line[x2])): - WHAT = ["lines "+str(x),"picture "+str(6)] - H = picture_set(self.picture00,self.line[x1]) | picture_set(self.picture00,self.line[x2]) - return list(H),WHAT + x1 = int(x[0] - 1) + x2 = int(x[1] - 1) # (recall | is union) + if L <= (picture_set(self.picture02, self.line[x1]) | picture_set(self.picture02,self.line[x2])): + WHAT = ["lines " + str(x), "picture " + str(1)] + H = picture_set(self.picture02, self.line[x1]) | picture_set(self.picture02,self.line[x2]) + return list(H), WHAT + if L <= (picture_set(self.picture21, self.line[x1]) | picture_set(self.picture21,self.line[x2])): + WHAT = ["lines " + str(x), "picture " + str(0)] + H = picture_set(self.picture21, self.line[x1]) | picture_set(self.picture21,self.line[x2]) + return list(H), WHAT + if L <= (picture_set(self.picture00, self.line[x1]) | picture_set(self.picture00,self.line[x2])): + WHAT = ["lines " + str(x), "picture " + str(6)] + H = picture_set(self.picture00, self.line[x1]) | picture_set(self.picture00,self.line[x2]) + return list(H), WHAT return [],[] def find_hexad2(self, pts, x0): @@ -426,17 +429,17 @@ def find_hexad2(self, pts, x0): H = set([x0]) for i in range(18): if (x0 == MINIMOG[2][1] and L <= picture_set(self.picture02,self.cross[i])): - WHAT = ["cross "+str(i),"picture "+str(1)] - H = H | picture_set(self.picture02,self.cross[i]) - return list(H),WHAT + WHAT = ["cross " + str(i), "picture " + str(1)] + H = H | picture_set(self.picture02, self.cross[i]) + return list(H), WHAT if (x0 == MINIMOG[0][2] and L <= picture_set(self.picture21,self.cross[i])): - WHAT = ["cross "+str(i),"picture "+str(MINIMOG[0][2])] - H = H | picture_set(self.picture21,self.cross[i]) - return list(H),WHAT + WHAT = ["cross " + str(i), "picture " + str(MINIMOG[0][2])] + H = H | picture_set(self.picture21, self.cross[i]) + return list(H), WHAT if (x0 == MINIMOG[0][0] and L <= picture_set(self.picture00,self.cross[i])): - WHAT = ["cross "+str(i),"picture "+str(6)] - H = H | picture_set(self.picture00,self.cross[i]) - return list(H),WHAT + WHAT = ["cross " + str(i), "picture " + str(6)] + H = H | picture_set(self.picture00, self.cross[i]) + return list(H), WHAT return [],[] def find_hexad3(self, pts, x0, x1): @@ -462,21 +465,21 @@ def find_hexad3(self, pts, x0, x1): """ MINIMOG = self.minimog L = set(pts) - H = set([x0,x1]) + H = set([x0, x1]) for i in range(18): - if (not (MINIMOG[0][2] in H) and L <= picture_set(self.picture21,self.square[i])): - WHAT = ["square "+str(i),"picture "+str(MINIMOG[0][2])] - H = H | picture_set(self.picture21,self.square[i]) - return list(H),WHAT - if (not (MINIMOG[2][1] in H) and L <= picture_set(self.picture02,self.square[i])): - WHAT = ["square "+str(i),"picture "+str(MINIMOG[2][1])] - H = H | picture_set(self.picture02,self.square[i]) - return list(H),WHAT - if (not (MINIMOG[0][0] in H) and L <= picture_set(self.picture00,self.square[i])): - WHAT = ["square "+str(i),"picture "+str(MINIMOG[0][0])] - H = H | picture_set(self.picture00,self.square[i]) - return list(H),WHAT - return [],[] + if (not (MINIMOG[0][2] in H) and L <= picture_set(self.picture21, self.square[i])): + WHAT = ["square " + str(i), "picture " + str(MINIMOG[0][2])] + H = H | picture_set(self.picture21, self.square[i]) + return list(H), WHAT + if (not (MINIMOG[2][1] in H) and L <= picture_set(self.picture02, self.square[i])): + WHAT = ["square " + str(i), "picture " + str(MINIMOG[2][1])] + H = H | picture_set(self.picture02, self.square[i]) + return list(H), WHAT + if (not (MINIMOG[0][0] in H) and L <= picture_set(self.picture00, self.square[i])): + WHAT = ["square " + str(i), "picture " + str(MINIMOG[0][0])] + H = H | picture_set(self.picture00, self.square[i]) + return list(H), WHAT + return [], [] def find_hexad(self, pts): r""" @@ -521,89 +524,81 @@ def find_hexad(self, pts): David Joyner (2006-05) - REFERENCES: - - R. Curtis, The Steiner system `S(5,6,12)`, the Mathieu group `M_{12}`, - and the kitten, in *Computational group theory*, ed. M. Atkinson, - Academic Press, 1984. - - J. Conway, Hexacode and tetracode - MINIMOG and MOG, in *Computational - group theory*, ed. M. Atkinson, Academic Press, 1984. - + REFERENCES: [Cur84]_, [Con84]_ """ MINIMOG = self.minimog L = set(pts) LL = L.copy() - ## recall & means intersection + # recall & means intersection L2 = LL & set([MINIMOG[0][2],MINIMOG[2][1],MINIMOG[0][0]]) - if len(L2) == 3: ## must be type 0 (line + pts at infty) - H,WHAT = self.find_hexad0(LL - set([MINIMOG[0][2],MINIMOG[2][1],MINIMOG[0][0]])) - return H,WHAT - if len(L2) == 2: ## type 0 or 3 + if len(L2) == 3: # must be type 0 (line + pts at infty) + H, WHAT = self.find_hexad0(LL - set([MINIMOG[0][2],MINIMOG[2][1],MINIMOG[0][0]])) + return H, WHAT + if len(L2) == 2: # type 0 or 3 if (MINIMOG[0][2] in LL and MINIMOG[2][1] in LL): - H,WHAT = self.find_hexad3(LL - set([MINIMOG[0][2],MINIMOG[2][1]]),MINIMOG[0][2],MINIMOG[2][1]) + H, WHAT = self.find_hexad3(LL - set([MINIMOG[0][2],MINIMOG[2][1]]),MINIMOG[0][2],MINIMOG[2][1]) if H != []: # must be type 3 - return list(H),WHAT - if H == []: ## could be type 0 - H,WHAT = self.find_hexad0(LL - L2) + return list(H), WHAT + if H == []: # could be type 0 + H, WHAT = self.find_hexad0(LL - L2) if H != []: # must be type 0 - return list(H),WHAT + return list(H), WHAT if (MINIMOG[2][1] in LL and MINIMOG[0][0] in LL): - H,WHAT = self.find_hexad3(LL - set([MINIMOG[2][1],MINIMOG[0][0]]),MINIMOG[2][1],MINIMOG[0][0]) + H, WHAT = self.find_hexad3(LL - set([MINIMOG[2][1],MINIMOG[0][0]]),MINIMOG[2][1],MINIMOG[0][0]) if H != []: # must be type 3 - return list(H),WHAT - if H == []: ## could be type 0 - H,WHAT = self.find_hexad0(LL - L2) + return list(H), WHAT + if H == []: # could be type 0 + H, WHAT = self.find_hexad0(LL - L2) if H != []: # must be type 0 - return list(H),WHAT + return list(H), WHAT if (MINIMOG[0][2] in LL and MINIMOG[0][0] in LL): - H,WHAT = self.find_hexad3(LL - set([MINIMOG[0][2],MINIMOG[0][0]]),MINIMOG[0][2],MINIMOG[0][0]) + H, WHAT = self.find_hexad3(LL - set([MINIMOG[0][2],MINIMOG[0][0]]),MINIMOG[0][2],MINIMOG[0][0]) if H != []: # must be type 3 - return list(H),WHAT - if H == []: ## could be type 0 - H,WHAT = self.find_hexad0(LL - L2) + return list(H), WHAT + if H == []: # could be type 0 + H, WHAT = self.find_hexad0(LL - L2) if H != []: # must be type 0 - return list(H),WHAT + return list(H), WHAT if len(L2) == 1: - H,WHAT = self.find_hexad2(LL - L2,list(L2)[0]) - if H == []: ## not a cross in picture at infinity + H, WHAT = self.find_hexad2(LL - L2,list(L2)[0]) + if H == []: # not a cross in picture at infinity if list(L2)[0] == MINIMOG[2][1]: - L1 = LL - L2 - H,WHAT = self.find_hexad3(L1,MINIMOG[0][0],MINIMOG[2][1]) - if H != []: - return list(H),WHAT - L1 = LL - L2 - H,WHAT = self.find_hexad3(L1,MINIMOG[0][2],MINIMOG[2][1]) - if H != []: - return list(H),WHAT + L1 = LL - L2 + H, WHAT = self.find_hexad3(L1, MINIMOG[0][0], MINIMOG[2][1]) + if H != []: + return list(H), WHAT + L1 = LL - L2 + H, WHAT = self.find_hexad3(L1, MINIMOG[0][2], MINIMOG[2][1]) + if H != []: + return list(H), WHAT if list(L2)[0] == MINIMOG[0][0]: - L1 = (LL - L2) - H,WHAT = self.find_hexad3(L1,MINIMOG[0][0],MINIMOG[2][1]) - if H != []: - return list(H),WHAT - L1 = (LL - L2) - H,WHAT = self.find_hexad3(L1,MINIMOG[0][0],MINIMOG[0][2]) - if H != []: - return list(H),WHAT + L1 = (LL - L2) + H, WHAT = self.find_hexad3(L1, MINIMOG[0][0], MINIMOG[2][1]) + if H != []: + return list(H), WHAT + L1 = (LL - L2) + H, WHAT = self.find_hexad3(L1, MINIMOG[0][0], MINIMOG[0][2]) + if H != []: + return list(H), WHAT if list(L2)[0] == MINIMOG[0][2]: - L1 = (LL - L2) - H,WHAT = self.find_hexad3(L1,MINIMOG[0][0],MINIMOG[0][2]) - if H != []: - return list(H),WHAT - L1 = (LL - L2) - H,WHAT = self.find_hexad3(L1,MINIMOG[2][1],MINIMOG[0][2]) - if H != []: - return list(H),WHAT - return list(H),WHAT - ## a cross in a pic at infty - if len(L2) == 0: ## L is either a union of 2 lines or a cross + L1 = (LL - L2) + H, WHAT = self.find_hexad3(L1, MINIMOG[0][0], MINIMOG[0][2]) + if H != []: + return list(H), WHAT + L1 = (LL - L2) + H, WHAT = self.find_hexad3(L1, MINIMOG[2][1], MINIMOG[0][2]) + if H != []: + return list(H), WHAT + return list(H), WHAT + # a cross in a pic at infty + if len(L2) == 0: # L is either a union of 2 lines or a cross for i in LL: for j in [MINIMOG[0][2],MINIMOG[2][1],MINIMOG[0][0]]: - H,WHAT = self.find_hexad2(LL - set([i]),j) + H, WHAT = self.find_hexad2(LL - set([i]),j) if (H != [] and i in H): - return list(H),WHAT ## L is in a cross - H,WHAT = self.find_hexad1(LL) ## L is a union of lines - return H,WHAT + return list(H), WHAT # L is in a cross + H, WHAT = self.find_hexad1(LL) # L is a union of lines + return H, WHAT def blackjack_move(self, L0): """ @@ -633,7 +628,6 @@ def blackjack_move(self, L0): Proposition (Ryba, Conway) For this Steiner system, the winning strategy is to choose a move which is a hexad from this system. - EXAMPLES:: sage: M = Minimog(type="modulo11") @@ -643,54 +637,46 @@ def blackjack_move(self, L0): sage: M.blackjack_move([0,2,4,6,7,11]) '4 --> 3. The total went from 30 to 29.' - Is this really a hexad? + Is this really a hexad? :: sage: M.find_hexad([11,2,3,6,7]) ([0, 2, 3, 6, 7, 11], ['square 9', 'picture 1']) - So, yes it is, but here is further confirmation: + So, yes it is, but here is further confirmation:: sage: M.blackjack_move([0,2,3,6,7,11]) This is a hexad. There is no winning move, so make a random legal move. [0, 2, 3, 6, 7, 11] - Now, suppose player 2 replaced the 11 by a 9. Your next move: + Now, suppose player 2 replaced the 11 by a 9. Your next move:: sage: M.blackjack_move([0,2,3,6,7,9]) '7 --> 1. The total went from 27 to 21.' - You have now won. Sage will even tell you so: + You have now won. Sage will even tell you so:: sage: M.blackjack_move([0,2,3,6,1,9]) 'No move possible. Shuffle the deck and redeal.' - AUTHOR:: - David Joyner (2006-05) + AUTHOR: - REFERENCES:: - J. Conway and N. Sloane, "Lexicographic codes: error-correcting codes from - game theory,'' IEEE Trans. Infor. Theory32(1986)337-348. - J. Kahane and A. Ryba, "The hexad game,'' Electronic Journal of Combinatorics, 8 (2001) - http://www.combinatorics.org/Volume_8/Abstracts/v8i2r11.html + David Joyner (2006-05) + REFERENCES: [ConSlo86]_, [KahRyb01]_ """ - MINIMOG = self.minimog total = sum(L0) - if total <22: + if total < 22: return "No move possible. Shuffle the deck and redeal." L = set(L0) for x in L: - h,WHAT = self.find_hexad(L - set([x])) + h, WHAT = self.find_hexad(L - set([x])) if list(L0) == list(h): print " This is a hexad. \n There is no winning move, so make a random legal move." return L0 y = list(set(h) - (L - set([x])))[0] #print x,y,h if y < x: - return str(x) +' --> '+str(y)+". The total went from "+ str(total) +" to "+str(total-x+y)+"." + return str(x) + ' --> ' + str(y) + ". The total went from " + str(total) + " to " + str(total - x + y) + "." print "This is a hexad. \n There is no winning move, so make a random legal move." return L0 - - - diff --git a/src/sage/games/sudoku.py b/src/sage/games/sudoku.py index 84e8a2d221e..118d7e2d462 100644 --- a/src/sage/games/sudoku.py +++ b/src/sage/games/sudoku.py @@ -138,6 +138,7 @@ def __init__(self, puzzle, verify_input = True): Initialize a Sudoku puzzle, determine its size, sanity-check the inputs. TESTS:: + sage: d = Sudoku('1.......2.9.4...5...6...7...5.9.3.......7.......85..4.7.....6...3...9.8...2.....1') sage: d == loads(dumps(d)) True diff --git a/src/sage/geometry/all.py b/src/sage/geometry/all.py index 46bf2c2af0e..a7986cc7e14 100644 --- a/src/sage/geometry/all.py +++ b/src/sage/geometry/all.py @@ -1,7 +1,7 @@ from sage.misc.lazy_import import lazy_import -from cone import Cone +from cone import Cone, random_cone from fan import Fan, FaceFan, NormalFan, Fan2d diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index e62d5119aa0..68fd8ba37cc 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -207,10 +207,10 @@ is_ToricLatticeQuotient from sage.geometry.toric_plotter import ToricPlotter, label_list from sage.graphs.digraph import DiGraph -from sage.matrix.all import matrix +from sage.matrix.all import matrix, MatrixSpace from sage.misc.all import cached_method, flatten, latex from sage.misc.superseded import deprecation -from sage.modules.all import span, vector +from sage.modules.all import span, vector, VectorSpace from sage.rings.all import QQ, RR, ZZ, gcd from sage.structure.all import SageObject, parent from sage.libs.ppl import C_Polyhedron, Generator_System, Constraint_System, \ @@ -907,6 +907,16 @@ def dual_lattice(self): sage: Cone([], ZZ^3).dual_lattice() Ambient free module of rank 3 over the principal ideal domain Integer Ring + + TESTS: + + The dual lattice of the dual lattice of a random cone should be + the original lattice:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8, max_rays=10) + sage: K.dual_lattice().dual() is K.lattice() + True """ try: return self.lattice().dual() @@ -1027,6 +1037,162 @@ def rays(self, *args): """ return self._rays if not args else self._rays(*args) + def codim(self): + r""" + Return the codimension of ``self``. + + The codimension of a collection of rays (of a cone/fan) is the + difference between the dimension of the ambient space and the + dimension of the subspace spanned by those rays (of the cone/fan). + + OUTPUT: + + A nonnegative integer representing the codimension of ``self``. + + .. SEEALSO:: + + :meth:`dim`, :meth:`lattice_dim` + + EXAMPLES: + + The codimension of the nonnegative orthant is zero, since the + span of its generators equals the entire ambient space:: + + sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: K.codim() + 0 + + However, if we remove a ray so that the entire cone is contained + within the `x`-`y` plane, then the resulting cone will have + codimension one, because the `z`-axis is perpendicular to every + element of the cone:: + + sage: K = Cone([(1,0,0), (0,1,0)]) + sage: K.codim() + 1 + + If our cone is all of `\mathbb{R}^{2}`, then its codimension is + zero:: + + sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)]) + sage: K.is_full_space() + True + sage: K.codim() + 0 + + And if the cone is trivial in any space, then its codimension is + equal to the dimension of the ambient space:: + + sage: K = Cone([], lattice=ToricLattice(0)) + sage: K.lattice_dim() + 0 + sage: K.codim() + 0 + + sage: K = Cone([(0,)]) + sage: K.lattice_dim() + 1 + sage: K.codim() + 1 + + sage: K = Cone([(0,0)]) + sage: K.lattice_dim() + 2 + sage: K.codim() + 2 + + TESTS: + + The codimension of a cone should be an integer between zero and + the dimension of the ambient space, inclusive:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim = 8) + sage: c = K.codim() + sage: c in ZZ + True + sage: 0 <= c <= K.lattice_dim() + True + + A solid cone should have codimension zero:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim = 8, solid = True) + sage: K.codim() + 0 + + The codimension of a cone is equal to the lineality of its dual:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim = 8) + sage: K.codim() == K.dual().lineality() + True + """ + return (self.lattice_dim() - self.dim()) + + + def span(self, base_ring=None): + r""" + Return the span of ``self``. + + INPUT: + + - ``base_ring`` -- (default: from lattice) the base ring to use + for the generated module. + + OUTPUT: + + A module spanned by the generators of ``self``. + + EXAMPLES: + + The span of a single ray is a one-dimensional sublattice:: + + sage: K1 = Cone([(1,)]) + sage: K1.span() + Sublattice + sage: K2 = Cone([(1,0)]) + sage: K2.span() + Sublattice + + The span of the nonnegative orthant is the entire ambient lattice:: + + sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)]) + sage: K.span() == K.lattice() + True + + By specifying a ``base_ring``, we can obtain a vector space:: + + sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)]) + sage: K.span(base_ring=QQ) + Vector space of degree 3 and dimension 3 over Rational Field + Basis matrix: + [1 0 0] + [0 1 0] + [0 0 1] + + TESTS: + + We can take the span of the trivial cone:: + + sage: K = Cone([], ToricLattice(0)) + sage: K.span() + Sublattice <> + + The span of a solid cone is the entire ambient space:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6, max_rays=8, solid=True) + sage: K.span().vector_space() == K.lattice().vector_space() + True + """ + L = self.lattice() + + if base_ring is None: + base_ring = L.base_ring() + + return L.span(self, base_ring) + def classify_cone_2d(ray0, ray1, check=True): """ @@ -1892,6 +2058,15 @@ def dual(self): sage: Cone([(1,0),(0,1),(-1,-1)], lattice=N).dual().rays() # whole space Empty collection in 2-d lattice M + + TESTS: + + The dual cone of a (random) dual cone is the original cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8, max_rays=10) + sage: K.dual().dual() is K + True """ if "_dual" not in self.__dict__: rays = list(self.facet_normals()) @@ -2468,6 +2643,7 @@ def facet_normals(self): pass return PointCollection(normals, M) + @cached_method def facet_of(self): r""" Return *cones* of the ambient face lattice having ``self`` as a facet. @@ -2500,12 +2676,10 @@ def facet_of(self): sage: len(one_cone.facet_of()) 2 """ - if "_facet_of" not in self.__dict__: - L = self._ambient._face_lattice_function() - H = L.hasse_diagram() - self._facet_of = self._sort_faces(f - for f in H.neighbors_out(L(self)) if is_Cone(f)) - return self._facet_of + L = self._ambient._face_lattice_function() + H = L.hasse_diagram() + return self._sort_faces( + f for f in H.neighbors_out(L(self)) if is_Cone(f)) def facets(self): r""" @@ -2629,6 +2803,16 @@ def is_equivalent(self, other): False sage: cone1.is_equivalent(cone2) True + + TESTS: + + A random cone is equivalent to itself:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8, max_rays=10) + sage: K.is_equivalent(K) + True + """ if self is other: return True @@ -2670,6 +2854,16 @@ def is_face_of(self, cone): sage: cone = Cone([(2,1,0),(1,2,0)]) sage: cone.is_face_of(octant) False + + TESTS: + + Any cone is a face of itself:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8, max_rays=10) + sage: K.is_face_of(K) + True + """ if self.lattice() != cone.lattice(): return False @@ -2745,9 +2939,31 @@ def is_isomorphic(self, other): (1, 0) sage: classify_cone_2d(*cone2.rays()) (3, 2) + + We check that :trac:`18613` is fixed:: + + sage: K = Cone([], ToricLattice(0)) + sage: K.is_isomorphic(K) + True + sage: K = Cone([(0,)]) + sage: K.is_isomorphic(K) + True + sage: K = Cone([(0,0)]) + + A random (strictly convex) cone is isomorphic to itself:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6, strictly_convex=True) + sage: K.is_isomorphic(K) + True """ - from sage.geometry.fan import Fan - return Fan([self]).is_isomorphic(Fan([other])) + if self.is_strictly_convex() and other.is_strictly_convex(): + from sage.geometry.fan import Fan + return Fan([self]).is_isomorphic(Fan([other])) + if self.is_strictly_convex() ^ other.is_strictly_convex(): + return False + raise NotImplementedError("isomorphism check for not strictly convex " + "cones is not implemented") def is_simplicial(self): r""" @@ -2879,7 +3095,7 @@ def lattice_polytope(self): See http://trac.sagemath.org/16180 for details. sage: lp 2-d lattice polytope in 2-d lattice N - sage: lp.vertices_pc() + sage: lp.vertices() N(1, 0), N(0, 1), N(0, 0) @@ -2889,7 +3105,7 @@ def lattice_polytope(self): sage: lp = line.lattice_polytope() sage: lp 1-d lattice polytope in 2-d lattice N - sage: lp.vertices_pc() + sage: lp.vertices() N( 1, 0), N(-1, 0) in 2-d lattice N @@ -3935,10 +4151,9 @@ def Hilbert_coefficients(self, point): return vector(ZZ, p.get_values(x)) - def is_solid(self): r""" - Return whether or not this cone is solid. + Check if this cone is solid. A cone is said to be solid if it has nonempty interior. That is, if its extreme rays span the entire ambient space. @@ -3973,10 +4188,9 @@ def is_solid(self): """ return (self.dim() == self.lattice_dim()) - def is_proper(self): r""" - Return whether or not this cone is proper. + Check if this cone is proper. A cone is said to be proper if it is closed, convex, solid, and contains no lines. This cone is assumed to be closed and @@ -4019,3 +4233,1002 @@ def is_proper(self): """ return (self.is_strictly_convex() and self.is_solid()) + + def is_full_space(self): + r""" + Check if this cone is equal to its ambient vector space. + + OUTPUT: + + ``True`` if this cone equals its entire ambient vector + space and ``False`` otherwise. + + EXAMPLES: + + A single ray in two dimensions is not equal to the entire + space:: + + sage: K = Cone([(1,0)]) + sage: K.is_full_space() + False + + Neither is the nonnegative orthant:: + + sage: K = Cone([(1,0),(0,1)]) + sage: K.is_full_space() + False + + The right half-space contains a vector subspace, but it is + still not equal to the entire space:: + + sage: K = Cone([(1,0),(-1,0),(0,1)]) + sage: K.is_full_space() + False + + However, if we allow conic combinations of both axes, then + the resulting cone is the entire two-dimensional space:: + + sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)]) + sage: K.is_full_space() + True + + """ + return self.linear_subspace() == self.lattice().vector_space() + + def lineality(self): + r""" + Return the lineality of this cone. + + The lineality of a cone is the dimension of the largest linear + subspace contained in that cone. + + OUTPUT: + + A nonnegative integer; the dimension of the largest subspace + contained within this cone. + + REFERENCES: + + .. [Rockafellar] R.T. Rockafellar. Convex Analysis. Princeton + University Press, Princeton, 1970. + + EXAMPLES: + + The lineality of the nonnegative orthant is zero, since it clearly + contains no lines:: + + sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: K.lineality() + 0 + + However, if we add another ray so that the entire `x`-axis belongs + to the cone, then the resulting cone will have lineality one:: + + sage: K = Cone([(1,0,0), (-1,0,0), (0,1,0), (0,0,1)]) + sage: K.lineality() + 1 + + If our cone is all of `\mathbb{R}^{2}`, then its lineality is equal + to the dimension of the ambient space (i.e. two):: + + sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)]) + sage: K.is_full_space() + True + sage: K.lineality() + 2 + sage: K.lattice_dim() + 2 + + Per the definition, the lineality of the trivial cone in a trivial + space is zero:: + + sage: K = Cone([], lattice=ToricLattice(0)) + sage: K.lineality() + 0 + + TESTS: + + The lineality of a cone should be an integer between zero and the + dimension of the ambient space, inclusive:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim = 8) + sage: l = K.lineality() + sage: l in ZZ + True + sage: 0 <= l <= K.lattice_dim() + True + + A strictly convex cone should have lineality zero:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim = 8, strictly_convex = True) + sage: K.lineality() + 0 + """ + return self.linear_subspace().dimension() + + @cached_method + def discrete_complementarity_set(self): + r""" + Compute a discrete complementarity set of this cone. + + A discrete complementarity set of a cone is the set of all + orthogonal pairs `(x,s)` where `x` is in some fixed generating + set of the cone, and `s` is in some fixed generating set of its + dual. The generators chosen for this cone and its dual are + simply their :meth:`~IntegralRayCollection.rays`. + + OUTPUT: + + A tuple of pairs `(x,s)` such that, + + * `x` and `s` are nonzero. + * `x` and `s` are orthogonal. + * `x` is one of this cone's :meth:`~IntegralRayCollection.rays`. + * `s` is one of the :meth:`~IntegralRayCollection.rays` of this + cone's :meth:`dual`. + + REFERENCES: + + .. [Orlitzky] M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + EXAMPLES: + + Pairs of standard basis elements form a discrete complementarity + set for the nonnegative orthant:: + + sage: K = Cone([(1,0),(0,1)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(0, 1), M(1, 0))) + + If a cone consists of a single ray, then the second components + of a discrete complementarity set for that cone should generate + the orthogonal complement of the ray:: + + sage: K = Cone([(1,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(1, 0), M(0, -1))) + sage: K = Cone([(1,0,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0, 0), M(0, 1, 0)), + (N(1, 0, 0), M(0, -1, 0)), + (N(1, 0, 0), M(0, 0, 1)), + (N(1, 0, 0), M(0, 0, -1))) + + When a cone is the entire space, its dual is the trivial cone, + so the only discrete complementarity set for it is empty:: + + sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)]) + sage: K.is_full_space() + True + sage: K.discrete_complementarity_set() + () + + Likewise for trivial cones, whose duals are the entire space:: + + sage: L = ToricLattice(0) + sage: K = Cone([], ToricLattice(0)) + sage: K.discrete_complementarity_set() + () + + TESTS: + + A discrete complementarity set for the dual can be obtained by + switching components in a discrete complementarity set of the + original cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs_dual = K.dual().discrete_complementarity_set() + sage: expected = tuple( (x,s) for (s,x) in dcs_dual ) + sage: actual = K.discrete_complementarity_set() + sage: sorted(actual) == sorted(expected) + True + + The pairs in a discrete complementarity set are in fact + complementary:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs = K.discrete_complementarity_set() + sage: sum([ x.inner_product(s).abs() for (x,s) in dcs ]) + 0 + """ + # Return an immutable tuple instead of a mutable list because + # the result will be cached. + return tuple( (x,s) for x in self + for s in self.dual() + if x.inner_product(s) == 0 ) + + def lyapunov_like_basis(self): + r""" + Compute a basis of Lyapunov-like transformations on this cone. + + A linear transformation `L` is said to be Lyapunov-like on this + cone if `L(x)` and `s` are orthogonal for every pair `(x,s)` in + its :meth:`discrete_complementarity_set`. The set of all such + transformations forms a vector space, namely the Lie algebra of + the automorphism group of this cone. + + OUTPUT: + + A list of matrices forming a basis for the space of all + Lyapunov-like transformations on this cone. + + REFERENCES: + + M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + .. [Rudolf] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh. + Bilinear optimality constraints for the cone of positive + polynomials. Mathematical Programming, Series B, 129 (2011) 5-31. + + EXAMPLES: + + Every transformation is Lyapunov-like on the trivial cone:: + + sage: K = Cone([(0,0)]) + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + And by duality, every transformation is Lyapunov-like on the + ambient space:: + + sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)]) + sage: K.is_full_space() + True + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + However, in a trivial space, there are no non-trivial linear maps, + so there can be no Lyapunov-like basis:: + + sage: L = ToricLattice(0) + sage: K = Cone([], lattice=L) + sage: K.lyapunov_like_basis() + [] + + The Lyapunov-like transformations on the nonnegative orthant are + diagonal matrices:: + + sage: K = Cone([(1,)]) + sage: K.lyapunov_like_basis() + [[1]] + + sage: K = Cone([(1,0),(0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0] [0 0] + [0 0], [0 1] + ] + + sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0 0] [0 0 0] [0 0 0] + [0 0 0] [0 1 0] [0 0 0] + [0 0 0], [0 0 0], [0 0 1] + ] + + Only the identity matrix is Lyapunov-like on the pyramids + defined by the one- and infinity-norms [Rudolf]_:: + + sage: l31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)]) + sage: l31.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + sage: l3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)]) + sage: l3infty.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + TESTS: + + The vectors `L(x)` and `s` are orthogonal for every pair `(x,s)` + in the :meth:`discrete_complementarity_set` of the cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: dcs = K.discrete_complementarity_set() + sage: LL = K.lyapunov_like_basis() + sage: ips = [ (L*x).inner_product(s) for (x,s) in dcs + ....: for L in LL ] + sage: sum(map(abs, ips)) + 0 + + The Lyapunov-like transformations on a cone and its dual are + transposes of one another. However, there's no reason to expect + that one basis will consist of transposes of the other:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: LL1 = K.lyapunov_like_basis() + sage: LL2 = [L.transpose() for L in K.dual().lyapunov_like_basis()] + sage: V = VectorSpace(K.lattice().base_field(), K.lattice_dim()^2) + sage: LL1_vecs = [ V(m.list()) for m in LL1 ] + sage: LL2_vecs = [ V(m.list()) for m in LL2 ] + sage: V.span(LL1_vecs) == V.span(LL2_vecs) + True + + The space of all Lyapunov-like transformations is a Lie algebra + and should therefore be closed under the lie bracket:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=4) + sage: LL = K.lyapunov_like_basis() + sage: W = VectorSpace(K.lattice().base_field(), K.lattice_dim()**2) + sage: LL_W = W.span([ W(m.list()) for m in LL ]) + sage: brackets = [ W((L1*L2 - L2*L1).list()) for L1 in LL + ....: for L2 in LL ] + sage: all([ b in LL_W for b in brackets ]) + True + """ + # Matrices are not vectors in Sage, so we have to convert them + # to vectors explicitly before we can find a basis. We need these + # two values to construct the appropriate "long vector" space. + F = self.lattice().base_field() + n = self.lattice_dim() + + # These tensor products contain a basis for the orthogonal + # complement of the Lyapunov-like transformations on this cone. + tensor_products = [ s.tensor_product(x) + for (x,s) in self.discrete_complementarity_set() ] + + # Convert those tensor products to long vectors. + W = VectorSpace(F, n**2) + perp_vectors = [ W(tp.list()) for tp in tensor_products ] + + # Now find the Lyapunov-like transformations (as long vectors). + LL_vectors = W.span(perp_vectors).complement() + + # And finally convert the long vectors back to matrices. + M = MatrixSpace(F, n, n) + return [ M(v.list()) for v in LL_vectors.basis() ] + + +def random_cone(lattice=None, min_ambient_dim=0, max_ambient_dim=None, + min_rays=0, max_rays=None, strictly_convex=None, solid=None): + r""" + Generate a random convex rational polyhedral cone. + + Lower and upper bounds may be provided for both the dimension of the + ambient space and the number of generating rays of the cone. If a + lower bound is left unspecified, it defaults to zero. Unspecified + upper bounds will be chosen randomly, unless you set ``solid``, in + which case they are chosen a little more wisely. + + You may specify the ambient ``lattice`` for the returned cone. In + that case, the ``min_ambient_dim`` and ``max_ambient_dim`` + parameters are ignored. + + You may also request that the returned cone be strictly convex (or + not). Likewise you may request that it be (non-)solid. + + .. WARNING:: + + If you request a large number of rays in a low-dimensional + space, you might be waiting for a while. For example, in three + dimensions, it is possible to obtain an octagon raised up to height + one (all z-coordinates equal to one). But in practice, we usually + generate the entire three-dimensional space with six rays before we + get to the eight rays needed for an octagon. We therefore have to + throw the cone out and start over from scratch. This process repeats + until we get lucky. + + We also refrain from "adjusting" the min/max parameters given to + us when a (non-)strictly convex or (non-)solid cone is + requested. This means that it may take a long time to generate + such a cone if the parameters are chosen unwisely. + + For example, you may want to set ``min_rays`` close to + ``min_ambient_dim`` if you desire a solid cone. Or, if you desire a + non-strictly-convex cone, then they all contain at least two + generating rays. So that might be a good candidate for + ``min_rays``. + + INPUT: + + * ``lattice`` (default: random) -- A ``ToricLattice`` object in + which the returned cone will live. By default a new lattice will + be constructed with a randomly-chosen rank (subject to + ``min_ambient_dim`` and ``max_ambient_dim``). + + * ``min_ambient_dim`` (default: zero) -- A nonnegative integer + representing the minimum dimension of the ambient lattice. + + * ``max_ambient_dim`` (default: random) -- A nonnegative integer + representing the maximum dimension of the ambient lattice. + + * ``min_rays`` (default: zero) -- A nonnegative integer representing + the minimum number of generating rays of the cone. + + * ``max_rays`` (default: random) -- A nonnegative integer representing + the maximum number of generating rays of the cone. + + * ``strictly_convex`` (default: random) -- Whether or not to make the + returned cone strictly convex. Specify ``True`` for a strictly convex + cone, ``False`` for a non-strictly-convex cone, or ``None`` if you + don't care. + + * ``solid`` (defalt: random) -- Whether or not to make the returned + cone solid. Specify ``True`` for a solid cone, ``False`` for a + non-solid cone, or ``None`` if you don't care. + + OUTPUT: + + A new, randomly generated cone. + + A ``ValueError`` will be thrown under the following conditions: + + * Any of ``min_ambient_dim``, ``max_ambient_dim``, ``min_rays``, or + ``max_rays`` are negative. + + * ``max_ambient_dim`` is less than ``min_ambient_dim``. + + * ``max_rays`` is less than ``min_rays``. + + * Both ``max_ambient_dim`` and ``lattice`` are specified. + + * ``min_rays`` is greater than four but ``max_ambient_dim`` is less than + three. + + * ``min_rays`` is greater than four but ``lattice`` has dimension + less than three. + + * ``min_rays`` is greater than two but ``max_ambient_dim`` is less than + two. + + * ``min_rays`` is greater than two but ``lattice`` has dimension less + than two. + + * ``min_rays`` is positive but ``max_ambient_dim`` is zero. + + * ``min_rays`` is positive but ``lattice`` has dimension zero. + + * A trivial lattice is supplied and a non-strictly-convex cone + is requested. + + * A non-strictly-convex cone is requested but ``max_rays`` is less + than two. + + * A solid cone is requested but ``max_rays`` is less than + ``min_ambient_dim``. + + * A solid cone is requested but ``max_rays`` is less than the + dimension of ``lattice``. + + * A non-solid cone is requested but ``max_ambient_dim`` is zero. + + * A non-solid cone is requested but ``lattice`` has dimension zero. + + * A non-solid cone is requested but ``min_rays`` is so large that + it guarantees a solid cone. + + ALGORITHM: + + First, a lattice is determined from ``min_ambient_dim`` and + ``max_ambient_dim`` (or from the supplied ``lattice``). + + Then, lattice elements are generated one at a time and added to a + cone. This continues until either the cone meets the user's + requirements, or the cone is equal to the entire space (at which + point it is futile to generate more). + + We check whether or not the resulting cone meets the user's + requirements; if it does, it is returned. If not, we throw it away + and start over. This process repeats indefinitely until an + appropriate cone is generated. + + EXAMPLES: + + Generate a trivial cone in a trivial space:: + + sage: set_random_seed() + sage: random_cone(max_ambient_dim=0, max_rays=0) + 0-d cone in 0-d lattice N + + We can predict the ambient dimension when + ``min_ambient_dim == max_ambient_dim``:: + + sage: set_random_seed() + sage: K = random_cone(min_ambient_dim=4, max_ambient_dim=4) + sage: K.lattice_dim() + 4 + + Likewise for the number of rays when ``min_rays == max_rays``:: + + sage: set_random_seed() + sage: K = random_cone(min_rays=3, max_rays=3) + sage: K.nrays() + 3 + + If we specify a lattice, then the returned cone will live in it:: + + sage: set_random_seed() + sage: L = ToricLattice(5, "L") + sage: K = random_cone(lattice=L) + sage: K.lattice() is L + True + + We can also request a strictly convex cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8, max_rays=10, + ....: strictly_convex=True) + sage: K.is_strictly_convex() + True + + Or one that isn't strictly convex:: + + sage: set_random_seed() + sage: K = random_cone(min_ambient_dim=5, min_rays=2, + ....: strictly_convex=False) + sage: K.is_strictly_convex() + False + + An example with all parameters set:: + + sage: set_random_seed() + sage: K = random_cone(min_ambient_dim=4, max_ambient_dim=7, + ....: min_rays=2, max_rays=10, + ....: strictly_convex=False, solid=True) + sage: 4 <= K.lattice_dim() and K.lattice_dim() <= 7 + True + sage: 2 <= K.nrays() and K.nrays() <= 10 + True + sage: K.is_strictly_convex() + False + sage: K.is_solid() + True + + TESTS: + + It's hard to test the output of a random process, but we can at + least make sure that we get a cone back. + + sage: set_random_seed() + sage: from sage.geometry.cone import is_Cone + sage: K = random_cone(max_ambient_dim=6, max_rays=10) + sage: is_Cone(K) + True + + The upper/lower bounds are respected:: + + sage: set_random_seed() + sage: K = random_cone(min_ambient_dim=5, max_ambient_dim=8, + ....: min_rays=3, max_rays=4) + sage: 5 <= K.lattice_dim() and K.lattice_dim() <= 8 + True + sage: 3 <= K.nrays() and K.nrays() <= 4 + True + + Ensure that an exception is raised when either lower bound is greater + than its respective upper bound:: + + sage: set_random_seed() + sage: random_cone(min_ambient_dim=5, max_ambient_dim=2) + Traceback (most recent call last): + ... + ValueError: max_ambient_dim cannot be less than min_ambient_dim. + + sage: random_cone(min_rays=5, max_rays=2) + Traceback (most recent call last): + ... + ValueError: max_rays cannot be less than min_rays. + + Or if we specify both ``max_ambient_dim`` and ``lattice``:: + + sage: set_random_seed() + sage: L = ToricLattice(5, "L") + sage: random_cone(lattice=L, max_ambient_dim=10) + Traceback (most recent call last): + ... + ValueError: max_ambient_dim cannot be specified when a lattice is + provided. + + If the user requests too many rays in zero, one, or two dimensions, + a ``ValueError`` is thrown:: + + sage: set_random_seed() + sage: random_cone(max_ambient_dim=0, min_rays=1) + Traceback (most recent call last): + ... + ValueError: all cones in zero dimensions have no generators. + Please increase max_ambient_dim to at least 1, or decrease min_rays. + + sage: random_cone(max_ambient_dim=1, min_rays=3) + Traceback (most recent call last): + ... + ValueError: all cones in zero/one dimensions have two or fewer + generators. Please increase max_ambient_dim to at least 2, or decrease + min_rays. + + sage: random_cone(max_ambient_dim=2, min_rays=5) + Traceback (most recent call last): + ... + ValueError: all cones in zero/one/two dimensions have four or fewer + generators. Please increase max_ambient_dim to at least 3, or decrease + min_rays. + + sage: L = ToricLattice(0) + sage: random_cone(lattice=L, min_rays=1) + Traceback (most recent call last): + ... + ValueError: all cones in the given lattice have no generators. + Please decrease min_rays. + + sage: L = ToricLattice(1) + sage: random_cone(lattice=L, min_rays=3) + Traceback (most recent call last): + ... + ValueError: all cones in the given lattice have two or fewer + generators. Please decrease min_rays. + + sage: L = ToricLattice(2) + sage: random_cone(lattice=L, min_rays=5) + Traceback (most recent call last): + ... + ValueError: all cones in the given lattice have four or fewer + generators. Please decrease min_rays. + + Ensure that we can obtain a cone in three dimensions with a large + number (in particular, more than 2*dim) rays:: + + sage: set_random_seed() # long time + sage: K = random_cone(min_ambient_dim=3, # long time + ....: max_ambient_dim=3, # long time + ....: min_rays=7) # long time + sage: K.nrays() >= 7 # long time + True + sage: K.lattice_dim() # long time + 3 + + We need three dimensions to obtain five rays; we should throw out + cones in zero/one/two dimensions until we get lucky:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=3, min_rays=5) + sage: K.nrays() >= 5 + True + sage: K.lattice_dim() + 3 + + It is an error to request a non-strictly-convex trivial cone:: + + sage: set_random_seed() + sage: L = ToricLattice(0,"L") + sage: random_cone(lattice=L, strictly_convex=False) + Traceback (most recent call last): + ... + ValueError: all cones in this lattice are strictly convex (trivial). + + Or a non-strictly-convex cone with fewer than two rays:: + + sage: set_random_seed() + sage: random_cone(max_rays=1, strictly_convex=False) + Traceback (most recent call last): + ... + ValueError: all cones are strictly convex when ``max_rays`` is + less than two. + + But fine to ask for a strictly convex trivial cone:: + + sage: set_random_seed() + sage: L = ToricLattice(0,"L") + sage: random_cone(lattice=L, strictly_convex=True) + 0-d cone in 0-d lattice L + + A ``ValueError`` is thrown if a non-solid cone is requested in a + zero-dimensional lattice:: + + sage: set_random_seed() + sage: L = ToricLattice(0) + sage: random_cone(lattice=L, solid=False) + Traceback (most recent call last): + ... + ValueError: all cones in the given lattice are solid. + + sage: random_cone(max_ambient_dim=0, solid=False) + Traceback (most recent call last): + ... + ValueError: all cones are solid when max_ambient_dim is zero. + + A ``ValueError`` is thrown if a solid cone is requested but the + maximum number of rays is too few:: + + sage: set_random_seed() + sage: random_cone(min_ambient_dim=4, max_rays=3, solid=True) + Traceback (most recent call last): + ... + ValueError: max_rays must be at least min_ambient_dim for a solid cone. + + sage: L = ToricLattice(5) + sage: random_cone(lattice=L, max_rays=3, solid=True) + Traceback (most recent call last): + ... + ValueError: max_rays must be at least 5 for a solid cone in this + lattice. + + A ``ValueError`` is thrown if a non-solid cone is requested but + ``min_rays`` guarantees a solid cone:: + + sage: set_random_seed() + sage: random_cone(max_ambient_dim=4, min_rays=10, solid=False) + Traceback (most recent call last): + ... + ValueError: every cone is solid when min_rays > 2*(max_ambient_dim - 1). + + sage: L = ToricLattice(4) + sage: random_cone(lattice=L, min_rays=10, solid=False) + Traceback (most recent call last): + ... + ValueError: every cone is solid when min_rays > 2*(d - 1) where d + is the dimension of the given lattice. + + """ + + # Catch obvious mistakes so that we can generate clear error + # messages. + + if min_ambient_dim < 0: + raise ValueError('min_ambient_dim must be nonnegative.') + + if min_rays < 0: + raise ValueError('min_rays must be nonnegative.') + + if max_ambient_dim is not None: + if max_ambient_dim < 0: + raise ValueError('max_ambient_dim must be nonnegative.') + if (max_ambient_dim < min_ambient_dim): + msg = 'max_ambient_dim cannot be less than min_ambient_dim.' + raise ValueError(msg) + if lattice is not None: + msg = 'max_ambient_dim cannot be specified when a lattice is ' + msg += 'provided.' + raise ValueError(msg) + + # The next three checks prevent an infinite loop (a futile + # search for more rays) in zero, one, or two dimensions. + if min_rays > 4 and max_ambient_dim < 3: + msg = 'all cones in zero/one/two dimensions have four or fewer ' + msg += 'generators. Please increase max_ambient_dim to at least ' + msg += '3, or decrease min_rays.' + raise ValueError(msg) + + if min_rays > 2 and max_ambient_dim < 2: + msg = 'all cones in zero/one dimensions have two or fewer ' + msg += 'generators. Please increase max_ambient_dim to at least ' + msg += '2, or decrease min_rays.' + raise ValueError(msg) + + if min_rays > 0 and max_ambient_dim == 0: + msg = 'all cones in zero dimensions have no generators. ' + msg += 'Please increase max_ambient_dim to at least 1, or ' + msg += 'decrease min_rays.' + raise ValueError(msg) + + if max_rays is not None: + if max_rays < 0: + raise ValueError('max_rays must be nonnegative.') + if (max_rays < min_rays): + raise ValueError('max_rays cannot be less than min_rays.') + + # Also perform the "futile search" checks when a lattice is given, + # using its dimension rather than max_ambient_dim as the indicator. + if lattice is not None: + if min_rays > 4 and lattice.dimension() < 3: + msg = 'all cones in the given lattice have four or fewer ' + msg += 'generators. Please decrease min_rays.' + raise ValueError(msg) + + if min_rays > 2 and lattice.dimension() < 2: + msg = 'all cones in the given lattice have two or fewer ' + msg += 'generators. Please decrease min_rays.' + raise ValueError(msg) + + if min_rays > 0 and lattice.dimension() == 0: + msg = 'all cones in the given lattice have no generators. ' + msg += 'Please decrease min_rays.' + raise ValueError(msg) + + # Sanity checks for strictly_convex. + if strictly_convex is not None and not strictly_convex: + if lattice is not None and lattice.dimension() == 0: + msg = 'all cones in this lattice are strictly convex (trivial).' + raise ValueError(msg) + if max_rays is not None and max_rays < 2: + msg = 'all cones are strictly convex when ``max_rays`` is ' + msg += 'less than two.' + raise ValueError(msg) + + # Sanity checks for solid cones. + if solid is not None and solid: + # The user wants a solid cone. + if lattice is None: + if max_rays is not None: + if max_rays < min_ambient_dim: + msg = 'max_rays must be at least min_ambient_dim for ' + msg += 'a solid cone.' + raise ValueError(msg) + else: + # Repeat the checks above when a lattice is given. + if max_rays is not None and max_rays < lattice.dimension(): + msg = "max_rays must be at least {0} for a solid cone " + msg += "in this lattice." + raise ValueError(msg.format(lattice.dimension())) + + # Sanity checks for non-solid cones. + if solid is not None and not solid: + if lattice is None: + if max_ambient_dim is not None and max_ambient_dim == 0: + msg = 'all cones are solid when max_ambient_dim is zero.' + raise ValueError(msg) + if (max_ambient_dim is not None and + min_rays > 2*(max_ambient_dim - 1)): + msg = 'every cone is solid when ' + msg += 'min_rays > 2*(max_ambient_dim - 1).' + raise ValueError(msg) + else: + if lattice.dimension() == 0: + msg = 'all cones in the given lattice are solid.' + raise ValueError(msg) + if min_rays > 2*(lattice.dimension() - 1): + msg = 'every cone is solid when min_rays > 2*(d - 1) ' + msg += 'where d is the dimension of the given lattice.' + raise ValueError(msg) + + + # Now that we've sanity-checked our parameters, we can massage the + # min/maxes for (non-)solid cones. It doesn't violate the user's + # expectation to increase a minimum, decrease a maximum, or fix an + # "I don't care" parameter. + if solid is not None: + if solid: + # If max_ambient_dim is "I don't care", we can set it so that we + # guaranteed to generate a solid cone. + if max_rays is not None and max_ambient_dim is None: + # We won't make max_ambient_dim less than min_ambient_dim, + # since we already checked that + # min_ambient_dim <= min_rays = max_ambient_dim. + max_ambient_dim = min_rays + else: + if max_rays is None and max_ambient_dim is not None: + # This is an upper limit on the number of rays in a + # non-solid cone. + max_rays = 2*(max_ambient_dim - 1) + if max_rays is None and lattice is not None: + # Same thing, except when we're given a lattice. + max_rays = 2*(lattice.dimension() - 1) + + def random_min_max(l,u): + r""" + We need to handle two cases for the upper bounds, and we need + to do the same thing for max_ambient_dim/max_rays. So we consolidate + the logic here. + """ + if u is None: + # The upper bound is unspecified; return a random integer + # in [l,infinity). + return l + ZZ.random_element().abs() + else: + # We have an upper bound, and it's greater than or equal + # to our lower bound. So we generate a random integer in + # [0,u-l], and then add it to l to get something in + # [l,u]. To understand the "+1", check the + # ZZ.random_element() docs. + return l + ZZ.random_element(u - l + 1) + + def is_valid(K): + r""" + Check if the given cone is valid; that is, if its ambient + dimension and number of rays meet the upper and lower bounds + provided by the user. + """ + if lattice is None: + # We only care about min/max_ambient_dim when no lattice is given. + if K.lattice_dim() < min_ambient_dim: + return False + if (max_ambient_dim is not None and + K.lattice_dim() > max_ambient_dim): + return False + else: + if K.lattice() is not lattice: + return False + return all([ + K.nrays() >= min_rays, + K.nrays() <= max_rays or max_rays is None, + K.is_solid() == solid or solid is None, + K.is_strictly_convex() == strictly_convex or strictly_convex is None + ]) + + # Now we actually compute the thing. To avoid recursion (and the + # associated "maximum recustion depth exceeded" error), we loop + # until we have a valid cone and occasionally throw everything out + # and start over from scratch. + while True: + L = lattice + + if lattice is None: + # No lattice given, make our own. + d = random_min_max(min_ambient_dim, max_ambient_dim) + L = ToricLattice(d) + + r = random_min_max(min_rays, max_rays) + + # The rays are trickier to generate, since we could generate v and + # 2*v as our "two rays." In that case, the resulting cone would + # have one generating ray. To avoid such a situation, we start by + # generating ``r`` rays where ``r`` is the number we want to end + # up with... + rays = [L.random_element() for i in range(r)] + + # The lattice parameter is required when no rays are given, so + # we pass it in case ``r == 0`` or ``d == 0`` (or ``d == 1`` + # but we're making a strictly convex cone). + K = Cone(rays, lattice=L) + + # Now, some of the rays that we generated were probably redundant, + # so we need to come up with more. We can obviously stop if ``K`` + # becomes the entire ambient vector space. + # + # We're still not guaranteed to have the correct number of + # rays after this! Since we normalize the generators in the + # loop above, we can jump from two to four generators by + # adding e.g. (1,1) to [(0,1), (0,-1)]. Rather than trying to + # mangle what we have, we just start over if we get a cone + # that won't work. + # + while r > K.nrays() and not K.is_full_space(): + rays.append(L.random_element()) + K = Cone(rays, lattice=L) + rays = list(K.rays()) # Avoid re-normalizing next time around + + + if strictly_convex is not None: + if strictly_convex: + if not K.is_strictly_convex(): + # The user wants a strictly convex cone, but + # didn't get one. So let's take our rays, and give + # them all either (strictly) positive or negative + # leading coordinates. This makes the resulting + # cone strictly convex. Whether or not those + # coordinates become positive/negative is chosen + # randomly. + from random import choice + pm = choice([-1,1]) + + # rays has immutable elements + from copy import copy + rays = map(copy, rays) + + for i, ray in enumerate(rays): + rays[i][0] = pm * (ray[0].abs() + 1) + + K = Cone(rays, lattice=L) + else: + # The user requested that the cone be NOT strictly + # convex. So it should contain some line... + if K.is_strictly_convex(): + # ...but it doesn't. If K has at least two rays, + # we can just make the second one a multiple of + # the first -- then K will contain a line. If K + # has fewer than two rays, we punt. + if len(rays) >= 2: + rays[1] = -rays[0] + K = Cone(rays, lattice=L) + + if is_valid(K): + # Loop if we don't have a valid cone. + return K diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 6fc364e8dcd..ecc26970000 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -671,8 +671,9 @@ def FaceFan(polytope, lattice=None): if is_LatticePolytope(polytope): if any(d <= 0 for d in polytope.distances([0]*polytope.dim())): raise interior_point_error - cones = (facet.vertices() for facet in polytope.facets()) - rays = polytope.vertices_pc() + cones = (f.ambient_vertex_indices() for f in polytope.facets_lp()) + rays = polytope.vertices() + is_complete = polytope.dim() == polytope.lattice_dim() else: origin = polytope.ambient_space().zero() if not (polytope.is_compact() and @@ -681,13 +682,13 @@ def FaceFan(polytope, lattice=None): cones = [ [ v.index() for v in facet.incident() ] for facet in polytope.inequalities() ] rays = [vector(_) for _ in polytope.vertices()] + is_complete = polytope.dim() == polytope.ambient_dim() if lattice is None: # Since default lattice polytopes are in the M lattice, # treat polyhedra as being there as well. lattice = ToricLattice(len(origin)).dual() - fan = Fan(cones, rays, lattice=lattice, check=False, - is_complete=(polytope.dim() == polytope.ambient_dim())) - return fan + return Fan(cones, rays, lattice=lattice, check=False, + is_complete=is_complete) def NormalFan(polytope, lattice=None): @@ -752,21 +753,23 @@ def NormalFan(polytope, lattice=None): sage: fan1.is_equivalent(fan2) True """ + dimension_error = ValueError( + 'the normal fan is only defined for full-dimensional polytopes') from sage.geometry.lattice_polytope import is_LatticePolytope - if polytope.dim() != polytope.ambient_dim(): - raise ValueError('the normal fan is only defined for full-dimensional polytopes') if is_LatticePolytope(polytope): + if polytope.dim() != polytope.lattice_dim(): + raise dimension_error rays = polytope.facet_normals() - cones = (vertex.facets() for vertex in polytope.faces(dim=0)) + cones = (v.ambient_facet_indices() for v in polytope.faces_lp(dim=0)) else: + if polytope.dim() != polytope.ambient_dim(): + raise dimension_error if not polytope.is_compact(): raise NotImplementedError('the normal fan is only supported for polytopes (compact polyhedra).') cones = [ [ ieq.index() for ieq in vertex.incident() ] for vertex in polytope.vertices() ] rays =[ ieq.A() for ieq in polytope.inequalities() ] - fan = Fan(cones, rays, lattice=lattice, check=False, - is_complete=(polytope.dim() == polytope.ambient_dim())) - return fan + return Fan(cones, rays, lattice=lattice, check=False, is_complete=True) def Fan2d(rays, lattice=None): @@ -1318,6 +1321,18 @@ def _compute_cone_lattice(self): Finite poset containing 5 elements with distinguished linear extension These 5 elements are: 1 origin, 2 rays, 1 generating cone, 1 fan. + + A subcase of this common case is treatment of fans consisting of the + origin only, which used to be handled incorrectly :trac:`18613`:: + + sage: fan = Fan([Cone([], ToricLattice(0))]) + sage: list(fan.cone_lattice()) + [0-d cone of Rational polyhedral fan in 0-d lattice N, + Rational polyhedral fan in 0-d lattice N] + sage: fan = Fan([Cone([], ToricLattice(1))]) + sage: list(fan.cone_lattice()) + [0-d cone of Rational polyhedral fan in 1-d lattice N, + Rational polyhedral fan in 1-d lattice N] Finally, we have "intermediate" fans which are incomplete but are generated by more than one cone:: @@ -1356,7 +1371,8 @@ def FanFace(rays, cones): if "_is_complete" in self.__dict__ and self._is_complete: # We can use a fast way for complete fans self._cone_lattice = Hasse_diagram_from_incidences( - self._ray_to_cones(), + # When there are no rays, fan is the only atom + self._ray_to_cones() if self.rays() else [()], (cone.ambient_ray_indices() for cone in self), FanFace, key = id(self)) else: diff --git a/src/sage/geometry/fan_isomorphism.py b/src/sage/geometry/fan_isomorphism.py index 51bdd5f6f88..fd99e5cb8af 100644 --- a/src/sage/geometry/fan_isomorphism.py +++ b/src/sage/geometry/fan_isomorphism.py @@ -348,9 +348,17 @@ def fan_2d_echelon_forms(fan): ... perm_rays = [ rays[perm(i+1)-1] for i in range(len(rays)) ] ... fan2 = Fan(perm_cones, rays=[m*vector(r) for r in perm_rays]) ... assert fan_2d_echelon_form(fan2) in echelon_forms + + The trivial case was fixed in :trac:`18613`:: + + sage: fan = Fan([], lattice=ToricLattice(2)) + sage: fan_2d_echelon_forms(fan) + frozenset({[]}) + sage: parent(list(_)[0]) + Full MatrixSpace of 2 by 0 dense matrices over Integer Ring """ if fan.nrays() == 0: - return frozenset() + return frozenset([fan_2d_echelon_form(fan)]) rays = list(fan_2d_cyclically_ordered_rays(fan)) echelon_forms = [] for i in range(2): @@ -383,6 +391,5 @@ def fan_2d_echelon_form(fan): [ 1 0 -1] [ 0 1 -1] """ - ray_matrix = fan_2d_cyclically_ordered_rays(fan).matrix() - return ray_matrix.transpose().echelon_form() - + ray_matrix = fan_2d_cyclically_ordered_rays(fan).column_matrix() + return ray_matrix.echelon_form() diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 73fd2c6414e..f7bb0626e28 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -31,6 +31,7 @@ .. TODO:: Implement a parent for all geodesics of the hyperbolic plane? + Or implement geodesics as a parent in the subobjects category? """ @@ -1168,11 +1169,11 @@ def _crossratio_matrix(p0, p1, p2): # UHP r""" Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking - the elements of `p` to `0`,`1`, and `\infty'. + the elements of `p` to `0`, `1`, and `\infty`. INPUT: - - a list of three distinct elements of three distinct elements + - a list of three distinct elements of `\mathbb{CP}^1` in affine coordinates; that is, each element must be a complex number, `\infty`, or symbolic. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d8d3c411467..d2a4008207f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -97,7 +97,7 @@ def __init__(self): sage: H = HyperbolicPlane() sage: TestSuite(H).run() """ - Parent.__init__(self, category=Sets().WithRealizations()) + Parent.__init__(self, category=Sets().Metric().WithRealizations()) self.a_realization() # We create a realization so at least one is known def _repr_(self): @@ -181,9 +181,10 @@ def super_categories(self): sage: H = HyperbolicPlane() sage: models = HyperbolicModels(H) sage: models.super_categories() - [Category of sets, Category of realizations of Hyperbolic plane] + [Category of metric spaces, + Category of realizations of Hyperbolic plane] """ - return [Sets(), Realizations(self.base())] + return [Sets().Metric(), Realizations(self.base())] class ParentMethods: def _an_element_(self): @@ -204,31 +205,3 @@ def _an_element_(self): """ return self(self.realization_of().PD().get_point(0)) - # TODO: Move to a category of metric spaces once created - @abstract_method - def dist(self, a, b): - """ - Return the distance between ``a`` and ``b``. - - EXAMPLES:: - - sage: PD = HyperbolicPlane().PD() - sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) - arccosh(5/3) - """ - - class ElementMethods: - # TODO: Move to a category of metric spaces once created - def dist(self, other): - """ - Return the distance between ``self`` and ``other``. - - EXAMPLES:: - - sage: UHP = HyperbolicPlane().UHP() - sage: p1 = UHP.get_point(5 + 7*I) - sage: p2 = UHP.get_point(1 + I) - sage: p1.dist(p2) - arccosh(33/7) - """ - return self.parent().dist(self, other) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 73cbf2eee26..6717d0206fe 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -65,10 +65,6 @@ False sage: U.boundary_point_in_model(2) True - -.. TODO:: - - Implement a category for metric spaces. """ #*********************************************************************** diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index c1e4ee1c66d..bb58ffd52a0 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -23,7 +23,9 @@ sage: -2*h Hyperplane -6*x - 4*y + 10*z + 14 sage: x, y, z - (Hyperplane x + 0*y + 0*z + 0, Hyperplane 0*x + y + 0*z + 0, Hyperplane 0*x + 0*y + z + 0) + (Hyperplane x + 0*y + 0*z + 0, + Hyperplane 0*x + y + 0*z + 0, + Hyperplane 0*x + 0*y + z + 0) See :mod:`sage.geometry.hyperplane_arrangement.hyperplane` for more functionality of the individual hyperplanes. @@ -340,9 +342,11 @@ from sage.misc.cachefunc import cached_method from sage.misc.misc import uniq from sage.matrix.constructor import matrix, vector +from sage.modules.free_module import VectorSpace from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane +from copy import copy class HyperplaneArrangementElement(Element): @@ -695,7 +699,6 @@ def intersection_poset(self): """ K = self.base_ring() from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace - from sage.modules.all import VectorSpace whole_space = AffineSubspace(0, VectorSpace(K, self.dimension())) L = [[whole_space]] active = True @@ -756,7 +759,7 @@ def characteristic_polynomial(self): x^2 - 2*x + 1 TESTS:: - + sage: H. = HyperplaneArrangements(QQ) sage: m = matrix([(0, -1, 0, 1, -1), (0, -1, 1, -1, 0), (0, -1, 1, 0, -1), ....: (0, 1, 0, 0, 0), (0, 1, 0, 1, -1), (0, 1, 1, -1, 0), (0, 1, 1, 0, -1)]) @@ -948,7 +951,7 @@ def n_regions(self): 19 TESTS:: - + sage: H. = HyperplaneArrangements(QQ) sage: A = H([(1,1), 0], [(2,3), -1], [(4,5), 3]) sage: B = A.change_ring(FiniteField(7)) @@ -1359,16 +1362,17 @@ def vertices(self, exclude_sandwiched=False): sage: H. = HyperplaneArrangements(QQ) sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.vertices()) 81 sage: chessboard.vertices(exclude_sandwiched=True) ((0, 0), (0, 8), (8, 0), (8, 8)) """ + import itertools from sage.matroids.all import Matroid - from sage.combinat.cartesian_product import CartesianProduct R = self.parent().base_ring() parallels = self._parallel_hyperplanes() A_list = [parallel[0][1] for parallel in parallels] @@ -1390,7 +1394,7 @@ def skip(b_list): for row, i in enumerate(indices): lhs[row] = A_list[i] b_list = [b_list_list[i] for i in indices] - for b in CartesianProduct(*b_list): + for b in itertools.product(*b_list): for i in range(d): rhs[i] = b[i] vertex = lhs.solve_right(rhs) @@ -1418,7 +1422,7 @@ def _make_region(self, hyperplanes): sage: h._make_region([x, 1-x, y, 1-y]) A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices """ - ieqs = [h.coefficients() for h in hyperplanes] + ieqs = [h.dense_coefficient_list() for h in hyperplanes] from sage.geometry.polyhedron.constructor import Polyhedron return Polyhedron(ieqs=ieqs, ambient_dim=self.dimension(), base_ring=self.parent().base_ring()) @@ -1454,8 +1458,9 @@ def regions(self): sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.bounded_regions()) # long time, 359 ms on a Core i7 64 @@ -1468,7 +1473,7 @@ def regions(self): universe = Polyhedron(eqns=[[0] + [0] * dim], base_ring=R) regions = [universe] for hyperplane in self: - ieq = vector(R, hyperplane.coefficients()) + ieq = vector(R, hyperplane.dense_coefficient_list()) pos_half = Polyhedron(ieqs=[ ieq], base_ring=R) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R) subdivided = [] @@ -1928,14 +1933,119 @@ def varchenko_matrix(self, names='h'): v.set_immutable() return v + @cached_method + def matroid(self): + r""" + Return the matroid associated to ``self``. + + Let `A` denote a central hyperplane arrangement and `n_H` the + normal vector of some hyperplane `H \in A`. We define a matroid + `M_A` as the linear matroid spanned by `\{ n_H | H \in A \}`. + The matroid `M_A` is such that the lattice of flats of `M` is + isomorphic to the intersection lattice of `A` + (Proposition 3.6 in [RS]_). + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z) + sage: M = A.matroid(); M + Linear matroid of rank 3 on 7 elements represented over the Rational Field + + We check the lattice of flats is isomorphic to the + intersection lattice:: + + sage: f = sum([list(M.flats(i)) for i in range(M.rank()+1)], []) + sage: PF = Poset([f, lambda x,y: x < y]) + sage: PF.is_isomorphic(A.intersection_poset()) + True + """ + if not self.is_central(): + raise ValueError("the hyperplane arrangement must be central") + norms = [p.normal() for p in self] + from sage.matroids.constructor import Matroid + return Matroid(matrix=matrix(norms).transpose()) + + @cached_method + def minimal_generated_number(self): + r""" + Return the minimum `k` such that ``self`` is `k`-generated. + + Let `A` be a central hyperplane arrangement. Let `W_k` denote + the solution space of the linear system corresponding to the + linear dependencies among the hyperplanes of `A` of length at + most `k`. We say `A` is `k`-*generated* if + `\dim W_k = \operatorname{rank} A`. + Equivalently this says all dependencies forming the Orlik-Terao + ideal are generated by at most `k` hyperplanes. + + EXAMPLES: + + We construct Example 2.2 from [Vuz93]_:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.minimal_generated_number() + 3 + sage: B.minimal_generated_number() + 4 + + REFERENCES: + + .. [Vuz93] Sergey Yuzvinksy, + *The first two obstructions to the freeness of arrangements*, + Transactions of the American Mathematical Society, + Vol. 335, **1** (1993) pp. 231--244. + """ + V = VectorSpace(self.base_ring(), self.dimension()) + W = VectorSpace(self.base_ring(), self.n_hyperplanes()) + r = self.rank() + M = self.matroid() + norms = M.representation().columns() + circuits = M.circuits() + for i in range(2, self.n_hyperplanes()): + sol = [] + for d in circuits: + if len(d) > i: + continue + d = list(d) + dep = V.linear_dependence([norms[j] for j in d]) + w = W.zero().list() + for j,k in enumerate(d): + w[k] = dep[0][j] + sol.append(w) + mat = matrix(sol) + if mat.right_kernel().dimension() == r: + return i + return self.n_hyperplanes() + + def is_formal(self): + """ + Return if ``self`` is formal. + + A hyperplane arrangement is *formal* if it is 3-generated [Vuz93]_, + where `k`-generated is defined in :meth:`minimal_generated_number`. + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.is_formal() + True + sage: B.is_formal() + False + """ + return self.minimal_generated_number() <= 3 class HyperplaneArrangements(Parent, UniqueRepresentation): """ Hyperplane arrangements. For more information on hyperplane arrangements, see - :mod:`sage.geometry.hyperplane_arrangements.arrangement`. + :mod:`sage.geometry.hyperplane_arrangement.arrangement`. INPUT: @@ -1944,7 +2054,7 @@ class HyperplaneArrangements(Parent, UniqueRepresentation): - ``names`` -- tuple of strings; the variable names EXAMPLES:: - + sage: H. = HyperplaneArrangements(QQ) sage: x Hyperplane x + 0*y + 0 @@ -2227,7 +2337,7 @@ def _coerce_map_from_(self, P): Return whether there is a coercion. TESTS:: - + sage: L. = HyperplaneArrangements(QQ); L Hyperplane arrangements in 1-dimensional linear space over Rational Field with coordinate x sage: M. = HyperplaneArrangements(RR); M diff --git a/src/sage/geometry/integral_points.pyx b/src/sage/geometry/integral_points.pyx index bde90c18d6d..3881ba5fce7 100644 --- a/src/sage/geometry/integral_points.pyx +++ b/src/sage/geometry/integral_points.pyx @@ -10,13 +10,14 @@ Cython helper methods to compute integral points in polyhedra. # http://www.gnu.org/licenses/ #***************************************************************************** +import copy +import itertools + from sage.matrix.constructor import matrix, column_matrix, vector, diagonal_matrix from sage.rings.all import QQ, RR, ZZ, gcd, lcm from sage.rings.integer cimport Integer from sage.combinat.permutation import Permutation -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import prod, uniq -import copy ############################################################################## # The basic idea to enumerate the lattice points in the parallelotope @@ -209,7 +210,7 @@ cpdef loop_over_parallelotope_points(e, d, VDinv, R, lattice, A=None, b=None): s = ZZ.zero() # summation variable gen = lattice(0) q_times_d = vector(ZZ, dim) - for base in CartesianProduct(*[ range(0,i) for i in e ]): + for base in itertools.product(*[ range(0,i) for i in e ]): for i in range(0, dim): s = ZZ.zero() for j in range(0, dim): diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 93b89fa6bde..d0cc12f77b6 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -102,9 +102,11 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.combinat.posets.posets import FinitePoset +from sage.geometry.hasse_diagram import Hasse_diagram_from_incidences from sage.geometry.point_collection import PointCollection, is_PointCollection from sage.geometry.toric_lattice import ToricLattice, is_ToricLattice -from sage.graphs.graph import Graph +from sage.graphs.graph import DiGraph, Graph from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.interfaces.all import maxima from sage.matrix.constructor import matrix @@ -162,8 +164,7 @@ def __call__(self, x): SetOfAllLatticePolytopes = SetOfAllLatticePolytopesClass() -def LatticePolytope(data, desc=None, compute_vertices=True, - n=0, lattice=None): +def LatticePolytope(data, compute_vertices=True, n=0, lattice=None): r""" Construct a lattice polytope. @@ -184,9 +185,6 @@ def LatticePolytope(data, desc=None, compute_vertices=True, * a filename of such a file, see :func:`read_palp_matrix` for the file format; - - ``desc`` -- DEPRECATED (default: "A lattice polytope") a string - description of the polytope; - - ``compute_vertices`` -- boolean (default: ``True``). If ``True``, the convex hull of the given points will be computed for determining vertices. Otherwise, the given points must be @@ -211,7 +209,7 @@ def LatticePolytope(data, desc=None, compute_vertices=True, sage: p = LatticePolytope(points) sage: p 3-d reflexive polytope in 3-d lattice M - sage: p.vertices_pc() + sage: p.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -244,11 +242,11 @@ def LatticePolytope(data, desc=None, compute_vertices=True, Given points must be in the lattice:: - sage: LatticePolytope(matrix([1/2, 3/2])) + sage: LatticePolytope([[1/2], [3/2]]) Traceback (most recent call last): ... ValueError: points - [(1/2), (3/2)] + [[1/2], [3/2]] are not in 1-d lattice M! But it is OK to create polytopes of non-maximal dimension:: @@ -258,7 +256,7 @@ def LatticePolytope(data, desc=None, compute_vertices=True, ... (-1,0,0), (0,-1,0), (0,0,0), (0,0,0)]) sage: p 2-d lattice polytope in 3-d lattice M - sage: p.vertices_pc() + sage: p.vertices() M( 1, 0, 0), M( 0, 1, 0), M(-1, 0, 0), @@ -269,21 +267,18 @@ def LatticePolytope(data, desc=None, compute_vertices=True, sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p -1-d lattice polytope in 3-d lattice M - sage: p.ambient_dim() + sage: p.lattice_dim() 3 sage: p.npoints() 0 sage: p.nfacets() 0 - sage: p.points_pc() + sage: p.points() Empty collection in 3-d lattice M - sage: p.faces() - [] + sage: p.faces_lp() + ((-1-d lattice polytope in 3-d lattice M,),) """ - if desc is not None: - deprecation(15240, "custom descriptions for lattice polytopes are " - "deprecated and ignored!") if isinstance(data, LatticePolytopeClass): data = data._vertices compute_vertices = False @@ -297,13 +292,7 @@ def LatticePolytope(data, desc=None, compute_vertices=True, f.close() if isinstance(data, (file, StringIO.StringIO)): data = read_palp_matrix(data) - if is_Matrix(data): - deprecation(15240, "constructing lattice polytopes from matrices is " - "deprecated!") - if lattice is None: - lattice = ToricLattice(data.nrows()).dual() - data = data.columns(copy=False) - if not is_PointCollection(data) and not isinstance(data, (list, tuple)): + if not is_PointCollection(data) and not isinstance(data, (list, tuple)): try: data = list(data) except TypeError: @@ -359,7 +348,7 @@ def ReflexivePolytope(dim, n): sage: ReflexivePolytope(2, 3) 2-d reflexive polytope #3 in 2-d lattice M - sage: lattice_polytope.ReflexivePolytope(2,3).vertices_pc() + sage: lattice_polytope.ReflexivePolytope(2,3).vertices() M( 1, 0), M( 0, 1), M( 0, -1), @@ -429,7 +418,7 @@ def ReflexivePolytopes(dim): os.path.join(data_location, "reflexive_polytopes_%dd" % dim)) for n, p in enumerate(rp): # Data files have normal form of reflexive polytopes - p.normal_form_pc.set_cache(p._vertices) + p.normal_form.set_cache(p._vertices) p.index.set_cache(n) # Prevents dimension computation later p._dim = dim @@ -476,19 +465,48 @@ def is_LatticePolytope(x): class LatticePolytopeClass(SageObject, collections.Hashable): r""" - Construct a lattice polytope from prepared data. + Create a lattice polytope. + + .. WARNING:: - In most cases you should use :func:`LatticePolytope` for constructing - polytopes. + This class does not perform any checks of correctness of input nor + does it convert input into the standard representation. Use + :func:`LatticePolytope` to construct lattice polytopes. + + Lattice polytopes are immutable, but they cache most of the returned values. INPUT: - - ``points`` -- a :class:`~sage.geometry.point_collection.PointCollection`; + The input can be either: + + - ``points`` -- :class:`~sage.geometry.point_collection.PointCollection`; - ``compute_vertices`` -- boolean. + + or (these parameters must be given as keywords): + + - ``ambient`` -- ambient structure, this polytope *must be a face of* + ``ambient``; + + - ``ambient_vertex_indices`` -- increasing list or tuple of integers, + indices of vertices of ``ambient`` generating this polytope; + + - ``ambient_facet_indices`` -- increasing list or tuple of integers, + indices of facets of ``ambient`` generating this polytope. + + OUTPUT: + + - lattice polytope. + + .. NOTE:: + + Every polytope has an ambient structure. If it was not specified, it is + this polytope itself. """ - def __init__(self, points, compute_vertices): + def __init__(self, points=None, compute_vertices=None, + ambient=None, ambient_vertex_indices=None, + ambient_facet_indices=None): r""" Construct a lattice polytope. @@ -499,9 +517,18 @@ def __init__(self, points, compute_vertices): sage: LatticePolytope([(1,2,3), (4,5,6)]) # indirect test 1-d lattice polytope in 3-d lattice M """ - self._vertices = points - if compute_vertices: - self._compute_dim(compute_vertices=True) + if ambient is None: + self._ambient = self + self._vertices = points + if compute_vertices: + self._compute_dim(compute_vertices=True) + self._ambient_vertex_indices = tuple(range(self.nvertices())) + self._ambient_facet_indices = () + else: + self._ambient = ambient + self._ambient_vertex_indices = tuple(ambient_vertex_indices) + self._ambient_facet_indices = tuple(ambient_facet_indices) + self._vertices = ambient.vertices(self._ambient_vertex_indices) def __eq__(self, other): r""" @@ -537,7 +564,7 @@ def __eq__(self, other): sage: p1 == 0 False """ - return (is_LatticePolytope(other) + return (isinstance(other, LatticePolytopeClass) and self._vertices == other._vertices) @cached_method @@ -602,7 +629,7 @@ def __reduce__(self): TESTS:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() == loads(o.dumps()).vertices_pc() + sage: o.vertices() == loads(o.dumps()).vertices() True """ state = self.__dict__.copy() @@ -623,7 +650,7 @@ def __setstate__(self, state): TESTS:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() == loads(o.dumps()).vertices_pc() + sage: o.vertices() == loads(o.dumps()).vertices() True """ self.__dict__.update(state) @@ -646,7 +673,7 @@ def _compute_dim(self, compute_vertices): TESTS:: sage: p = LatticePolytope(([1], [2], [3]), compute_vertices=False) - sage: p.vertices_pc() # wrong, since these were not vertices + sage: p.vertices() # wrong, since these were not vertices M(1), M(2), M(3) @@ -654,7 +681,7 @@ def _compute_dim(self, compute_vertices): sage: hasattr(p, "_dim") False sage: p._compute_dim(compute_vertices=True) - sage: p.vertices_pc() + sage: p.vertices() M(1), M(3) in 1-d lattice M @@ -681,7 +708,7 @@ def _compute_dim(self, compute_vertices): self._dim = H.rank() if self._dim == 0: self._vertices = PointCollection((p0, ), N) - elif self._dim == self.ambient_dim(): + elif self._dim == self.lattice_dim(): if compute_vertices: points = [N(_) for _ in read_palp_matrix(self.poly_x("v")).columns()] for point in points: @@ -736,8 +763,13 @@ def _compute_faces(self): ... (-1,0,0), (0,-1,0), (0,0,0), (0,0,0)]) sage: p._compute_faces() sage: p.facets() + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. [[0, 3], [2, 3], [0, 1], [1, 2]] """ + # Remove with 19071 deprecations if hasattr(self, "_constructed_as_polar"): # "Polar of polar polytope" computed by poly.x may have the # order of vertices different from the original polytope. Thus, @@ -767,7 +799,7 @@ def _compute_facets(self): in 3-d lattice N """ assert not hasattr(self, "_facet_normals") - if self.dim() == self.ambient_dim(): + if self.dim() == self.lattice_dim(): self._read_equations(self.poly_x("e")) else: sp = self._sublattice_polytope @@ -807,32 +839,39 @@ def _copy_faces(self, other, reverse=False): reflexive polytopes, faces of this polytope and its polar are in inclusion reversing bijection. - .. note:: + .. NOTE:: This function does not perform any checks that this operation makes sense. INPUT: - - ``other`` - another LatticePolytope, whose facial structure will be + - ``other`` -- another LatticePolytope, whose facial structure will be copied - - ``reverse`` - (default: False) if True, the facial structure of the - other polytope will be reversed, i.e. vertices will correspond to - facets etc. + - ``reverse`` -- (default: ``False``) if ``True``, the facial + structure of the other polytope will be reversed, + i.e. vertices will correspond to facets etc. TESTS:: sage: o = lattice_polytope.cross_polytope(3) - sage: p = LatticePolytope(o.vertices_pc()) + sage: p = LatticePolytope(o.vertices()) sage: p._copy_faces(o) + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: str(o.faces()) == str(p.faces()) + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. True sage: c = o.polar() sage: p._copy_faces(c, reverse=True) + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: str(o.faces()) == str(p.faces()) True """ + # Remove with 19071 deprecations self._faces = Sequence([], cr=True) if reverse: for d_faces in reversed(other.faces()): @@ -859,7 +898,7 @@ def _embed(self, data): TESTS:: sage: o = lattice_polytope.cross_polytope(3) - sage: o._embed(o.vertices_pc()) == o.vertices_pc() + sage: o._embed(o.vertices()) == o.vertices() True sage: m = matrix(ZZ, 3) sage: m[0, 0] = 1 @@ -868,7 +907,7 @@ def _embed(self, data): sage: p._embed((0,0)) M(1, 0, 0) """ - if self.ambient_dim() == self.dim(): + if self.lattice_dim() == self.dim(): return data M = self.lattice() if is_PointCollection(data): @@ -895,6 +934,8 @@ def _face_compute_points(self, face): sage: o = lattice_polytope.cross_polytope(3) sage: e = o.faces(dim=1)[0] + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: v = e.__dict__.pop("_points", None) # points may be cached already sage: "_points" in e.__dict__ False @@ -902,6 +943,7 @@ def _face_compute_points(self, face): sage: "_points" in e.__dict__ True """ + # Remove with 19071 deprecations m = self.distances().matrix_from_rows(face._facets) cols = m.columns(copy=False) points = [i for i, col in enumerate(cols) if sum(col) == 0] @@ -917,6 +959,8 @@ def _face_split_points(self, face): sage: c = lattice_polytope.cross_polytope(3).polar() sage: f = c.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: v = f.__dict__.pop("_interior_points", None) sage: "_interior_points" in f.__dict__ False @@ -934,12 +978,15 @@ def _face_split_points(self, face): Vertices don't have boundary:: sage: f = c.faces(dim=0)[0] + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: c._face_split_points(f) sage: len(f._interior_points) 1 sage: len(f._boundary_points) 0 """ + # Remove with 19071 deprecations if face.npoints() == 1: # Vertex face._interior_points = face.points() face._boundary_points = Sequence([], int, check=False) @@ -1031,40 +1078,13 @@ def _palp(self, command, reduce_dimension=False): if self.dim() <= 0: raise ValueError(("Cannot run \"%s\" for the zero-dimensional " + "polytope!\nPolytope: %s") % (command, self)) - if self.dim() < self.ambient_dim() and not reduce_dimension: + if self.dim() < self.lattice_dim() and not reduce_dimension: raise ValueError(("Cannot run PALP for a %d-dimensional polytope " + - "in a %d-dimensional space!") % (self.dim(), self.ambient_dim())) - if _always_use_files: - fn = _palp(command, [self], reduce_dimension) - f = open(fn) + "in a %d-dimensional space!") % (self.dim(), self.lattice_dim())) + fn = _palp(command, [self], reduce_dimension) + with open(fn) as f: result = f.read() - f.close() - os.remove(fn) - else: - if _palp_dimension is not None: - dot = command.find(".") - command = (command[:dot] + "-%dd" % _palp_dimension + - command[dot:]) - p = subprocess.Popen(command, shell=True, bufsize=2048, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True) - stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr) - write_palp_matrix(self._pullback(self._vertices), stdin) - stdin.close() - err = stderr.read() - if len(err) > 0: - raise RuntimeError(("Error executing \"%s\" for the given polytope!" - + "\nPolytope: %s\nVertices:\n%s\nOutput:\n%s") % (command, - self, self.vertices_pc(), err)) - result = stdout.read() - # We program around an issue with subprocess (this so far seems to - # only be an issue on Cygwin). - try: - p.terminate() - except OSError: - pass + os.remove(fn) if (not result or "!" in result or "failed." in result or @@ -1081,8 +1101,8 @@ def _pullback(self, data): INPUT: - - ``data`` - rational point or matrix of points (as columns) in the - ambient space + - ``data`` -- rational point or matrix of points (as columns) in the + ambient space OUTPUT: The same point(s) in the coordinates of the affine subspace space spanned by this polytope. @@ -1090,7 +1110,7 @@ def _pullback(self, data): TESTS:: sage: o = lattice_polytope.cross_polytope(3) - sage: o._pullback(o.vertices_pc().column_matrix()) == o.vertices_pc().column_matrix() + sage: o._pullback(o.vertices().column_matrix()) == o.vertices().column_matrix() True sage: m = matrix(ZZ, 3) sage: m[0, 0] = 1 @@ -1099,7 +1119,7 @@ def _pullback(self, data): sage: p._pullback((0, 0, 0)) [-1, 0] """ - if self.ambient_dim() == self.dim(): + if self.lattice_dim() == self.dim(): return data if data is self._vertices: return self._sublattice_polytope._vertices @@ -1123,7 +1143,7 @@ def _read_equations(self, data): TESTS: For a reflexive polytope construct the polar polytope:: sage: p = LatticePolytope([(1,0), (0,1), (-1,-1)]) - sage: p.vertices_pc() + sage: p.vertices() M( 1, 0), M( 0, 1), M(-1, -1) @@ -1146,7 +1166,7 @@ def _read_equations(self, data): For a non-reflexive polytope cache facet equations:: sage: p = LatticePolytope([(1,0), (0,2), (-1,-3 )]) - sage: p.vertices_pc() + sage: p.vertices() M( 1, 0), M( 0, 2), M(-1, -3) @@ -1331,7 +1351,7 @@ def _read_nef_partitions(self, data): We make a copy of the octahedron since direct use of this function may destroy cache integrity and lead so strange effects in other doctests:: - sage: o_copy = LatticePolytope(o.vertices_pc()) + sage: o_copy = LatticePolytope(o.vertices()) sage: "_nef_partitions" in o_copy.__dict__ False sage: o_copy._read_nef_partitions(s) @@ -1350,10 +1370,10 @@ def _read_nef_partitions(self, data): nvertices = self.nvertices() data.readline() # Skip M/N information nef_vertices = read_palp_matrix(data) - if self.vertices_pc().column_matrix() != nef_vertices: + if self.vertices().column_matrix() != nef_vertices: # It seems that we SHOULD worry about this... # raise RunTimeError, "nef.x changed the order of vertices!" - trans = [self.vertices_pc().index(v) + trans = [self.vertices().index(v) for v in nef_vertices.columns()] for i, p in enumerate(partitions): partitions[i] = [trans[v] for v in p] @@ -1412,20 +1432,107 @@ def _repr_(self): sage: o._repr_() '3-d reflexive polytope in 3-d lattice M' """ - parts = ["%d-d" % self.dim(), "lattice", "polytope", "in"] - try: - if self.is_reflexive(): - parts[1] = "reflexive" - if self.dim() == 2 or self.index.is_in_cache(): - parts.insert(-1, "#%d" % self.index()) - except ValueError: - pass - if is_ToricLattice(self.lattice()): - parts.append(str(self.lattice())) + parts = ["%d-d" % self.dim()] + if self.ambient() is self: + parts.extend(["lattice", "polytope", "in"]) + try: + if self.is_reflexive(): + parts[1] = "reflexive" + if self.dim() == 2 or self.index.is_in_cache(): + parts.insert(-1, "#%d" % self.index()) + except ValueError: + pass + if is_ToricLattice(self.lattice()): + parts.append(str(self.lattice())) + else: + parts.append("%d-d lattice" % self.lattice_dim()) else: - parts.append("%d-d lattice" % self.lattice_dim()) + parts.extend(["face of", str(self.ambient())]) return " ".join(parts) + def _sort_faces(self, faces): + r""" + Return sorted (if necessary) ``faces`` as a tuple. + + This function ensures that zero-dimensional faces are listed in + agreement with the order of corresponding vertices and facets with + facet normals. + + INPUT: + + - ``faces`` -- iterable of :class:`lattice polytopes + `. + + OUTPUT: + + - :class:`tuple` of :class:`lattice polytopes `. + + TESTS:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: # indirect doctest + sage: for i, face in enumerate(o.faces_lp(0)): + ... if face.vertex(0) != o.vertex(i): + ... print "Wrong order!" + """ + faces = tuple(faces) + if len(faces) > 1: # Otherwise there is nothing to sort + if faces[0].nvertices() == 1: + faces = tuple(sorted(faces, + key=lambda f: f._ambient_vertex_indices)) + elif faces[0].dim() == self.dim() - 1 and \ + hasattr(self, "_facet_normals"): + # If we already have facet normals, sort according to them + faces = set(faces) + sorted_faces = [None] * len(faces) + for i, n in enumerate(self.facet_normals()): + for f in faces: + if set(n * f.vertices()) == set([- self.facet_constant(i)]): + sorted_faces[i] = f + faces.remove(f) + break + faces = tuple(sorted_faces) + return faces + + @cached_method + def adjacent(self): + r""" + Return faces adjacent to ``self`` in the ambient face lattice. + + Two *distinct* faces `F_1` and `F_2` of the same face lattice are + **adjacent** if all of the following conditions hold: + + * `F_1` and `F_2` have the same dimension `d`; + + * `F_1` and `F_2` share a facet of dimension `d-1`; + + * `F_1` and `F_2` are facets of some face of dimension `d+1`, unless + `d` is the dimension of the ambient structure. + + OUTPUT: + + - :class:`tuple` of :class:`lattice polytopes `. + + EXAMPLES:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.adjacent() + () + sage: face = o.faces_lp(1)[0] + sage: face.adjacent() + (1-d face of 3-d reflexive polytope in 3-d lattice M, + 1-d face of 3-d reflexive polytope in 3-d lattice M, + 1-d face of 3-d reflexive polytope in 3-d lattice M, + 1-d face of 3-d reflexive polytope in 3-d lattice M) + """ + L = self._ambient.face_lattice() + adjacent = set() + for superface in self.facet_of(): + for facet in self.facets_lp(): + adjacent.update(L.open_interval(facet, superface)) + adjacent.discard(self) + return self._sort_faces(adjacent) + def affine_transform(self, a=1, b=0): r""" Return a*P+b, where P is this lattice polytope. @@ -1450,44 +1557,44 @@ def affine_transform(self, a=1, b=0): EXAMPLES:: sage: o = lattice_polytope.cross_polytope(2) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0), M( 0, 1), M(-1, 0), M( 0, -1) in 2-d lattice M - sage: o.affine_transform(2).vertices_pc() + sage: o.affine_transform(2).vertices() M( 2, 0), M( 0, 2), M(-2, 0), M( 0, -2) in 2-d lattice M - sage: o.affine_transform(1,1).vertices_pc() + sage: o.affine_transform(1,1).vertices() M(2, 1), M(1, 2), M(0, 1), M(1, 0) in 2-d lattice M - sage: o.affine_transform(b=1).vertices_pc() + sage: o.affine_transform(b=1).vertices() M(2, 1), M(1, 2), M(0, 1), M(1, 0) in 2-d lattice M - sage: o.affine_transform(b=(1, 0)).vertices_pc() + sage: o.affine_transform(b=(1, 0)).vertices() M(2, 0), M(1, 1), M(0, 0), M(1, -1) in 2-d lattice M sage: a = matrix(QQ, 2, [1/2, 0, 0, 3/2]) - sage: o.polar().vertices_pc() + sage: o.polar().vertices() N(-1, 1), N( 1, 1), N(-1, -1), N( 1, -1) in 2-d lattice N - sage: o.polar().affine_transform(a, (1/2, -1/2)).vertices_pc() + sage: o.polar().affine_transform(a, (1/2, -1/2)).vertices() M(0, 1), M(1, 1), M(0, -2), @@ -1503,7 +1610,7 @@ def affine_transform(self, a=1, b=0): [(1/2, 0), (0, 3/2), (-1/2, 0), (0, -3/2)] are not in 2-d lattice M! """ - new_vertices = self.vertices_pc() * a + new_vertices = self.vertices() * a if b in QQ: b = vector(QQ, [b]*new_vertices.ncols()) else: @@ -1519,6 +1626,31 @@ def affine_transform(self, a=1, b=0): r._original = self return r + def ambient(self): + r""" + Return the ambient structure of ``self``. + + OUTPUT: + + - lattice polytope containing ``self`` as a face. + + EXAMPLES:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.ambient() + 3-d reflexive polytope in 3-d lattice M + sage: o.ambient() is o + True + sage: face = o.faces_lp(1)[0] + sage: face + 1-d face of 3-d reflexive polytope in 3-d lattice M + sage: face.ambient() + 3-d reflexive polytope in 3-d lattice M + sage: face.ambient() is o + True + """ + return self._ambient + def ambient_dim(self): r""" Return the dimension of the ambient space of this polytope. @@ -1528,10 +1660,200 @@ def ambient_dim(self): sage: o = lattice_polytope.cross_polytope(3) sage: o.ambient_dim() + doctest:...: DeprecationWarning: ambient_dim() is deprecated, please use lattice_dim()! + See http://trac.sagemath.org/19071 for details. 3 """ + deprecation(19071, "ambient_dim() is deprecated, please use lattice_dim()!") return self.lattice().dimension() + + def ambient_facet_indices(self): + r""" + Return indices of facets of the ambient polytope containing ``self``. + + OUTPUT: + + - increasing :class:`tuple` of integers. + + EXAMPLES: + + The polytope itself is not contained in any of its facets:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.ambient_facet_indices() + () + + But each of its other faces is contained in one or more facets:: + + sage: face = o.faces_lp(1)[0] + sage: face.ambient_facet_indices() + (0, 4) + sage: face.vertices() + M(1, 0, 0), + M(0, 1, 0) + in 3-d lattice M + sage: o.facets_lp()[face.ambient_facet_indices()[0]].vertices() + M(1, 0, 0), + M(0, 1, 0), + M(0, 0, -1) + in 3-d lattice M + """ + return self._ambient_facet_indices + + @cached_method + def ambient_point_indices(self): + r""" + Return indices of points of the ambient polytope contained in this one. + + OUTPUT: + + - :class:`tuple` of integers, the order corresponds to the order of + points of this polytope. + + EXAMPLES:: + + sage: cube = lattice_polytope.cross_polytope(3).polar() + sage: face = cube.facets_lp()[0] + sage: face.ambient_point_indices() + (0, 2, 4, 6, 8, 9, 10, 11, 12) + sage: cube.points(face.ambient_point_indices()) == face.points() + True + """ + if self._ambient is self: + return tuple(range(self.npoints())) + points = self._ambient.points() + return tuple(points.index(p) for p in self.points()) + + @cached_method + def ambient_ordered_point_indices(self): + r""" + Return indices of points of the ambient polytope contained in this one. + + OUTPUT: + + - :class:`tuple` of integers such that ambient points in this order are + geometrically ordered, e.g. for an edge points will appear from one + end point to the other. + + EXAMPLES:: + + sage: cube = lattice_polytope.cross_polytope(3).polar() + sage: face = cube.facets_lp()[0] + sage: face.ambient_ordered_point_indices() + (4, 8, 0, 9, 10, 11, 6, 12, 2) + sage: cube.points(face.ambient_ordered_point_indices()) + N(-1, -1, -1), + N(-1, -1, 0), + N(-1, -1, 1), + N(-1, 0, -1), + N(-1, 0, 0), + N(-1, 0, 1), + N(-1, 1, -1), + N(-1, 1, 0), + N(-1, 1, 1) + in 3-d lattice N + """ + if self._ambient is self: + return tuple(range(self.npoints())) + points = self._ambient.points() + return tuple(points.index(p) for p in sorted(self.points())) + + def ambient_vertex_indices(self): + r""" + Return indices of vertices of the ambient structure generating ``self``. + OUTPUT: + + - increasing :class:`tuple` of integers. + + EXAMPLES:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.ambient_vertex_indices() + (0, 1, 2, 3, 4, 5) + sage: face = o.faces_lp(1)[0] + sage: face.ambient_vertex_indices() + (0, 1) + """ + return self._ambient_vertex_indices + + @cached_method + def boundary_point_indices(self): + r""" + Return indices of (relative) boundary lattice points of this polytope. + + OUTPUT: + + - increasing :class:`tuple` of integers. + + EXAMPLES: + + All points but the origin are on the boundary of this square:: + + sage: square = lattice_polytope.cross_polytope(2).polar() + sage: square.points() + N(-1, 1), + N( 1, 1), + N(-1, -1), + N( 1, -1), + N(-1, 0), + N( 0, -1), + N( 0, 0), + N( 0, 1), + N( 1, 0) + in 2-d lattice N + sage: square.boundary_point_indices() + (0, 1, 2, 3, 4, 5, 7, 8) + + For an edge the boundary is formed by the end points:: + + sage: face = square.edges_lp()[0] + sage: face.points() + N(-1, 1), + N(-1, -1), + N(-1, 0) + in 2-d lattice N + sage: face.boundary_point_indices() + (0, 1) + """ + return tuple(i + for i, c in enumerate(self.distances().columns(copy=False)) + if len(c.nonzero_positions()) < self.nfacets()) + + def boundary_points(self): + r""" + Return (relative) boundary lattice points of this polytope. + + OUTPUT: + + - a :class:`point collection `. + + EXAMPLES: + + All points but the origin are on the boundary of this square:: + + sage: square = lattice_polytope.cross_polytope(2).polar() + sage: square.boundary_points() + N(-1, 1), + N( 1, 1), + N(-1, -1), + N( 1, -1), + N(-1, 0), + N( 0, -1), + N( 0, 1), + N( 1, 0) + in 2-d lattice N + + For an edge the boundary is formed by the end points:: + + sage: face = square.edges_lp()[0] + sage: face.boundary_points() + N(-1, 1), + N(-1, -1) + in 2-d lattice N + """ + return self.points(self.boundary_point_indices()) + def dim(self): r""" Return the dimension of this polytope. @@ -1548,7 +1870,7 @@ def dim(self): sage: p = LatticePolytope([(1,0,0), (0,1,0), (-1,0,0), (0,-1,0)]) sage: p.dim() 2 - sage: p.ambient_dim() + sage: p.lattice_dim() 3 """ if not hasattr(self, "_dim"): @@ -1615,17 +1937,17 @@ def distances(self, point=None): ArithmeticError: vector is not in free module """ if point is not None: - if self.dim() < self.ambient_dim(): + if self.dim() < self.lattice_dim(): return self._sublattice_polytope.distances( self._pullback(point)) return (vector(QQ, point) * self.facet_normals() + self.facet_constants()) - if self.dim() < self.ambient_dim(): + if self.dim() < self.lattice_dim(): return self._sublattice_polytope.distances() try: return self._distances except AttributeError: - P = self.points_pc() + P = self.points() n = self.npoints() self._distances = matrix(ZZ, [F * P + vector(ZZ, [c]*n) for F, c in zip(self.facet_normals(), self.facet_constants())]) @@ -1654,7 +1976,7 @@ def dual_lattice(self): try: return self.lattice().dual() except AttributeError: - return ZZ**self.ambient_dim() + return ZZ**self.lattice_dim() def edges(self): r""" @@ -1665,12 +1987,207 @@ def edges(self): sage: o = lattice_polytope.cross_polytope(3) sage: len(o.edges()) + doctest:...: DeprecationWarning: the output of this method will change, use edges_lp instead to get edges as lattice polytopes + See http://trac.sagemath.org/19071 for details. + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. 12 sage: o.edges() [[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]] """ + deprecation(19071, "the output of this method will change, use edges_lp" + " instead to get edges as lattice polytopes") return self.faces(dim=1) + def edges_lp(self): + r""" + Return edges (faces of dimension 1) of ``self``. + + OUTPUT: + + - :class:`tuple` of :class:`lattice polytopes `. + + EXAMPLES:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.edges_lp() + (1-d face of 3-d reflexive polytope in 3-d lattice M, + ... + 1-d face of 3-d reflexive polytope in 3-d lattice M) + sage: len(o.edges_lp()) + 12 + """ + return self.faces_lp(dim=1) + + @cached_method + def face_lattice(self): + r""" + Return the face lattice of ``self``. + + This lattice will have the empty polytope as the bottom and this + polytope itself as the top. + + OUTPUT: + + - :class:`finite poset ` of + :class:`lattice polytopes `. + + EXAMPLES: + + Let's take a look at the face lattice of a square:: + + sage: square = LatticePolytope([(0,0), (1,0), (1,1), (0,1)]) + sage: L = square.face_lattice() + sage: L + Finite poset containing 10 elements with distinguished linear extension + + To see all faces arranged by dimension, you can do this:: + + sage: for level in L.level_sets(): print level + [-1-d face of 2-d lattice polytope in 2-d lattice M] + [0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M] + [1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M] + [2-d lattice polytope in 2-d lattice M] + + For a particular face you can look at its actual vertices... :: + + sage: face = L.level_sets()[1][0] + sage: face.vertices() + M(0, 0) + in 2-d lattice M + + ... or you can see the index of the vertex of the original polytope that + corresponds to the above one:: + + sage: face.ambient_vertex_indices() + (0,) + sage: square.vertex(0) + M(0, 0) + + An alternative to extracting faces from the face lattice is to use + :meth:`faces` method:: + + sage: face is square.faces_lp(dim=0)[0] + True + + The advantage of working with the face lattice directly is that you + can (relatively easily) get faces that are related to the given one:: + + sage: face = L.level_sets()[1][0] + sage: D = L.hasse_diagram() + sage: D.neighbors(face) + [1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + -1-d face of 2-d lattice polytope in 2-d lattice M] + + However, you can achieve some of this functionality using + :meth:`facets`, :meth:`facet_of`, and :meth:`adjacent` methods:: + + sage: face = square.faces_lp(0)[0] + sage: face + 0-d face of 2-d lattice polytope in 2-d lattice M + sage: face.vertices() + M(0, 0) + in 2-d lattice M + sage: face.facets_lp() + (-1-d face of 2-d lattice polytope in 2-d lattice M,) + sage: face.facet_of() + (1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M) + sage: face.adjacent() + (0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M) + sage: face.adjacent()[0].vertices() + M(1, 0) + in 2-d lattice M + + Note that if ``p`` is a face of ``superp``, then the face + lattice of ``p`` consists of (appropriate) faces of ``superp``:: + + sage: superp = LatticePolytope([(1,2,3,4), (5,6,7,8), + ... (1,2,4,8), (1,3,9,7)]) + sage: superp.face_lattice() + Finite poset containing 16 elements with distinguished linear extension + sage: superp.face_lattice().top() + 3-d lattice polytope in 4-d lattice M + sage: p = superp.facets_lp()[0] + sage: p + 2-d face of 3-d lattice polytope in 4-d lattice M + sage: p.face_lattice() + Finite poset containing 8 elements with distinguished linear extension + sage: p.face_lattice().bottom() + -1-d face of 3-d lattice polytope in 4-d lattice M + sage: p.face_lattice().top() + 2-d face of 3-d lattice polytope in 4-d lattice M + sage: p.face_lattice().top() is p + True + """ + if self._ambient is self: + # We need to compute face lattice on our own. + vertex_to_facets = [] + facet_to_vertices = [[] for _ in range(self.nfacets())] + for i, vertex in enumerate(self.vertices()): + facets = [j for j, normal in enumerate(self.facet_normals()) + if normal * vertex + self.facet_constant(j) == 0] + vertex_to_facets.append(facets) + for j in facets: + facet_to_vertices[j].append(i) + + def LPFace(vertices, facets): + if not facets: + return self + return LatticePolytopeClass(ambient=self, + ambient_vertex_indices=vertices, + ambient_facet_indices=facets) + + return Hasse_diagram_from_incidences( + vertex_to_facets, facet_to_vertices, LPFace, key = id(self)) + else: + # Get face lattice as a sublattice of the ambient one + allowed_indices = frozenset(self._ambient_vertex_indices) + L = DiGraph() + empty = self._ambient.face_lattice().bottom() + L.add_vertex(0) # In case it is the only one + dfaces = [empty] + faces = [empty] + face_to_index = {empty:0} + next_index = 1 + next_d = 0 # Dimension of faces to be considered next. + while next_d < self.dim(): + ndfaces = [] + for face in dfaces: + face_index = face_to_index[face] + for new_face in face.facet_of(): + if not allowed_indices.issuperset( + new_face._ambient_vertex_indices): + continue + if new_face in ndfaces: + new_face_index = face_to_index[new_face] + else: + ndfaces.append(new_face) + face_to_index[new_face] = next_index + new_face_index = next_index + next_index += 1 + L.add_edge(face_index, new_face_index) + faces.extend(ndfaces) + dfaces = ndfaces + next_d += 1 + if self.dim() > 0: + # Last level is very easy to build, so we do it separately + # even though the above cycle could do it too. + faces.append(self) + for face in dfaces: + L.add_edge(face_to_index[face], next_index) + D = {i:f for i,f in enumerate(faces)} + L.relabel(D) + return FinitePoset(L, faces, key = id(self)) + def faces(self, dim=None, codim=None): r""" Return the sequence of proper faces of this polytope. @@ -1684,6 +2201,8 @@ def faces(self, dim=None, codim=None): sage: o = lattice_polytope.cross_polytope(3) sage: o.faces() + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. [ [[0], [1], [2], [3], [4], [5]], [[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]], @@ -1712,7 +2231,7 @@ def faces(self, dim=None, codim=None): the polytope itself, i.e. it has no proper faces at all:: sage: p = LatticePolytope([[1]]) - sage: p.vertices_pc() + sage: p.vertices() M(1) in 1-d lattice M sage: p.faces() @@ -1727,6 +2246,8 @@ def faces(self, dim=None, codim=None): ... IndexError: list index out of range """ + deprecation(19071, "the output of this method will change, use faces_lp" + " instead to get faces as lattice polytopes") try: if dim is None and codim is None: return self._faces @@ -1739,6 +2260,95 @@ def faces(self, dim=None, codim=None): except AttributeError: self._compute_faces() return self.faces(dim, codim) + + def faces_lp(self, dim=None, codim=None): + r""" + Return faces of ``self`` of specified (co)dimension. + + INPUT: + + - ``dim`` -- integer, dimension of the requested faces; + + - ``codim`` -- integer, codimension of the requested faces. + + .. NOTE:: + + You can specify at most one parameter. If you don't give any, then + all faces will be returned. + + OUTPUT: + + - if either ``dim`` or ``codim`` is given, the output will be a + :class:`tuple` of :class:`lattice polytopes `; + + - if neither ``dim`` nor ``codim`` is given, the output will be the + :class:`tuple` of tuples as above, giving faces of all existing + dimensions. If you care about inclusion relations between faces, + consider using :meth:`face_lattice` or :meth:`adjacent`, + :meth:`facet_of`, and :meth:`facets`. + + EXAMPLES: + + Let's take a look at the faces of a square:: + + sage: square = LatticePolytope([(0,0), (1,0), (1,1), (0,1)]) + sage: square.faces_lp() + ((-1-d face of 2-d lattice polytope in 2-d lattice M,), + (0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M, + 0-d face of 2-d lattice polytope in 2-d lattice M), + (1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M), + (2-d lattice polytope in 2-d lattice M,)) + + Its faces of dimension one (i.e., edges):: + + sage: square.faces_lp(dim=1) + (1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M, + 1-d face of 2-d lattice polytope in 2-d lattice M) + + Its faces of codimension one are the same (also edges):: + + sage: square.faces_lp(codim=1) is square.faces_lp(dim=1) + True + + Let's pick a particular face:: + + sage: face = square.faces_lp(dim=1)[0] + + Now you can look at the actual vertices of this face... :: + + sage: face.vertices() + M(0, 0), + M(0, 1) + in 2-d lattice M + + ... or you can see indices of the vertices of the orginal polytope that + correspond to the above ones:: + + sage: face.ambient_vertex_indices() + (0, 3) + sage: square.vertices(face.ambient_vertex_indices()) + M(0, 0), + M(0, 1) + in 2-d lattice M + """ + if dim is not None and codim is not None: + raise ValueError( + "dimension and codimension cannot be specified together!") + dim = self.dim() - codim if codim is not None else dim + if "_faces_lp" not in self.__dict__: + self._faces_lp = tuple(map(self._sort_faces, + self.face_lattice().level_sets())) + if dim is None: + return self._faces_lp + else: + return self._faces_lp[dim + 1] if -1 <= dim <= self.dim() else () def facet_constant(self, i): r""" @@ -1761,7 +2371,7 @@ def facet_constant(self, i): inside it:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -1773,8 +2383,8 @@ def facet_constant(self, i): N(-1, -1, 1) sage: o.facet_constant(0) 1 - sage: p = LatticePolytope(o.vertices_pc()(1,2,3,4,5)) - sage: p.vertices_pc() + sage: p = LatticePolytope(o.vertices()(1,2,3,4,5)) + sage: p.vertices() M( 0, 1, 0), M( 0, 0, 1), M(-1, 0, 0), @@ -1785,8 +2395,8 @@ def facet_constant(self, i): N(-1, 0, 0) sage: p.facet_constant(0) 0 - sage: p = LatticePolytope(o.vertices_pc()(1,2,4,5)) - sage: p.vertices_pc() + sage: p = LatticePolytope(o.vertices()(1,2,4,5)) + sage: p.vertices() M(0, 1, 0), M(0, 0, 1), M(0, -1, 0), @@ -1802,7 +2412,7 @@ def facet_constant(self, i): sage: p = LatticePolytope([(1,-1,1,3), (-1,-1,1,3), (0,0,0,0)]) sage: p 2-d lattice polytope in 4-d lattice M - sage: p.vertices_pc() + sage: p.vertices() M( 1, -1, 1, 3), M(-1, -1, 1, 3), M( 0, 0, 0, 0) @@ -1841,7 +2451,7 @@ def facet_constants(self): For reflexive polytopes all constants are 1:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -1857,7 +2467,7 @@ def facet_constants(self): sage: p = LatticePolytope([(0,0,0,0), (1,1,1,3), ... (1,-1,1,3), (-1,-1,1,3)]) - sage: p.vertices_pc() + sage: p.vertices() M( 0, 0, 0, 0), M( 1, 1, 1, 3), M( 1, -1, 1, 3), @@ -1894,7 +2504,7 @@ def facet_normal(self, i): inside it:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -1906,8 +2516,8 @@ def facet_normal(self, i): N(-1, -1, 1) sage: o.facet_constant(0) 1 - sage: p = LatticePolytope(o.vertices_pc()(1,2,3,4,5)) - sage: p.vertices_pc() + sage: p = LatticePolytope(o.vertices()(1,2,3,4,5)) + sage: p.vertices() M( 0, 1, 0), M( 0, 0, 1), M(-1, 0, 0), @@ -1918,8 +2528,8 @@ def facet_normal(self, i): N(-1, 0, 0) sage: p.facet_constant(0) 0 - sage: p = LatticePolytope(o.vertices_pc()(1,2,4,5)) - sage: p.vertices_pc() + sage: p = LatticePolytope(o.vertices()(1,2,4,5)) + sage: p.vertices() M(0, 1, 0), M(0, 0, 1), M(0, -1, 0), @@ -1935,13 +2545,13 @@ def facet_normal(self, i): sage: p = LatticePolytope([(0,0,0,0), (1,1,1,3), ... (1,-1,1,3), (-1,-1,1,3)]) - sage: p.vertices_pc() + sage: p.vertices() M( 0, 0, 0, 0), M( 1, 1, 1, 3), M( 1, -1, 1, 3), M(-1, -1, 1, 3) in 4-d lattice M - sage: ker = p.vertices_pc().column_matrix().integer_kernel().matrix() + sage: ker = p.vertices().column_matrix().integer_kernel().matrix() sage: ker [ 0 0 3 -1] sage: ker * p.facet_normals() @@ -1977,7 +2587,7 @@ def facet_normals(self): Normals to facets of an octahedron are vertices of a cube:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -2001,7 +2611,7 @@ def facet_normals(self): sage: p = LatticePolytope([(0,0,0,0), (1,1,1,3), ... (1,-1,1,3), (-1,-1,1,3)]) - sage: p.vertices_pc() + sage: p.vertices() M( 0, 0, 0, 0), M( 1, 1, 1, 3), M( 1, -1, 1, 3), @@ -2020,6 +2630,30 @@ def facet_normals(self): self._compute_facets() return self._facet_normals + @cached_method + def facet_of(self): + r""" + Return elements of the ambient face lattice having ``self`` as a facet. + + OUTPUT: + + - :class:`tuple` of :class:`lattice polytopes `. + + EXAMPLES:: + + sage: square = LatticePolytope([(0,0), (1,0), (1,1), (0,1)]) + sage: square.facet_of() + () + sage: face = square.faces_lp(0)[0] + sage: len(face.facet_of()) + 2 + sage: face.facet_of()[1] + 1-d face of 2-d lattice polytope in 2-d lattice M + """ + L = self._ambient.face_lattice() + H = L.hasse_diagram() + return self._sort_faces(f for f in H.neighbors_out(L(self))) + def facets(self): r""" Return the sequence of facets of this polytope (i.e. faces of @@ -2029,14 +2663,40 @@ def facets(self): sage: o = lattice_polytope.cross_polytope(3) sage: o.facets() + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. [[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]] Facets are the same as faces of codimension one:: sage: o.facets() is o.faces(codim=1) + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. True """ + deprecation(19071, "the output of this method will change, use facets_lp" + " instead to get facets as lattice polytopes") return self.faces(codim=1) + + def facets_lp(self): + r""" + Return facets (faces of codimension 1) of ``self``. + + OUTPUT: + + - :class:`tuple` of :class:`lattice polytopes `. + + EXAMPLES:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.facets_lp() + (2-d face of 3-d reflexive polytope in 3-d lattice M, + ... + 2-d face of 3-d reflexive polytope in 3-d lattice M) + sage: len(o.facets_lp()) + 8 + """ + return self.faces_lp(codim=1) # Dictionaries of normal forms _rp_dict = [None]*4 @@ -2066,13 +2726,13 @@ def index(self): Note that polytopes with the same index are not necessarily the same:: - sage: d.vertices_pc() + sage: d.vertices() M( 1, 0), M( 0, 1), M(-1, 0), M( 0, -1) in 2-d lattice M - sage: lattice_polytope.ReflexivePolytope(2,3).vertices_pc() + sage: lattice_polytope.ReflexivePolytope(2,3).vertices() M( 1, 0), M( 0, 1), M( 0, -1), @@ -2082,13 +2742,13 @@ def index(self): But they are in the same `GL(Z^n)` orbit and have the same normal form:: - sage: d.normal_form_pc() + sage: d.normal_form() M( 1, 0), M( 0, 1), M( 0, -1), M(-1, 0) in 2-d lattice M - sage: lattice_polytope.ReflexivePolytope(2,3).normal_form_pc() + sage: lattice_polytope.ReflexivePolytope(2,3).normal_form() M( 1, 0), M( 0, 1), M( 0, -1), @@ -2103,16 +2763,85 @@ def index(self): if LatticePolytopeClass._rp_dict[dim] is None: rp_dict = dict() for n, p in enumerate(ReflexivePolytopes(dim)): - rp_dict[p.normal_form_pc().matrix()] = n + rp_dict[p.normal_form().matrix()] = n LatticePolytopeClass._rp_dict[dim] = rp_dict - return LatticePolytopeClass._rp_dict[dim][self.normal_form_pc().matrix()] + return LatticePolytopeClass._rp_dict[dim][self.normal_form().matrix()] + @cached_method + def interior_point_indices(self): + r""" + Return indices of (relative) interior lattice points of this polytope. + + OUTPUT: + + - increasing :class:`tuple` of integers. + + EXAMPLES: + + The origin is the only interior point of this square:: + + sage: square = lattice_polytope.cross_polytope(2).polar() + sage: square.points() + N(-1, 1), + N( 1, 1), + N(-1, -1), + N( 1, -1), + N(-1, 0), + N( 0, -1), + N( 0, 0), + N( 0, 1), + N( 1, 0) + in 2-d lattice N + sage: square.interior_point_indices() + (6,) + + Its edges also have a single interior point each:: + + sage: face = square.edges_lp()[0] + sage: face.points() + N(-1, 1), + N(-1, -1), + N(-1, 0) + in 2-d lattice N + sage: face.interior_point_indices() + (2,) + """ + return tuple(i + for i, c in enumerate(self.distances().columns(copy=False)) + if len(c.nonzero_positions()) == self.nfacets()) + + def interior_points(self): + r""" + Return (relative) boundary lattice points of this polytope. + + OUTPUT: + + - a :class:`point collection `. + + EXAMPLES: + + The origin is the only interior point of this square:: + + sage: square = lattice_polytope.cross_polytope(2).polar() + sage: square.interior_points() + N(0, 0) + in 2-d lattice N + + Its edges also have a single interior point each:: + + sage: face = square.edges_lp()[0] + sage: face.interior_points() + N(-1, 0) + in 2-d lattice N + """ + return self.points(self.interior_point_indices()) + @cached_method def is_reflexive(self): r""" Return True if this polytope is reflexive. - EXAMPLES: The 3-dimensional octahedron is reflexive (and 4318 other + EXAMPLES: The 3-dimensional octahedron is reflexive (and 4319 other 3-polytopes):: sage: o = lattice_polytope.cross_polytope(3) @@ -2132,7 +2861,7 @@ def is_reflexive(self): sage: p.is_reflexive() False """ - return self.dim() == self.ambient_dim() and \ + return self.dim() == self.lattice_dim() and \ all(c == 1 for c in self.facet_constants()) def lattice(self): @@ -2150,6 +2879,24 @@ def lattice(self): """ return self._vertices.module() + def lattice_dim(self): + r""" + Return the dimension of the ambient lattice of ``self``. + + OUTPUT: + + - integer. + + EXAMPLES:: + + sage: p = LatticePolytope([(1,0)]) + sage: p.lattice_dim() + 2 + sage: p.dim() + 0 + """ + return self.lattice().dimension() + def linearly_independent_vertices(self): r""" Return a maximal set of linearly independent vertices. @@ -2170,7 +2917,7 @@ def linearly_independent_vertices(self): sage: L.linearly_independent_vertices() (0,) """ - return self.vertices_pc().matrix().pivot_rows() + return self.vertices().matrix().pivot_rows() def nef_partitions(self, keep_symmetric=False, keep_products=True, keep_projections=True, hodge_numbers=False): @@ -2380,31 +3127,8 @@ def nfacets(self): """ return len(self.facet_normals()) if self.dim() > 0 else 0 - def normal_form(self): - r""" - Return the normal form of ``self`` as a matrix. - - EXAMPLES: - - We compute the normal form of the "diamond":: - - sage: o = lattice_polytope.cross_polytope(3) - sage: o.normal_form() - doctest:...: DeprecationWarning: normal_form() output will change, - please use normal_form_pc().column_matrix() instead - or consider using normal_form_pc() directly! - See http://trac.sagemath.org/15240 for details. - [ 1 0 0 0 0 -1] - [ 0 1 0 0 -1 0] - [ 0 0 1 -1 0 0] - """ - deprecation(15240, "normal_form() output will change, " - "please use normal_form_pc().column_matrix() instead " - "or consider using normal_form_pc() directly!") - return self.normal_form_pc().column_matrix() - @cached_method - def normal_form_pc(self, algorithm="palp", permutation=False): + def normal_form(self, algorithm="palp", permutation=False): r""" Return the normal form of vertices of ``self``. @@ -2456,13 +3180,13 @@ def normal_form_pc(self, algorithm="palp", permutation=False): We compute the normal form of the "diamond":: sage: d = LatticePolytope([(1,0), (0,1), (-1,0), (0,-1)]) - sage: d.vertices_pc() + sage: d.vertices() M( 1, 0), M( 0, 1), M(-1, 0), M( 0, -1) in 2-d lattice M - sage: d.normal_form_pc() + sage: d.normal_form() M( 1, 0), M( 0, 1), M( 0, -1), @@ -2478,7 +3202,7 @@ def normal_form_pc(self, algorithm="palp", permutation=False): You can get it in its normal form (in the default lattice) as :: - sage: lattice_polytope.ReflexivePolytope(2, 3).vertices_pc() + sage: lattice_polytope.ReflexivePolytope(2, 3).vertices() M( 1, 0), M( 0, 1), M( 0, -1), @@ -2498,7 +3222,7 @@ def normal_form_pc(self, algorithm="palp", permutation=False): We can perform the same examples using other algorithms:: sage: o = lattice_polytope.cross_polytope(2) - sage: o.normal_form_pc(algorithm="palp_native") + sage: o.normal_form(algorithm="palp_native") M( 1, 0), M( 0, 1), M( 0, -1), @@ -2506,14 +3230,14 @@ def normal_form_pc(self, algorithm="palp", permutation=False): in 2-d lattice M sage: o = lattice_polytope.cross_polytope(2) - sage: o.normal_form_pc(algorithm="palp_modified") + sage: o.normal_form(algorithm="palp_modified") M( 1, 0), M( 0, 1), M( 0, -1), M(-1, 0) in 2-d lattice M """ - if self.dim() < self.ambient_dim(): + if self.dim() < self.lattice_dim(): raise ValueError("normal form is not defined for %s" % self) if algorithm == "palp": result = read_palp_matrix(self.poly_x("N"), @@ -2537,6 +3261,8 @@ def normal_form_pc(self, algorithm="palp", permutation=False): v.set_immutable() vertices = PointCollection(vertices, M) return (vertices, perm) if permutation else vertices + + normal_form_pc = deprecated_function_alias(19070, normal_form) def _palp_modified_normal_form(self, permutation=False): r""" @@ -2559,7 +3285,7 @@ def _palp_modified_normal_form(self, permutation=False): EXAMPLES:: sage: o = lattice_polytope.cross_polytope(2) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0), M( 0, 1), M(-1, 0), @@ -2586,7 +3312,7 @@ def _palp_modified_normal_form(self, permutation=False): permutations = PM.automorphisms_of_rows_and_columns() permutations = {k:[(perm[0])*p[0], (perm[1])*p[1]] for k, p in enumerate(permutations)} - out = _palp_canonical_order(self.vertices_pc(), PM_max, permutations) + out = _palp_canonical_order(self.vertices(), PM_max, permutations) if permutation: return out else: @@ -2614,7 +3340,7 @@ def _palp_native_normal_form(self, permutation=False): EXAMPLES:: sage: o = lattice_polytope.cross_polytope(2) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0), M( 0, 1), M(-1, 0), @@ -2634,7 +3360,7 @@ def _palp_native_normal_form(self, permutation=False): in 2-d lattice M, (1,4,2,3)) """ PM_max, permutations = self._palp_PM_max(check=True) - out = _palp_canonical_order(self.vertices_pc(), PM_max, permutations) + out = _palp_canonical_order(self.vertices(), PM_max, permutations) if permutation: return out else: @@ -2926,7 +3652,7 @@ def npoints(self): try: return self._npoints except AttributeError: - return len(self.points_pc()) + return len(self.points()) def nvertices(self): r""" @@ -2962,7 +3688,7 @@ def origin(self): M(0, 0) sage: p = LatticePolytope(([1],[2])) - sage: p.points_pc() + sage: p.points() M(1), M(2) in 1-d lattice M @@ -2977,7 +3703,7 @@ def origin(self): """ origin = self.lattice().zero() try: - return self.points_pc().index(origin) + return self.points().index(origin) except ValueError: pass @@ -3095,7 +3821,7 @@ def plot3d(self, Graphics3d Object """ dim = self.dim() - amb_dim = self.ambient_dim() + amb_dim = self.lattice_dim() if dim > 3: raise ValueError("%d-dimensional polytopes can not be plotted in 3D!" % self.dim()) elif amb_dim > 3: @@ -3110,9 +3836,9 @@ def plot3d(self, show_pindices, pindex_color, index_shift) elif dim == 3: - vertices = self.vertices_pc() + vertices = self.vertices() if show_points or show_pindices: - points = self.points_pc()[self.nvertices():] + points = self.points()[self.nvertices():] else: vertices = [vector(ZZ, list(self.vertex(i))+[0]*(3-amb_dim)) for i in range(self.nvertices())] @@ -3125,19 +3851,18 @@ def plot3d(self, pplot += IndexFaceSet([self.traverse_boundary()], vertices, opacity=facet_opacity, rgbcolor=facet_color) elif dim == 3: - if facet_colors is not None: - for i, f in enumerate(self.facets()): - pplot += IndexFaceSet([f.traverse_boundary()], - vertices, opacity=facet_opacity, rgbcolor=facet_colors[i]) - else: - pplot += IndexFaceSet([f.traverse_boundary() for f in self.facets()], - vertices, opacity=facet_opacity, rgbcolor=facet_color) + if facet_colors is None: + facet_colors = [facet_color] * self.nfacets() + for f, c in zip(self.facets_lp(), facet_colors): + pplot += IndexFaceSet([[self.vertices().index(v) for v in f.vertices(f.traverse_boundary())]], + vertices, opacity=facet_opacity, rgbcolor=c) if show_edges: if dim == 1: pplot += line3d(vertices, thickness=edge_thickness, rgbcolor=edge_color) else: - for e in self.edges(): - pplot += line3d([vertices[e.vertices()[0]], vertices[e.vertices()[1]]], + for e in self.edges_lp(): + start, end = e.ambient_vertex_indices() + pplot += line3d([vertices[start], vertices[end]], thickness=edge_thickness, rgbcolor=edge_color) if show_vertices: pplot += point3d(vertices, size=vertex_size, rgbcolor=vertex_color) @@ -3165,7 +3890,7 @@ def polyhedron(self): Return the Polyhedron object determined by this polytope's vertices. EXAMPLES:: - + sage: o = lattice_polytope.cross_polytope(2) sage: o.polyhedron() A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices @@ -3194,7 +3919,7 @@ def point(self, i): EXAMPLES: First few points are actually vertices:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -3209,7 +3934,7 @@ def point(self, i): sage: o.point(6) M(0, 0, 0) - sage: o.points_pc() + sage: o.points() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -3219,32 +3944,15 @@ def point(self, i): M( 0, 0, 0) in 3-d lattice M """ - return self.points_pc()[i] + return self.points()[i] - def points(self): - r""" - Return all lattice points of this polytope as columns of a matrix. - - EXAMPLES:: - - sage: o = lattice_polytope.cross_polytope(3) - sage: o.points() - doctest:...: DeprecationWarning: points() output will change, - please use points_pc().column_matrix() instead or - consider using points_pc() directly! - See http://trac.sagemath.org/15240 for details. - [ 1 0 0 -1 0 0 0] - [ 0 1 0 0 -1 0 0] - [ 0 0 1 0 0 -1 0] - """ - deprecation(15240, "points() output will change, " - "please use points_pc().column_matrix() instead " - "or consider using points_pc() directly!") - return self.points_pc().column_matrix() - - def points_pc(self): + def points(self, *args, **kwds): r""" Return all lattice points of ``self``. + + INPUT: + + - any arguments given will be passed on to the returned object. OUTPUT: @@ -3255,7 +3963,7 @@ def points_pc(self): Lattice points of the octahedron and its polar cube:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.points_pc() + sage: o.points() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -3265,7 +3973,7 @@ def points_pc(self): M( 0, 0, 0) in 3-d lattice M sage: cube = o.polar() - sage: cube.points_pc() + sage: cube.points() N(-1, -1, 1), N( 1, -1, 1), N(-1, 1, 1), @@ -3298,18 +4006,25 @@ def points_pc(self): Lattice points of a 2-dimensional diamond in a 3-dimensional space:: sage: p = LatticePolytope([(1,0,0), (0,1,0), (-1,0,0), (0,-1,0)]) - sage: p.points_pc() + sage: p.points() M( 1, 0, 0), M( 0, 1, 0), M(-1, 0, 0), M( 0, -1, 0), M( 0, 0, 0) in 3-d lattice M + + Only two of the above points: + + sage: p.points(1, 3) + M(0, 1, 0), + M(0, -1, 0) + in 3-d lattice M We check that points of a zero-dimensional polytope can be computed:: sage: p = LatticePolytope([[1]]) - sage: p.points_pc() + sage: p.points() M(1) in 1-d lattice M """ @@ -3324,8 +4039,13 @@ def points_pc(self): for point in points: point.set_immutable() self._points = PointCollection(points, M) - return self._points + if args or kwds: + return self._points(*args, **kwds) + else: + return self._points + points_pc = deprecated_function_alias(19070, points) + def polar(self): r""" Return the polar polytope, if this polytope is reflexive. @@ -3428,11 +4148,12 @@ def poly_x(self, keys, reduce_dimension=False): """ return self._palp("poly.x -f" + keys, reduce_dimension) + @cached_method def skeleton(self): r""" Return the graph of the one-skeleton of this polytope. - EXAMPLES: We construct the one-skeleton graph for the "diamond":: + EXAMPLES:: sage: d = lattice_polytope.cross_polytope(2) sage: g = d.skeleton() @@ -3441,17 +4162,13 @@ def skeleton(self): sage: g.edges() [(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)] """ - try: - return self._skeleton - except AttributeError: - skeleton = Graph() - skeleton.add_vertices(self.skeleton_points(1)) - for e in self.faces(dim=1): - points = e.ordered_points() - for i in range(len(points)-1): - skeleton.add_edge(points[i], points[i+1]) - self._skeleton = skeleton - return skeleton + skeleton = Graph() + skeleton.add_vertices(self.skeleton_points(1)) + for edge in self.edges_lp(): + points = edge.ambient_ordered_point_indices() + for i in range(len(points) - 1): + skeleton.add_edge(points[i], points[i + 1]) + return skeleton.copy(immutable=True) def skeleton_points(self, k=1): r""" @@ -3494,8 +4211,8 @@ def skeleton_points(self, k=1): if k >= self.dim(): return range(self.npoints()) skeleton = set([]) - for face in self.faces(dim=k): - skeleton.update(face.points()) + for face in self.faces_lp(dim=k): + skeleton.update(face.ambient_point_indices()) skeleton = sorted(skeleton) return skeleton @@ -3525,13 +4242,13 @@ def skeleton_show(self, normal=None): ... NotImplementedError: skeleton view is implemented only in 3-d space """ - if self.ambient_dim() != 3: + if self.lattice_dim() != 3: raise NotImplementedError("skeleton view is implemented only in 3-d space") if normal is None: normal = [ZZ.random_element(20),ZZ.random_element(20),ZZ.random_element(20)] normal = matrix(QQ,3,1,list(normal)) projectionm = normal.kernel().basis_matrix() - positions = dict(enumerate([list(c) for c in (projectionm*self.points_pc()).columns(copy=False)])) + positions = dict(enumerate([list(c) for c in (projectionm*self.points()).columns(copy=False)])) self.skeleton().show(pos=positions) def traverse_boundary(self): @@ -3544,25 +4261,20 @@ def traverse_boundary(self): sage: p = lattice_polytope.cross_polytope(2).polar() sage: p.traverse_boundary() - [0, 1, 3, 2] + [2, 0, 1, 3] """ if self.dim() != 2: raise ValueError("Boundary can be traversed only for 2-polytopes!") - edges = self.edges() - l = [0] - for e in edges: - if 0 in e.vertices(): - next = e.vertices()[0] if e.vertices()[0] != 0 else e.vertices()[1] - l.append(next) - prev = 0 + zero_faces = set(self.faces_lp(0)) + l = [self.faces_lp(0)[0]] + prev, next = zero_faces.intersection(l[0].adjacent()) + l = [prev, l[0], next] while len(l) < self.nvertices(): - for e in edges: - if next in e.vertices() and prev not in e.vertices(): - prev = next - next = e.vertices()[0] if e.vertices()[0] != next else e.vertices()[1] - l.append(next) - break - return l + prev, next = zero_faces.intersection(l[-1].adjacent()) + if next == l[-2]: + next = prev + l.append(next) + return [self.vertices().index(v.vertex(0)) for v in l] def vertex(self, i): r""" @@ -3572,7 +4284,7 @@ def vertex(self, i): EXAMPLES: Note that numeration starts with zero:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -3595,7 +4307,7 @@ def vertex_facet_pairing_matrix(self): :meth:`vertices` and :meth:`facets`. EXAMPLES:: - + sage: L = lattice_polytope.cross_polytope(3) sage: L.vertex_facet_pairing_matrix() [0 0 2 2 2 0] @@ -3607,49 +4319,20 @@ def vertex_facet_pairing_matrix(self): [0 2 0 2 0 2] [2 2 0 0 0 2] """ - V = self.vertices_pc() - PM = [] - for i in range(len(self.facets())): - n = self.facet_normal(i) - c = self.facet_constant(i) - row = [] - for v in V: - row.append(n.dot_product(v) + c) - PM.append(row) - PM = matrix(ZZ, PM) + V = self.vertices() + nv = self.nvertices() + PM = matrix(ZZ, [n * V + vector(ZZ, [c] * nv) + for n, c in zip(self.facet_normals(), self.facet_constants())]) PM.set_immutable() return PM - def vertices(self): - r""" - Return vertices of this polytope as columns of a matrix. - - EXAMPLES: The lattice points of the 3-dimensional octahedron and - its polar cube:: - - sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices() - doctest:...: DeprecationWarning: vertices() output will change, - please use vertices_pc().column_matrix() instead or - consider using vertices_pc() directly! - See http://trac.sagemath.org/15240 for details. - [ 1 0 0 -1 0 0] - [ 0 1 0 0 -1 0] - [ 0 0 1 0 0 -1] - sage: cube = o.polar() - sage: cube.vertices() - [-1 1 -1 1 -1 1 -1 1] - [-1 -1 1 1 -1 -1 1 1] - [ 1 1 1 1 -1 -1 -1 -1] - """ - deprecation(15240, "vertices() output will change, " - "please use vertices_pc().column_matrix() instead " - "or consider using vertices_pc() directly!") - return self._vertices.column_matrix() - - def vertices_pc(self): + def vertices(self, *args, **kwds): r""" Return vertices of ``self``. + + INPUT: + + - any arguments given will be passed on to the returned object. OUTPUT: @@ -3660,7 +4343,7 @@ def vertices_pc(self): Vertices of the octahedron and its polar cube are in dual lattices:: sage: o = lattice_polytope.cross_polytope(3) - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -3669,7 +4352,7 @@ def vertices_pc(self): M( 0, 0, -1) in 3-d lattice M sage: cube = o.polar() - sage: cube.vertices_pc() + sage: cube.vertices() N(-1, -1, 1), N( 1, -1, 1), N(-1, 1, 1), @@ -3680,7 +4363,12 @@ def vertices_pc(self): N( 1, 1, -1) in 3-d lattice N """ - return self._vertices + if args or kwds: + return self._vertices(*args, **kwds) + else: + return self._vertices + + vertices_pc = deprecated_function_alias(19070, vertices) def is_NefPartition(x): @@ -3811,7 +4499,7 @@ class NefPartition(SageObject, sage: np.dual() Nef-partition {4, 5, 6} U {1, 3} U {0, 2, 7} - sage: np.nabla_polar().vertices_pc() + sage: np.nabla_polar().vertices() N( 1, 1, 0), N( 0, 0, 1), N( 0, 1, 0), @@ -3827,11 +4515,11 @@ class NefPartition(SageObject, sage: np.dual().Delta_polar() is np.nabla_polar() True - sage: np.Delta(1).vertices_pc() + sage: np.Delta(1).vertices() N(0, 0, 1), N(0, 0, -1) in 3-d lattice N - sage: np.dual().nabla(1).vertices_pc() + sage: np.dual().nabla(1).vertices() N(0, 0, 1), N(0, 0, -1) in 3-d lattice N @@ -4047,7 +4735,7 @@ def Delta(self, i=None): Nef-partition {0, 1, 3} U {2, 4, 5} sage: np.Delta().polar() is o True - sage: np.Delta().vertices_pc() + sage: np.Delta().vertices() N(-1, -1, 1), N( 1, -1, 1), N(-1, 1, 1), @@ -4057,7 +4745,7 @@ def Delta(self, i=None): N(-1, 1, -1), N( 1, 1, -1) in 3-d lattice N - sage: np.Delta(0).vertices_pc() + sage: np.Delta(0).vertices() N( 1, -1, 0), N( 1, 0, 0), N(-1, -1, 0), @@ -4108,7 +4796,7 @@ def Deltas(self): sage: np = o.nef_partitions()[0] sage: np Nef-partition {0, 1, 3} U {2, 4, 5} - sage: np.Delta().vertices_pc() + sage: np.Delta().vertices() N(-1, -1, 1), N( 1, -1, 1), N(-1, 1, 1), @@ -4118,7 +4806,7 @@ def Deltas(self): N(-1, 1, -1), N( 1, 1, -1) in 3-d lattice N - sage: [Delta_i.vertices_pc() for Delta_i in np.Deltas()] + sage: [Delta_i.vertices() for Delta_i in np.Deltas()] [N( 1, -1, 0), N( 1, 0, 0), N(-1, -1, 0), @@ -4129,7 +4817,7 @@ def Deltas(self): N(0, 0, -1), N(0, 1, -1) in 3-d lattice N] - sage: np.nabla_polar().vertices_pc() + sage: np.nabla_polar().vertices() N( 1, -1, 0), N( 0, 1, 1), N( 1, 0, 0), @@ -4177,7 +4865,7 @@ def dual(self): n = nabla_polar.nvertices() vertex_to_part = [-1] * n for i in range(self._nparts): - A = nabla_polar.vertices_pc().matrix()*self.nabla(i).vertices_pc() + A = nabla_polar.vertices().matrix()*self.nabla(i).vertices() for j in range(n): if min(A[j]) == -1: vertex_to_part[j] = i @@ -4236,7 +4924,7 @@ def nabla(self, i=None): sage: np = o.nef_partitions()[0] sage: np Nef-partition {0, 1, 3} U {2, 4, 5} - sage: np.Delta_polar().vertices_pc() + sage: np.Delta_polar().vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -4244,12 +4932,12 @@ def nabla(self, i=None): M( 0, -1, 0), M( 0, 0, -1) in 3-d lattice M - sage: np.nabla(0).vertices_pc() + sage: np.nabla(0).vertices() M( 1, 0, 0), M( 0, 1, 0), M(-1, 0, 0) in 3-d lattice M - sage: np.nabla().vertices_pc() + sage: np.nabla().vertices() M( 1, 0, 1), M( 1, -1, 0), M( 1, 0, -1), @@ -4290,7 +4978,7 @@ def nabla_polar(self): sage: np = o.nef_partitions()[0] sage: np Nef-partition {0, 1, 3} U {2, 4, 5} - sage: np.nabla_polar().vertices_pc() + sage: np.nabla_polar().vertices() N( 1, -1, 0), N( 0, 1, 1), N( 1, 0, 0), @@ -4322,7 +5010,7 @@ def nablas(self): sage: np = o.nef_partitions()[0] sage: np Nef-partition {0, 1, 3} U {2, 4, 5} - sage: np.Delta_polar().vertices_pc() + sage: np.Delta_polar().vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -4330,7 +5018,7 @@ def nablas(self): M( 0, -1, 0), M( 0, 0, -1) in 3-d lattice M - sage: [nabla_i.vertices_pc() for nabla_i in np.nablas()] + sage: [nabla_i.vertices() for nabla_i in np.nablas()] [M( 1, 0, 0), M( 0, 1, 0), M(-1, 0, 0) @@ -4565,6 +5253,8 @@ def __init__(self, polytope, vertices, facets): sage: p = lattice_polytope.cross_polytope(2) sage: p.faces() + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. [ [[0], [1], [2], [3]], [[0, 3], [2, 3], [0, 1], [1, 2]] @@ -4583,6 +5273,8 @@ def __reduce__(self): sage: p = lattice_polytope.cross_polytope(2) sage: f = p.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: fl = loads(f.dumps()) sage: f.vertices() == fl.vertices() True @@ -4609,6 +5301,8 @@ def _repr_(self): sage: o = lattice_polytope.cross_polytope(3) sage: f = o.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: f._repr_() '[0, 1, 5]' """ @@ -4625,6 +5319,8 @@ def boundary_points(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.boundary_points() [0, 2, 4, 6, 8, 9, 11, 12] """ @@ -4643,6 +5339,8 @@ def facets(self): sage: o = lattice_polytope.cross_polytope(3) sage: edge = o.faces(dim=1)[0] + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: edge.facets() [0, 1] @@ -4651,6 +5349,8 @@ def facets(self): sage: edge [1, 5] sage: o.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. [0, 1, 5] sage: o.facets()[1] [1, 3, 5] @@ -4670,11 +5370,13 @@ def index_of_face_in_lattice(self): sage: L = LatticePolytope([[1,0],[1,-1],[-1,0],[-1,-1]]) sage: F = L.faces() + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face = F[1][0] # take the first 1-dimensional face sage: face.index_of_face_in_lattice() 1 """ - S = span(self._polytope.vertices_pc()(self._vertices)) + S = span(self._polytope.vertices()(self._vertices)) return S.index_in(self._polytope.lattice()) def interior_points(self): @@ -4688,6 +5390,8 @@ def interior_points(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.interior_points() [10] """ @@ -4707,6 +5411,8 @@ def nboundary_points(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.nboundary_points() 8 """ @@ -4721,6 +5427,8 @@ def nfacets(self): sage: o = lattice_polytope.cross_polytope(3) sage: edge = o.faces(dim=1)[0] + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: edge.nfacets() 2 """ @@ -4736,6 +5444,8 @@ def ninterior_points(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.ninterior_points() 1 """ @@ -4754,6 +5464,8 @@ def npoints(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.npoints() 9 """ @@ -4772,6 +5484,8 @@ def nvertices(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.nvertices() 4 """ @@ -4790,6 +5504,8 @@ def ordered_points(self): sage: o = lattice_polytope.cross_polytope(3) sage: c = o.polar() sage: e = c.edges()[0] + doctest:...: DeprecationWarning: the output of this method will change, use edges_lp instead to get edges as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: e.vertices() [0, 1] sage: e.ordered_points() @@ -4797,7 +5513,7 @@ def ordered_points(self): """ if len(self.vertices()) != 2: raise ValueError("Order of points is defined for edges only!") - pcol = self._polytope.points_pc() + pcol = self._polytope.points() start = pcol[self.vertices()[0]] end = pcol[self.vertices()[1]] primitive = vector(QQ, end - start) @@ -4823,6 +5539,8 @@ def points(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.points() [0, 2, 4, 6, 8, 9, 10, 11, 12] """ @@ -4843,9 +5561,15 @@ def traverse_boundary(self): sage: c = lattice_polytope.cross_polytope(3).polar() sage: f = c.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: f.vertices() [0, 2, 4, 6] sage: f.traverse_boundary() + doctest:...: DeprecationWarning: the output of this method will change, use faces_lp instead to get faces as lattice polytopes + See http://trac.sagemath.org/19071 for details. + doctest:...: DeprecationWarning: the output of this method will change, use edges_lp instead to get edges as lattice polytopes + See http://trac.sagemath.org/19071 for details. [0, 4, 6, 2] """ if self not in self._polytope.faces(dim=2): @@ -4878,6 +5602,8 @@ def vertices(self): sage: o = lattice_polytope.cross_polytope(3) sage: cube = o.polar() sage: face = cube.facets()[0] + doctest:...: DeprecationWarning: the output of this method will change, use facets_lp instead to get facets as lattice polytopes + See http://trac.sagemath.org/19071 for details. sage: face.vertices() [0, 2, 4, 6] """ @@ -4932,9 +5658,9 @@ def _palp(command, polytopes, reduce_dimension=False): if p.dim() == 0: raise ValueError(("Cannot run \"%s\" for the zero-dimensional " + "polytope!\nPolytope: %s") % (command, p)) - if p.dim() < p.ambient_dim() and not reduce_dimension: + if p.dim() < p.lattice_dim() and not reduce_dimension: raise ValueError(("Cannot run PALP for a %d-dimensional polytope " + - "in a %d-dimensional space!") % (p.dim(), p.ambient_dim())) + "in a %d-dimensional space!") % (p.dim(), p.lattice_dim())) write_palp_matrix(p._pullback(p._vertices), input_file) input_file.close() output_file_name = tmp_filename() @@ -4985,7 +5711,7 @@ def _palp_canonical_order(V, PM_max, permutations): TESTS:: sage: L = lattice_polytope.cross_polytope(2) - sage: V = L.vertices_pc() + sage: V = L.vertices() sage: PM_max, permutations = L._palp_PM_max(check=True) sage: from sage.geometry.lattice_polytope import _palp_canonical_order sage: _palp_canonical_order(V, PM_max, permutations) @@ -5026,7 +5752,7 @@ def _palp_convert_permutation(permutation): PALP specifies a permutation group element by its domain. Furthermore, it only supports permutations of up to 62 objects and labels these by - `0 \dots 9', 'a \dots z', and 'A \dots Z'. + `0 \dots 9`, `a \dots z`, and `A \dots Z`. INPUT: @@ -5034,8 +5760,7 @@ def _palp_convert_permutation(permutation): OUTPUT: - A :class:`permutation group element - `. + A :class:`permutation group element `. EXAMPLES:: @@ -5195,33 +5920,15 @@ def all_cached_data(polytopes): sage: o = lattice_polytope.cross_polytope(3) sage: lattice_polytope.all_cached_data([o]) - sage: o.faces() - [ - [[0], [1], [2], [3], [4], [5]], - [[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]], - [[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]] - ] - - However, you cannot use it for polytopes that are constructed as - polar polytopes of others:: - - sage: lattice_polytope.all_cached_data([o.polar()]) - Traceback (most recent call last): - ... - ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces! """ all_polars(polytopes) all_points(polytopes) - all_faces(polytopes) reflexive = [p for p in polytopes if p.is_reflexive()] all_nef_partitions(reflexive) polar = [p.polar() for p in reflexive] all_points(polar) all_nef_partitions(polar) - for p in polytopes + polar: - for d_faces in p.faces(): - for face in d_faces: - face.boundary_points() + def all_faces(polytopes): r""" @@ -5239,12 +5946,8 @@ def all_faces(polytopes): sage: o = lattice_polytope.cross_polytope(3) sage: lattice_polytope.all_faces([o]) - sage: o.faces() - [ - [[0], [1], [2], [3], [4], [5]], - [[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]], - [[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]] - ] + doctest:...: DeprecationWarning: this function will have no effect on face lattice computation + See http://trac.sagemath.org/19071 for details. However, you cannot use it for polytopes that are constructed as polar polytopes of others:: @@ -5254,6 +5957,7 @@ def all_faces(polytopes): ... ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces! """ + deprecation(19071, "this function will have no effect on face lattice computation") result_name = _palp("poly.x -fi", polytopes, reduce_dimension=True) result = open(result_name) for p in polytopes: @@ -5330,7 +6034,7 @@ def all_points(polytopes): sage: o = lattice_polytope.cross_polytope(3) sage: lattice_polytope.all_points([o]) - sage: o.points_pc() + sage: o.points() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -5388,52 +6092,6 @@ def all_polars(polytopes): all_facet_equations = all_polars -_always_use_files = True - - -def always_use_files(new_state=None): - r""" - Set or get the way of using PALP for lattice polytopes. - - INPUT: - - - ``new_state`` - (default:None) if specified, must be ``True`` or ``False``. - - OUTPUT: The current state of using PALP. If ``True``, files are used - for all calls to PALP, otherwise pipes are used for single polytopes. - While the latter may have some advantage in speed, the first method - is more reliable when working with large outputs. The initial state - is ``True``. - - EXAMPLES:: - - sage: lattice_polytope.always_use_files() - doctest:...: DeprecationWarning: using PALP via pipes is deprecated and - will be removed, if you have a use case for this, - please email Andrey Novoseltsev - See http://trac.sagemath.org/15240 for details. - True - sage: p = LatticePolytope(([1], [20])) - sage: p.npoints() - 20 - - Now let's use pipes instead of files:: - - sage: lattice_polytope.always_use_files(False) - False - sage: p = LatticePolytope(([1], [20])) - sage: p.npoints() - 20 - """ - deprecation(15240, "using PALP via pipes is deprecated and will be removed," - " if you have a use case for this, please email Andrey " - "Novoseltsev") - global _always_use_files - if new_state is not None: - _always_use_files = new_state - return _always_use_files - - def convex_hull(points): r""" Compute the convex hull of the given points. @@ -5473,11 +6131,11 @@ def convex_hull(points): if H.rank() == 0: return [p0] elif H.rank() == N.rank(): - vpoints = list(LatticePolytope(vpoints, lattice=N).vertices_pc()) + vpoints = list(LatticePolytope(vpoints, lattice=N).vertices()) else: H_points = [H.coordinates(p) for p in vpoints] H_polytope = LatticePolytope(H_points) - vpoints = (H_polytope.vertices_pc() * H.basis_matrix()).rows(copy=False) + vpoints = (H_polytope.vertices() * H.basis_matrix()).rows(copy=False) vpoints = [p+p0 for p in vpoints] return vpoints @@ -5499,7 +6157,7 @@ def cross_polytope(dim): sage: o = lattice_polytope.cross_polytope(3) sage: o 3-d reflexive polytope in 3-d lattice M - sage: o.vertices_pc() + sage: o.vertices() M( 1, 0, 0), M( 0, 1, 0), M( 0, 0, 1), @@ -5513,89 +6171,6 @@ def cross_polytope(dim): vertices += [-v for v in vertices] return LatticePolytope(vertices, compute_vertices=False) -octahedron = deprecated_function_alias(15240, cross_polytope) - - -def filter_polytopes(f, polytopes, subseq=None, print_numbers=False): - r""" - Use the function ``f`` to filter polytopes in a list. - - INPUT: - - - - ``f`` - filtering function, it must take one - argument, a lattice polytope, and return True or False. - - - ``polytopes`` - list of polytopes. - - - ``subseq`` - (default: None) list of integers. If it - is specified, only polytopes with these numbers will be - considered. - - - ``print_numbers`` - (default: False) if True, the - number of the current polytope will be printed on the screen before - calling ``f``. - - - OUTPUT: a list of integers -- numbers of polytopes in the given - list, that satisfy the given condition (i.e. function - ``f`` returns True) and are elements of subseq, if it - is given. - - EXAMPLES: Consider a sequence of cross-polytopes:: - - sage: polytopes = Sequence([lattice_polytope.cross_polytope(n) - ....: for n in range(2, 7)], cr=True) - sage: polytopes - [ - 2-d reflexive polytope #3 in 2-d lattice M, - 3-d reflexive polytope in 3-d lattice M, - 4-d reflexive polytope in 4-d lattice M, - 5-d reflexive polytope in 5-d lattice M, - 6-d reflexive polytope in 6-d lattice M - ] - - This filters polytopes of dimension at least 4:: - - sage: lattice_polytope.filter_polytopes(lambda p: p.dim() >= 4, polytopes) - doctest:...: DeprecationWarning: filter_polytopes is deprecated, - use standard tools instead - See http://trac.sagemath.org/15240 for details. - [2, 3, 4] - - For long tests you can see the current progress:: - - sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, print_numbers=True) - 0 - 1 - 2 - 3 - 4 - [3, 4] - - Here we consider only some of the polytopes:: - - sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, [2, 3, 4], print_numbers=True) - 2 - 3 - 4 - [3, 4] - """ - deprecation(15240, "filter_polytopes is deprecated, " - "use standard tools instead") - if subseq == []: - return [] - elif subseq is None: - subseq = range(len(polytopes)) - result = [] - for n in subseq: - if print_numbers: - print n - os.sys.stdout.flush() - if f(polytopes[n]): - result.append(n) - return result - def integral_length(v): """ @@ -5673,7 +6248,7 @@ def positive_integer_relations(points): sage: p = LatticePolytope([(1,0,0), (0,1,0), ... (-1,-1,0), (0,0,1), (-1,0,-1)]) - sage: p.points_pc() + sage: p.points() M( 1, 0, 0), M( 0, 1, 0), M(-1, -1, 0), @@ -5685,7 +6260,7 @@ def positive_integer_relations(points): We can compute linear relations between its points in the following way:: - sage: p.points_pc().matrix().kernel().echelonized_basis_matrix() + sage: p.points().matrix().kernel().echelonized_basis_matrix() [ 1 0 0 1 1 0] [ 0 1 1 -1 -1 0] [ 0 0 0 0 0 1] @@ -5694,11 +6269,11 @@ def positive_integer_relations(points): numbers. This function transforms them in such a way, that all coefficients are non-negative integers:: - sage: lattice_polytope.positive_integer_relations(p.points_pc().column_matrix()) + sage: lattice_polytope.positive_integer_relations(p.points().column_matrix()) [1 0 0 1 1 0] [1 1 1 0 0 0] [0 0 0 0 0 1] - sage: lattice_polytope.positive_integer_relations(ReflexivePolytope(2,1).vertices_pc().column_matrix()) + sage: lattice_polytope.positive_integer_relations(ReflexivePolytope(2,1).vertices().column_matrix()) [2 1 1] """ points = points.transpose().base_extend(QQ) @@ -5742,46 +6317,7 @@ def positive_integer_relations(points): return relations.change_ring(ZZ) -def projective_space(dim): - r""" - Return a simplex of the given dimension, corresponding to - `P_{dim}`. - - EXAMPLES: We construct 3- and 4-dimensional simplexes:: - - sage: p = lattice_polytope.projective_space(3) - doctest:...: DeprecationWarning: this function is deprecated, - perhaps toric_varieties.P(n) is what you are looking for? - See http://trac.sagemath.org/15240 for details. - sage: p - 3-d reflexive polytope in 3-d lattice M - sage: p.vertices_pc() - M( 1, 0, 0), - M( 0, 1, 0), - M( 0, 0, 1), - M(-1, -1, -1) - in 3-d lattice M - sage: p = lattice_polytope.projective_space(4) - sage: p - 4-d reflexive polytope in 4-d lattice M - sage: p.vertices_pc() - M( 1, 0, 0, 0), - M( 0, 1, 0, 0), - M( 0, 0, 1, 0), - M( 0, 0, 0, 1), - M(-1, -1, -1, -1) - in 4-d lattice M - """ - deprecation(15240, "this function is deprecated, " - "perhaps toric_varieties.P(n) is what you are looking for?") - m = matrix(ZZ, dim, dim+1) - for i in range(dim): - m[i,i] = 1 - m[i,dim] = -1 - return LatticePolytope(m.columns(), compute_vertices=False) - - -def read_all_polytopes(file_name, desc=None): +def read_all_polytopes(file_name): r""" Read all polytopes from the given file. @@ -5824,9 +6360,6 @@ def read_all_polytopes(file_name, desc=None): ] sage: os.remove(result_name) """ - if desc is not None: - deprecation(15240, "custom descriptions for lattice polytopes are " - "deprecated and ignored!") polytopes = Sequence([], LatticePolytope, cr=True) f = open(file_name) n = 0 @@ -5838,6 +6371,7 @@ def read_all_polytopes(file_name, desc=None): f.close() return polytopes + def read_palp_matrix(data, permutation=False): r""" Read and return an integer matrix from a string or an opened file. @@ -6044,12 +6578,12 @@ def write_palp_matrix(m, ofile=None, comment="", format=None): EXAMPLES:: sage: o = lattice_polytope.cross_polytope(3) - sage: lattice_polytope.write_palp_matrix(o.vertices_pc(), comment="3D Octahedron") + sage: lattice_polytope.write_palp_matrix(o.vertices(), comment="3D Octahedron") 3 6 3D Octahedron 1 0 0 -1 0 0 0 1 0 0 -1 0 0 0 1 0 0 -1 - sage: lattice_polytope.write_palp_matrix(o.vertices_pc(), format="%4d") + sage: lattice_polytope.write_palp_matrix(o.vertices(), format="%4d") 3 6 1 0 0 -1 0 0 0 1 0 0 -1 0 diff --git a/src/sage/geometry/linear_expression.py b/src/sage/geometry/linear_expression.py index 31aea667882..d52f0ddb04e 100644 --- a/src/sage/geometry/linear_expression.py +++ b/src/sage/geometry/linear_expression.py @@ -2,8 +2,8 @@ Linear Expressions A linear expression is just a linear polynomial in some (fixed) -variables. This class only implements linear expressions for others to -use. +variables (allowing a nonzero constant term). This class only implements +linear expressions for others to use. EXAMPLES:: @@ -12,6 +12,8 @@ Module of linear expressions in variables x, y, z over Rational Field sage: x + 2*y + 3*z + 4 x + 2*y + 3*z + 4 + sage: L(4) + 0*x + 0*y + 0*z + 4 You can also pass coefficients and a constant term to construct linear expressions:: @@ -23,7 +25,7 @@ sage: L([4, 1, 2, 3]) # note: constant is first in single-tuple notation x + 2*y + 3*z + 4 -The linear expressions are a module under the base ring, so you can +The linear expressions are a module over the base ring, so you can add them and multiply them with scalars:: sage: m = x + 2*y + 3*z + 4 @@ -91,9 +93,9 @@ def __init__(self, parent, coefficients, constant, check=True): self._const = constant if check: if self._coeffs.parent() is not self.parent().ambient_module(): - raise ValueError("cofficients are not in the ambient module") + raise ValueError("coefficients are not in the ambient module") if not self._coeffs.is_immutable(): - raise ValueError("cofficients are not immutable") + raise ValueError("coefficients are not immutable") if self._const.parent() is not self.parent().base_ring(): raise ValueError("the constant is not in the base ring") @@ -161,6 +163,32 @@ def coefficients(self): """ return [self._const] + list(self._coeffs) + dense_coefficient_list = coefficients + + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: linear = L([1, 2, 3], 4) + sage: sorted(linear.monomial_coefficients().items()) + [(0, 1), (1, 2), (2, 3), ('b', 4)] + """ + zero = self.parent().base_ring().zero() + d = {i: v for i,v in enumerate(self._coeffs) if v != zero} + if self._const != zero: + d['b'] = self._const + return d + def _repr_vector(self, variable='x'): """ Return a string representation. @@ -351,6 +379,18 @@ def change_ring(self, base_ring): return self return P.change_ring(base_ring)(self) + def __hash__(self): + r""" + TESTS:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: hash(L([0,1])) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self._coeffs) ^ hash(self._const) + def __cmp__(self, other): """ Compare two linear expressions. @@ -379,10 +419,7 @@ def __cmp__(self, other): False """ assert type(self) is type(other) and self.parent() is other.parent() # guaranteed by framework - c = cmp(self._coeffs, other._coeffs) - if c != 0: return c - c = cmp(self._const, other._const) - return c + return cmp(self._coeffs, other._coeffs) or cmp(self._const, other._const) def evaluate(self, point): """ @@ -398,7 +435,7 @@ def evaluate(self, point): The linear expression `Ax + b` evaluated at the point `x`. EXAMPLES:: - + sage: from sage.geometry.linear_expression import LinearExpressionModule sage: L. = LinearExpressionModule(QQ) sage: ex = 2*x + 3* y + 4 @@ -458,9 +495,31 @@ def __init__(self, base_ring, names=tuple()): sage: TestSuite(L).run() """ from sage.categories.modules import Modules - super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring)) + super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring).WithBasis().FiniteDimensional()) self._names = names - + + @cached_method + def basis(self): + """ + Return a basis of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L = LinearExpressionModule(QQ, ('x', 'y', 'z')) + sage: list(L.basis()) + [x + 0*y + 0*z + 0, + 0*x + y + 0*z + 0, + 0*x + 0*y + z + 0, + 0*x + 0*y + 0*z + 1] + """ + from sage.sets.family import Family + gens = self.gens() + d = {i: g for i,g in enumerate(gens)} + d['b'] = self.element_class(self, self.ambient_module().zero(), + self.base_ring().one()) + return Family(range(len(gens)) + ['b'], lambda i: d[i]) + @cached_method def ngens(self): """ @@ -482,7 +541,7 @@ def ngens(self): @cached_method def gens(self): """ - Return the generators. + Return the generators of ``self``. OUTPUT: @@ -554,7 +613,7 @@ def _element_constructor_(self, arg0, arg1=None): x + 2*y + 3*z + 4 Construct from linear expression:: - + sage: M = LinearExpressionModule(ZZ, ('u', 'v', 'w')) sage: m = M([1, 2, 3], 4) sage: L._element_constructor(m) @@ -576,13 +635,13 @@ def _element_constructor_(self, arg0, arg1=None): else: # Construct from list/tuple/iterable:: try: - arg0 = arg0.coefficients() + arg0 = arg0.dense_coefficient_list() except AttributeError: arg0 = list(arg0) const = arg0[0] coeffs = arg0[1:] else: - # arg1 is not None, construct from coeffients and constant term:: + # arg1 is not None, construct from coeffients and constant term coeffs = list(arg0) const = arg1 coeffs = self.ambient_module()(coeffs) @@ -669,7 +728,7 @@ def _coerce_map_from_(self, P): Return whether there is a coercion. TESTS:: - + sage: from sage.geometry.linear_expression import LinearExpressionModule sage: L. = LinearExpressionModule(QQ) sage: M. = LinearExpressionModule(ZZ) diff --git a/src/sage/geometry/newton_polygon.py b/src/sage/geometry/newton_polygon.py index 4d022cc27bd..e8ea034ace8 100644 --- a/src/sage/geometry/newton_polygon.py +++ b/src/sage/geometry/newton_polygon.py @@ -17,6 +17,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.structure.element import Element +from sage.structure.sage_object import op_EQ, op_NE, op_LE, op_GE, op_LT from sage.misc.cachefunc import cached_method from sage.rings.infinity import Infinity @@ -378,114 +379,76 @@ def __call__(self, x): (xd,yd) = vertices[b] return ((x-xg)*yd + (xd-x)*yg) / (xd-xg) - def __eq__(self, other): - """ - TESTS: + def _richcmp_(self, other, op): + r""" + Comparisons of two Newton polygons. + + TESTS:: sage: from sage.geometry.newton_polygon import NewtonPolygon + sage: NP1 = NewtonPolygon([ (0,0), (1,1), (3,6) ]) sage: NP2 = NewtonPolygon([ (0,0), (1,1), (2,6), (3,6) ]) sage: NP1 == NP2 True - """ - if not isinstance(other, NewtonPolygon_element): - return False - return self._polyhedron == other._polyhedron - - def __ne__(self, other): - """ - TESTS: - - sage: from sage.geometry.newton_polygon import NewtonPolygon - sage: NP1 = NewtonPolygon([ (0,0), (1,1), (3,6) ]) - sage: NP2 = NewtonPolygon([ (0,0), (1,1), (2,6), (3,6) ]) sage: NP1 != NP2 False - """ - return not (self == other) - def __le__(self, other): - """ - INPUT: - - - ``other`` -- an other Newton polygon - - OUTPUT: - - Return True is this Newton polygon lies below ``other`` - - EXAMPLES: + sage: NP1 >= NP1 and NP2 >= NP2 + True + sage: NP1 > NP1 or NP2 > NP2 + False - sage: from sage.geometry.newton_polygon import NewtonPolygon sage: NP1 = NewtonPolygon([ (0,0), (1,1), (2,6) ]) sage: NP2 = NewtonPolygon([ (0,0), (1,3/2) ], last_slope=2) + sage: NP3 = NP1 + NP2 + sage: NP1 <= NP2 False - - sage: NP1 + NP2 <= NP1 + sage: NP3 <= NP1 True - sage: NP1 + NP2 <= NP2 + sage: NP3 <= NP2 True - """ - if not isinstance(other, NewtonPolygon_element): - raise TypeError("Impossible to compare a Newton Polygon with something else") - if self.last_slope() > other.last_slope(): - return False - for v in other.vertices(): - if not v in self._polyhedron: - return False - return True - def __lt__(self, other): - """ - TESTS: - - sage: from sage.geometry.newton_polygon import NewtonPolygon - sage: NP1 = NewtonPolygon([ (0,0), (1,1), (2,6) ]) - sage: NP2 = NewtonPolygon([ (0,0), (1,3/2) ], last_slope=2) sage: NP1 < NP1 False sage: NP1 < NP2 False - sage: NP1 + NP2 < NP2 - True - """ - return self <= other and self != other - - def __ge__(self, other): - """ - TESTS: - - sage: from sage.geometry.newton_polygon import NewtonPolygon - sage: NP1 = NewtonPolygon([ (0,0), (1,1), (2,6) ]) - sage: NP2 = NewtonPolygon([ (0,0), (1,3/2) ], last_slope=2) sage: NP1 >= NP2 False - sage: NP1 >= NP1 + NP2 - True - sage: NP2 >= NP1 + NP2 + sage: NP1 >= NP3 True - """ - return other <= self - - def __gt__(self, other): - """ - TESTS: - sage: from sage.geometry.newton_polygon import NewtonPolygon - sage: NP1 = NewtonPolygon([ (0,0), (1,1), (2,6) ]) - sage: NP2 = NewtonPolygon([ (0,0), (1,3/2) ], last_slope=2) sage: NP1 > NP1 False sage: NP1 > NP2 False - sage: NP1 > NP1 + NP2 + sage: NP1 >= NP3 and NP2 >= NP3 and NP3 <= NP1 and NP3 <= NP2 + True + sage: NP1 > NP3 and NP2 > NP3 + True + sage: NP3 < NP2 and NP3 < NP1 True """ - return other <= self and self != other + if self._polyhedron == other._polyhedron: + return op == op_EQ or op == op_LE or op == op_GE + elif op == op_NE: + return True + elif op == op_EQ: + return False + + if op == op_LT or op == op_LE: + if self.last_slope() > other.last_slope(): + return False + return all(v in self._polyhedron for v in other.vertices()) + + else: + if self.last_slope() < other.last_slope(): + return False + return all(v in other._polyhedron for v in self.vertices()) def plot(self, **kwargs): """ diff --git a/src/sage/geometry/polyhedron/backend_field.py b/src/sage/geometry/polyhedron/backend_field.py index e0a9b2d3c4a..3a2991ab6a7 100644 --- a/src/sage/geometry/polyhedron/backend_field.py +++ b/src/sage/geometry/polyhedron/backend_field.py @@ -55,6 +55,15 @@ class Polyhedron_field(Polyhedron_base): sage: K. = NumberField(x^2-3) sage: p = Polyhedron([(0,0), (1,0), (1/2, sqrt3/2)]) sage: TestSuite(p).run() + + Check that :trac:`19013` is fixed:: + + sage: K. = NumberField(x^2-x-1, embedding=1.618) + sage: P1 = Polyhedron([[0,1],[1,1],[1,-phi+1]]) + sage: P2 = Polyhedron(ieqs=[[-1,-phi,0]]) + sage: P1.intersection(P2) + The empty polyhedron in (Number Field in phi with defining polynomial + x^2 - x - 1)^2 """ def _is_zero(self, x): """ diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 01e5f4b129a..c19090f27b5 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -12,6 +12,7 @@ # http://www.gnu.org/licenses/ ######################################################################## +import itertools import six from sage.structure.element import Element, coerce_binop, is_Vector @@ -26,8 +27,6 @@ from sage.graphs.graph import Graph -from sage.combinat.cartesian_product import CartesianProduct - from constructor import Polyhedron @@ -120,6 +119,32 @@ def __init__(self, parent, Vrep, Hrep, **kwds): else: self._init_empty_polyhedron() + def __hash__(self): + r""" + TESTS:: + + sage: K. = QuadraticField(2) + sage: p = Polyhedron(vertices=[(0,1,a),(3,a,5)], + ....: rays=[(a,2,3), (0,0,1)], + ....: base_ring=K) + sage: q = Polyhedron(vertices=[(3,a,5),(0,1,a)], + ....: rays=[(0,0,1), (a,2,3)], + ....: base_ring=K) + sage: hash(p) == hash(q) + True + """ + # TODO: find something better *but* fast + return hash((self.dim(), + self.ambient_dim(), + self.n_Hrepresentation(), + self.n_Vrepresentation(), + self.n_equations(), + self.n_facets(), + self.n_inequalities(), + self.n_lines(), + self.n_rays(), + self.n_vertices())) + def _sage_input_(self, sib, coerced): """ Return Sage command to reconstruct ``self``. @@ -291,7 +316,7 @@ def set_adjacent(v1,v2): set_adjacent(Vrep[0], Vrep[1]) return M - def delete(self): + def _delete(self): """ Delete this polyhedron. @@ -305,20 +330,20 @@ def delete(self): EXAMPLES:: sage: p = Polyhedron([(0,0),(1,0),(0,1)]) - sage: p.delete() + sage: p._delete() sage: vertices = [(0,0,0,0),(1,0,0,0),(0,1,0,0),(1,1,0,0),(0,0,1,0),(0,0,0,1)] sage: def loop_polyhedra(): - ... for i in range(0,100): - ... p = Polyhedron(vertices) + ....: for i in range(0,100): + ....: p = Polyhedron(vertices) sage: timeit('loop_polyhedra()') # not tested - random 5 loops, best of 3: 79.5 ms per loop sage: def loop_polyhedra_with_recycling(): - ... for i in range(0,100): - ... p = Polyhedron(vertices) - ... p.delete() + ....: for i in range(0,100): + ....: p = Polyhedron(vertices) + ....: p._delete() sage: timeit('loop_polyhedra_with_recycling()') # not tested - random 5 loops, best of 3: 57.3 ms per loop @@ -433,8 +458,8 @@ def _is_subpolyhedron(self, other): True """ return all( other_H.contains(self_V) - for other_H, self_V in - CartesianProduct(other.Hrepresentation(), self.Vrepresentation()) ) + for other_H in other.Hrepresentation() \ + for self_V in self.Vrepresentation()) def plot(self, point=None, line=None, polygon=None, # None means unspecified by the user @@ -819,6 +844,14 @@ def cdd_Hrepresentation(self): 1 -1 0 1 0 -1 end + + sage: triangle = Polyhedron(vertices = [[1,0],[0,1],[1,1]],base_ring=AA) + sage: triangle.base_ring() + Algebraic Real Field + sage: triangle.cdd_Hrepresentation() + Traceback (most recent call last): + ... + TypeError: The base ring must be ZZ, QQ, or RDF """ from cdd_file_format import cdd_Hrepresentation try: @@ -1891,8 +1924,17 @@ def base_ring(self): OUTPUT: - Either ``QQ`` (exact arithmetic using gmp, default) or ``RDF`` - (double precision floating-point arithmetic) + The ring over which the polyhedron is defined. Must be a + sub-ring of the reals to define a polyhedron, in particular + comparison must be defined. Popular choices are + + * ``ZZ`` (the ring of integers, lattice polytope), + + * ``QQ`` (exact arithmetic using gmp), + + * ``RDF`` (double precision floating-point arithmetic), or + + * ``AA`` (real algebraic field). EXAMPLES:: @@ -1909,7 +1951,7 @@ def center(self): """ Return the average of the vertices. - See also :meth:`interior_point`. + See also :meth:`representative_point`. OUTPUT: @@ -2381,7 +2423,7 @@ def Minkowski_difference(self, other): return P.element_class(P, None, [new_ieqs, new_eqns]) def __sub__(self, other): - """ + r""" Implement minus binary operation Polyhedra are not a ring with respect to dilatation and @@ -2732,6 +2774,17 @@ def intersection(self, other): A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex sage: _.Vrepresentation() (A vertex at (1/2, 1/2),) + + TESTS: + + Check that :trac:`19012` is fixed:: + + sage: K. = QuadraticField(5) + sage: P = Polyhedron([[0,0],[0,a],[1,1]]) + sage: Q = Polyhedron(ieqs=[[-1,a,1]]) + sage: P.intersection(Q) + A 2-dimensional polyhedron in (Number Field in a with defining + polynomial x^2 - 5)^2 defined as the convex hull of 4 vertices """ new_ieqs = self.inequalities() + other.inequalities() new_eqns = self.equations() + other.equations() @@ -3107,7 +3160,38 @@ def vertex_graph(self): sage: s4.is_eulerian() True """ - return Graph(self.vertex_adjacency_matrix(), loops=False) + from itertools import combinations + inequalities = self.inequalities() + vertices = self.vertices() + + # Associated to 'v' the inequalities in contact with v + vertex_ineq_incidence = [frozenset([i for i,ineq in enumerate(inequalities) if self._is_zero(ineq.eval(v))]) + for i,v in enumerate(vertices)] + + # the dual incidence structure + ineq_vertex_incidence = [set() for _ in range(len(inequalities))] + for v,ineq_list in enumerate(vertex_ineq_incidence): + for ineq in ineq_list: + ineq_vertex_incidence[ineq].add(v) + + d = self.dim() + n = len(vertices) + X = set(range(n)) + + pairs = [] + for i,j in combinations(range(n),2): + common_ineq = vertex_ineq_incidence[i]&vertex_ineq_incidence[j] + if not common_ineq: # or len(common_ineq) < d-2: + continue + + if len(X.intersection(*[ineq_vertex_incidence[k] for k in common_ineq])) == 2: + pairs.append((i,j)) + + from sage.graphs.graph import Graph + g = Graph() + g.add_vertices(vertices) + g.add_edges((vertices[i],vertices[j]) for i,j in pairs) + return g graph = vertex_graph @@ -3786,7 +3870,7 @@ def lattice_polytope(self, envelope=False): sage: P = Polyhedron( vertices = [(1, 0), (0, 1), (-1, 0), (0, -1)]) sage: lp = P.lattice_polytope(); lp 2-d reflexive polytope #3 in 2-d lattice M - sage: lp.vertices_pc() + sage: lp.vertices() M(-1, 0), M( 0, -1), M( 0, 1), @@ -3804,7 +3888,7 @@ def lattice_polytope(self, envelope=False): lattice polytope. sage: lp = P.lattice_polytope(True); lp 2-d reflexive polytope #5 in 2-d lattice M - sage: lp.vertices_pc() + sage: lp.vertices() M(-1, 0), M( 0, -1), M( 0, 1), @@ -3825,7 +3909,7 @@ def lattice_polytope(self, envelope=False): vertices = [] for v in self.vertex_generator(): vbox = [ set([floor(x),ceil(x)]) for x in v ] - vertices.extend( CartesianProduct(*vbox) ) + vertices.extend( itertools.product(*vbox) ) # construct the (enveloping) lattice polytope from sage.geometry.lattice_polytope import LatticePolytope @@ -3846,7 +3930,7 @@ def _integral_points_PALP(self): sage: Polyhedron(vertices=[(-1,-1),(1,0),(1,1),(0,1)])._integral_points_PALP() [M(-1, -1), M(0, 1), M(1, 0), M(1, 1), M(0, 0)] - sage: Polyhedron(vertices=[(-1/2,-1/2),(1,0),(1,1),(0,1)]).lattice_polytope(True).points_pc() + sage: Polyhedron(vertices=[(-1/2,-1/2),(1,0),(1,1),(0,1)]).lattice_polytope(True).points() M( 0, -1), M(-1, 0), M(-1, -1), @@ -3868,8 +3952,8 @@ def _integral_points_PALP(self): except AttributeError: pass if self.is_lattice_polytope(): - return list(lp.points_pc()) - return [p for p in lp.points_pc() if self.contains(p)] + return list(lp.points()) + return [p for p in lp.points() if self.contains(p)] @cached_method def bounding_box(self, integral=False): @@ -3974,9 +4058,8 @@ def integral_points_count(self,verbose=False): stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: - raise ValueError("The package latte_int must be installed " - "(type 'sage -i latte_int' in a console or " - "'install_package('latte_int')' at a Sage prompt)!\n") + from sage.misc.package import PackageNotFoundError + raise PackageNotFoundError('latte_int') ans, err = latte_proc.communicate(ine) ret_code = latte_proc.poll() @@ -4047,7 +4130,7 @@ def integral_points(self, threshold=100000): sage: pts1 = P.integral_points() # Sage's own code sage: all(P.contains(p) for p in pts1) True - sage: pts2 = LatticePolytope(v).points_pc() # PALP + sage: pts2 = LatticePolytope(v).points() # PALP sage: for p in pts1: p.set_immutable() sage: set(pts1) == set(pts2) True @@ -4149,10 +4232,10 @@ def combinatorial_automorphism_group(self): Permutation Group with generators [(3,4)] """ G = Graph() - for edge in self.vertex_graph().edges(): - i = edge[0] - j = edge[1] - G.add_edge(i+1, j+1, (self.Vrepresentation(i).type(), self.Vrepresentation(j).type()) ) + for u,v in self.vertex_graph().edges(labels=False): + i = u.index() + j = v.index() + G.add_edge(i+1, j+1, (u.type(), v.type()) ) group = G.automorphism_group(edge_labels=True) self._combinatorial_automorphism_group = group diff --git a/src/sage/geometry/polyhedron/base_ZZ.py b/src/sage/geometry/polyhedron/base_ZZ.py index b13c56c65a9..8de9c6e5581 100644 --- a/src/sage/geometry/polyhedron/base_ZZ.py +++ b/src/sage/geometry/polyhedron/base_ZZ.py @@ -304,9 +304,8 @@ def ehrhart_polynomial(self, verbose=False, dual=None, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: - raise ValueError("The package latte_int must be installed " - "(type 'sage -i latte_int' in a console or " - "'install_package('latte_int')' at a Sage prompt)!\n") + from sage.misc.package import PackageNotFoundError + raise PackageNotFoundError('latte_int') ans, err = latte_proc.communicate(ine) ret_code = latte_proc.poll() @@ -450,7 +449,7 @@ def fibration_generator(self, dim): if fiber_vertices not in fibers: yield fiber fibers.update([fiber_vertices]) - plane.delete() + plane._delete() def find_translation(self, translated_polyhedron): """ @@ -548,8 +547,8 @@ def _subpoly_parallel_facets(self): edge_vectors.append([ v_prim*i for i in range(d+1) ]) origin = self.ambient_space().zero() parent = self.parent() - from sage.combinat.cartesian_product import CartesianProduct - for edges in CartesianProduct(*edge_vectors): + from itertools import product + for edges in product(*edge_vectors): v = [] point = origin for e in edges: diff --git a/src/sage/geometry/polyhedron/double_description.py b/src/sage/geometry/polyhedron/double_description.py index 3bff500b362..451e4638b90 100644 --- a/src/sage/geometry/polyhedron/double_description.py +++ b/src/sage/geometry/polyhedron/double_description.py @@ -628,7 +628,7 @@ def dim(self): return self._A.ncols() def __repr__(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py index 6208724c85f..2425070aca6 100644 --- a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py +++ b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py @@ -199,6 +199,8 @@ def __init__(self, base_ring, dim, inequalities, equations): [] """ super(Hrep2Vrep, self).__init__(base_ring, dim) + inequalities = [list(x) for x in inequalities] + equations = [list(x) for x in equations] A = self._init_Vrep(inequalities, equations) DD = Algorithm(A).run() self._extract_Vrep(DD) @@ -230,7 +232,7 @@ def _init_Vrep(self, inequalities, equations): return self._pivot_inequalities(A) def _split_linear_subspace(self): - """ + r""" Split the linear subspace in a generator with `x_0\not=0` and the remaining generators with `x_0=0`. @@ -437,7 +439,8 @@ def __init__(self, base_ring, dim, vertices, rays, lines): [] """ super(Vrep2Hrep, self).__init__(base_ring, dim) - assert len(vertices) > 0 + if rays or lines: + assert len(vertices) > 0 A = self._init_Vrep(vertices, rays, lines) DD = Algorithm(A).run() self._extract_Hrep(DD) @@ -501,7 +504,7 @@ def is_trivial(ray): self.equations = self._linear_subspace.matrix().rows() def _repr_(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 8446f5df7bb..d35ab1690f6 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -131,6 +131,19 @@ def __init__(self, polyhedron, V_indices, H_indices): self._ambient_Vrepresentation = tuple( polyhedron.Vrepresentation(i) for i in V_indices ) self._ambient_Hrepresentation = tuple( polyhedron.Hrepresentation(i) for i in H_indices ) + def __hash__(self): + r""" + TESTS:: + + sage: P = Polyhedron([[0,0],[0,1],[23,3],[9,12]]) + sage: map(hash, P.faces(1)) # random + [2377119663630407734, + 2377136578164722109, + 5966674064902575359, + 4795242501625591634] + """ + return hash((self._polyhedron, self._ambient_Vrepresentation_indices)) + def vertex_generator(self): """ Return a generator for the vertices of the face. diff --git a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py index 96b2bc42eeb..f3fc6f54125 100644 --- a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py +++ b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py @@ -116,7 +116,7 @@ def __call__(self, x): return v def _repr_(self): - """ + r""" Return a string representation EXAMPLES:: diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index 12b2a832870..b1368de04bc 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -605,6 +605,51 @@ def cuboctahedron(self): [-1, 1, 0], [-1, 0,-1], [-1,-1, 0] ] return Polyhedron(vertices=v, base_ring=ZZ) + def icosidodecahedron(self, exact=True): + """ + Return the Icosidodecahedron + + The Icosidodecahedron is a polyhedron with twenty triangular faces and + twelve pentagonal faces. For more information see the + :wikipedia:`Icosidodecahedron`. + + INPUT: + + - ``exact`` -- (boolean, default ``True``) If ``False`` use an + approximate ring for the coordinates. + + EXAMPLES:: + + sage: gr = polytopes.icosidodecahedron() + sage: gr.f_vector() + (1, 30, 60, 32, 1) + + TESTS:: + + sage: polytopes.icosidodecahedron(exact=False) + A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 30 vertices + """ + from sage.rings.number_field.number_field import QuadraticField + from itertools import product + + K = QuadraticField(5, 'sqrt5') + one = K.one() + phi = (one+K.gen())/2 + + gens = [((-1)**a*one/2, (-1)**b*phi/2, (-1)**c*(one+phi)/2) + for a,b,c in product([0,1],repeat=3)] + gens.extend([(0,0,phi), (0,0,-phi)]) + + verts = [] + for p in AlternatingGroup(3): + verts.extend(p(x) for x in gens) + + if exact: + return Polyhedron(vertices=verts,base_ring=K) + else: + verts = [(RR(x),RR(y),RR(z)) for x,y,z in verts] + return Polyhedron(vertices=verts) + def buckyball(self, exact=True, base_ring=None): """ Return the bucky ball. diff --git a/src/sage/geometry/polyhedron/palp_database.py b/src/sage/geometry/polyhedron/palp_database.py index 1d71e13366e..4fb51d9312a 100644 --- a/src/sage/geometry/polyhedron/palp_database.py +++ b/src/sage/geometry/polyhedron/palp_database.py @@ -134,7 +134,7 @@ def _palp_Popen(self): return Popen(["class.x", "-b2a", "-di", self._data_basename], stdout=PIPE) def _read_vertices(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -158,7 +158,7 @@ def _read_vertices(self, stdout, rows, cols): return m def _read_vertices_transposed(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -402,7 +402,7 @@ class Reflexive4dHodge(PALPreader): Read the PALP database for Hodge numbers of 4d polytopes. The database is very large and not installed by default. You can - install it with the command ``install_package('polytopes_db_4d')``. + install it with the shell command ``sage -i polytopes_db_4d``. INPUT: @@ -428,9 +428,9 @@ def __init__(self, h11, h21, data_basename=None, **kwds): TESTS:: - sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge - sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d - + sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge + sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d + """ dim = 4 if data_basename is None: diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 9728c86307b..dd5c5198ee5 100644 --- a/src/sage/geometry/polyhedron/parent.py +++ b/src/sage/geometry/polyhedron/parent.py @@ -155,7 +155,7 @@ def recycle(self, polyhedron): sage: p = Polyhedron([(0,0),(1,0),(0,1)]) sage: n = len(p.parent()._Vertex_pool) - sage: p.delete() + sage: p._delete() sage: len(p.parent()._Vertex_pool) - n 3 """ diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index d20bfc61e5e..6b43c095f42 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -395,7 +395,7 @@ def integral_points(self): ....: (-1,2,-1), (-1,2,-2), (-1,1,-2), (-1,-1,2), (-1,-3,2)] sage: P = LatticePolytope_PPL(*v) sage: pts1 = P.integral_points() # Sage's own code - sage: pts2 = LatticePolytope(v).points_pc() # PALP + sage: pts2 = LatticePolytope(v).points() # PALP sage: for p in pts1: p.set_immutable() sage: set(pts1) == set(pts2) True @@ -813,7 +813,6 @@ def affine_lattice_polytope(self): vertices = [ V.coordinates(v-v0) for v in self.vertices() ] return LatticePolytope_PPL(*vertices) - @cached_method def base_projection(self, fiber): """ The projection that maps the sub-polytope ``fiber`` to a @@ -834,7 +833,6 @@ def base_projection(self, fiber): """ return self.ambient_space().quotient(fiber.affine_space()) - @cached_method def base_projection_matrix(self, fiber): """ The projection that maps the sub-polytope ``fiber`` to a @@ -902,18 +900,19 @@ def base_rays(self, fiber, points): ((1),) """ quo = self.base_projection(fiber) - vertices = [] + vertices = set() for p in points: - v = vector(ZZ,quo(p)) + v = quo(p).vector() if v.is_zero(): continue - d = GCD_list(v.list()) - if d>1: - for i in range(0,v.degree()): + d = GCD_list(v.list()) + if d > 1: + v = v.__copy__() + for i in range(v.degree()): v[i] /= d - v.set_immutable() - vertices.append(v) - return tuple(uniq(vertices)) + v.set_immutable() + vertices.add(v) + return tuple(sorted(vertices)) @cached_method def has_IP_property(self): @@ -1069,7 +1068,7 @@ def lattice_automorphism_group(self, points=None, point_labels=None): Permutation Group with generators [(), (3,4), (1,6)(2,5), (1,6)(2,5)(3,4)] Point labels also work for lattice polytopes that are not - full-dimensional, see trac:`16669`:: + full-dimensional, see :trac:`16669`:: sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL sage: lp = LatticePolytope_PPL((1,0,0),(0,1,0),(-1,-1,0)) @@ -1203,8 +1202,9 @@ def embed_in_reflexive_polytope(self, output='hom'): ``self`` (up to a lattice linear transformation) is returned. That is, the domain of the ``output='hom'`` map is returned. If the affine span of ``self`` is less or equal - 2-dimnsional, the output is one of the following three - possibilities:: + 2-dimensional, the output is one of the following three + possibilities: + :func:`~sage.geometry.polyhedron.ppl_lattice_polygon.polar_P2_polytope`, :func:`~sage.geometry.polyhedron.ppl_lattice_polygon.polar_P1xP1_polytope`, or diff --git a/src/sage/geometry/polyhedron/representation.py b/src/sage/geometry/polyhedron/representation.py index cdf0b9a0e92..3249ceb5184 100644 --- a/src/sage/geometry/polyhedron/representation.py +++ b/src/sage/geometry/polyhedron/representation.py @@ -78,6 +78,22 @@ def __getitem__(self, i): """ return self._vector[i] + def __hash__(self): + r""" + TESTS:: + + sage: from sage.geometry.polyhedron.representation import Hrepresentation + sage: pr = Hrepresentation(Polyhedron(vertices = [[1,2,3]]).parent()) + sage: hash(pr) + 1647257843 # 32-bit + 4686581268940269811 # 64-bit + """ + # TODO: ideally the argument self._vector of self should be immutable. + # So that we could change the line below by hash(self._vector). The + # mutability is kept because this argument might be reused (see e.g. + # Hrepresentation._set_data below). + return hash(tuple(self._vector)) + def __cmp__(self, other): """ Compare two representation objects @@ -119,9 +135,7 @@ def __cmp__(self, other): """ if not isinstance(other, PolyhedronRepresentation): return -1 - type_cmp = cmp(type(self), type(other)) - if (type_cmp != 0): return type_cmp - return cmp(self._vector, other._vector) + return cmp(type(self), type(other)) or cmp(self._vector, other._vector) def vector(self, base_ring=None): """ diff --git a/src/sage/geometry/pseudolines.py b/src/sage/geometry/pseudolines.py index 7dcac6a5137..e59d6980f21 100644 --- a/src/sage/geometry/pseudolines.py +++ b/src/sage/geometry/pseudolines.py @@ -88,6 +88,7 @@ a list of `n` bit vectors of length `n-1`, that is .. MATH:: + \begin{array}{ccc} 3 & 2 & 1\\ 3 & 2 & 0\\ diff --git a/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py b/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py index 65b9206f784..c154cef4a11 100644 --- a/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py +++ b/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py @@ -1497,6 +1497,7 @@ def _create_geodesic_ode_system(self): geodesic equations, used by :meth:`geodesics_numerical`. EXAMPLES:: + sage: p, q = var('p,q', domain='real') sage: sphere = ParametrizedSurface3D([cos(q)*cos(p),sin(q)*cos(p),sin(p)],[p,q],'sphere') sage: ode = sphere._create_geodesic_ode_system() @@ -1608,6 +1609,7 @@ def _create_pt_ode_system(self, curve, t): - ``t`` - curve parameter EXAMPLES:: + sage: p, q = var('p,q', domain='real') sage: sphere = ParametrizedSurface3D([cos(q)*cos(p),sin(q)*cos(p),sin(p)],[p,q],'sphere') sage: s = var('s') diff --git a/src/sage/geometry/toric_plotter.py b/src/sage/geometry/toric_plotter.py index 2b3a0ff33cb..388a6e32048 100644 --- a/src/sage/geometry/toric_plotter.py +++ b/src/sage/geometry/toric_plotter.py @@ -607,7 +607,7 @@ def plot_walls(self, walls): Let's also check that the truncating polyhedron is functioning correctly:: - + sage: tp = ToricPlotter({"mode": "box"}, 2, quadrant.rays()) sage: print tp.plot_walls([quadrant]) Graphics object consisting of 2 graphics primitives diff --git a/src/sage/geometry/triangulation/base.pyx b/src/sage/geometry/triangulation/base.pyx index 22024b77365..feda983770b 100644 --- a/src/sage/geometry/triangulation/base.pyx +++ b/src/sage/geometry/triangulation/base.pyx @@ -17,8 +17,7 @@ AUTHORS: # http://www.gnu.org/licenses/ ######################################################################## - - +from sage.misc.fast_methods cimport hash_by_id from sage.structure.sage_object cimport SageObject from sage.structure.parent cimport Parent from sage.categories.sets_cat import Sets @@ -97,6 +96,17 @@ cdef class Point(SageObject): P = point_configuration.reduced_projective_vector_space() self._reduced_projective_vector = P(self.reduced_projective()) + def __hash__(self): + r""" + Hash value for a point in a point configuration + + EXAMPLES:: + + sage: p = PointConfiguration([[0,0],[0,1],[1,1]]) + sage: hash(p[0]) # random + 35822008390213632 + """ + return hash(self._point_configuration) ^ (self._index) cpdef point_configuration(self): r""" @@ -455,6 +465,17 @@ cdef class PointConfiguration_base(Parent): self._pts = tuple([ Point(self, i, proj.column(i), aff.column(i), red.column(i)) for i in range(0,n) ]) + def __hash__(self): + r""" + Hash function. + + TESTS:: + + sage: p = PointConfiguration([[0,0],[0,1]]) + sage: hash(p) # random + 8746748042501 + """ + return hash_by_id( self) cpdef reduced_affine_vector_space(self): """ diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index a723189d42b..07064918416 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -20,9 +20,9 @@ triangulations is implemented natively in this package. However, for more advanced options [TOPCOM]_ needs to be installed. It is available as an optional package for Sage, and you can install it with the -command:: +shell command :: - sage: install_package('topcom') # not tested + sage -i topcom .. note:: diff --git a/src/sage/graphs/asteroidal_triples.pyx b/src/sage/graphs/asteroidal_triples.pyx index 56bfe7d2ce6..fbe72d233ca 100644 --- a/src/sage/graphs/asteroidal_triples.pyx +++ b/src/sage/graphs/asteroidal_triples.pyx @@ -240,7 +240,7 @@ cdef list is_asteroidal_triple_free_C(int n, # We now search for an unseen vertex v = bitset_first_in_complement(seen) - while v!=-1: + while v != -1: # and add it to the queue waiting_list[0] = v waiting_beginning = 0 diff --git a/src/sage/graphs/base/boost_graph.pxd b/src/sage/graphs/base/boost_graph.pxd index 297711629e6..2e021668a56 100644 --- a/src/sage/graphs/base/boost_graph.pxd +++ b/src/sage/graphs/base/boost_graph.pxd @@ -27,6 +27,13 @@ cdef extern from "boost/graph/adjacency_list.hpp" namespace "boost": pass cdef cppclass bidirectionalS: pass + cdef cppclass no_property: + pass + cdef cppclass edge_weight_t: + pass + cdef cppclass property[T, U]: + pass + cdef extern from "boost_interface.cpp": ctypedef unsigned long v_index @@ -40,30 +47,52 @@ cdef extern from "boost_interface.cpp": float average_clustering_coefficient vector[float] clust_of_v - cdef cppclass BoostGraph[OutEdgeListS, VertexListS, DirectedS, EdgeListS]: + cdef cppclass result_distances: + vector[double] distances + vector[v_index] predecessors + + cdef cppclass BoostGraph[OutEdgeListS, VertexListS, DirectedS, EdgeListS, EdgeProperty]: BoostGraph() void add_vertex() v_index num_verts() void add_edge(v_index u, v_index v) + void add_edge(v_index u, v_index v, double w) e_index num_edges() result_ec edge_connectivity() double clustering_coeff(v_index v) result_cc clustering_coeff_all() vector[v_index] dominator_tree(v_index v) + vector[v_index] bandwidth_ordering(bool) + vector[v_index] kruskal_min_spanning_tree() + vector[v_index] prim_min_spanning_tree() + result_distances dijkstra_shortest_paths(v_index s) + result_distances bellman_ford_shortest_paths(v_index s) + vector[vector[double]] johnson_shortest_paths() + +ctypedef property[edge_weight_t, double] EdgeWeight -ctypedef BoostGraph[vecS, vecS, undirectedS, vecS] BoostVecGraph -ctypedef BoostGraph[vecS, vecS, bidirectionalS, vecS] BoostVecDiGraph +ctypedef BoostGraph[vecS, vecS, undirectedS, vecS, no_property] BoostVecGraph +ctypedef BoostGraph[vecS, vecS, bidirectionalS, vecS, no_property] BoostVecDiGraph -ctypedef BoostGraph[setS, vecS, undirectedS, vecS] BoostSetGraph +ctypedef BoostGraph[vecS, vecS, undirectedS, vecS, EdgeWeight] BoostVecWeightedGraph +ctypedef BoostGraph[vecS, vecS, directedS, vecS, EdgeWeight] BoostVecWeightedDiGraphU +ctypedef BoostGraph[vecS, vecS, bidirectionalS, vecS, EdgeWeight] BoostVecWeightedDiGraph + +ctypedef BoostGraph[setS, vecS, undirectedS, vecS, no_property] BoostSetGraph ctypedef fused BoostVecGenGraph: BoostVecGraph BoostVecDiGraph +ctypedef fused BoostWeightedGraph: + BoostVecWeightedGraph + BoostVecWeightedDiGraph + BoostVecWeightedDiGraphU + ctypedef fused BoostGenGraph: BoostVecGraph BoostVecDiGraph - BoostGraph[vecS, vecS, directedS, vecS] + BoostGraph[vecS, vecS, directedS, vecS, no_property] BoostSetGraph - BoostGraph[setS, vecS, directedS, vecS] - BoostGraph[setS, vecS, bidirectionalS, vecS] + BoostGraph[setS, vecS, directedS, vecS, no_property] + BoostGraph[setS, vecS, bidirectionalS, vecS, no_property] diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index 8b167b3eeea..0663cb34d25 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -34,6 +34,11 @@ with ``delete()``. :func:`clustering_coeff` | Returns the clustering coefficient of all vertices in the graph. :func:`edge_connectivity` | Returns the edge connectivity of the graph. :func:`dominator_tree` | Returns a dominator tree of the graph. + :func:`bandwidth_heuristics` | Uses heuristics to approximate the bandwidth of the graph. + :func:`min_spanning_tree` | Computes a minimum spanning tree of a (weighted) graph. + :func:`shortest_paths` | Uses Dijkstra or Bellman-Ford algorithm to compute the single-source shortest paths. + :func:`johnson_shortest_paths` | Uses Johnson algorithm to compute the all-pairs shortest paths. + :func:`johnson_closeness_centrality` | Uses Johnson algorithm to compute the closeness centrality of all vertices. Functions --------- @@ -66,6 +71,66 @@ cdef boost_graph_from_sage_graph(BoostGenGraph *g, g_sage): for u,v in g_sage.edge_iterator(labels=None): g.add_edge(vertex_to_int[u], vertex_to_int[v]) +cdef boost_weighted_graph_from_sage_graph(BoostWeightedGraph *g, + g_sage, + weight_function = None): + r""" + Initializes the Boost weighted graph ``g`` to be equal to ``g_sage``. + + The Boost graph ``*g`` must represent an empty weighted graph. The edge + weights are chosen as follows, and they must be convertible to floats, + otherwise an error is raised. + + - If ``weight_function`` is not ``None``, this function is used. + + - If ``weight_function`` is ``None`` and ``g`` is weighted, the edge labels + of ``g`` are used; in other words, the weight of an edge ``e=(u,v,l)`` is + ``l``. + + - Otherwise, all weights are set to 1. + + In particular, the ``weight_function`` must be a function which inputs an + edge ``e`` and outputs a number. + """ + + from sage.graphs.generic_graph import GenericGraph + + if not isinstance(g_sage, GenericGraph): + raise ValueError("The input parameter must be a Sage graph.") + + if g.num_verts() > 0: + raise ValueError("The Boost graph in input must be empty") + + N = g_sage.num_verts() + cdef dict vertex_to_int = {v:i for i,v in enumerate(g_sage.vertices())} + + for i in range(N): + g.add_vertex() + + if weight_function is not None: + for e in g_sage.edge_iterator(): + try: + g.add_edge(vertex_to_int[e[0]], + vertex_to_int[e[1]], + float(weight_function(e))) + + except (ValueError, TypeError): + raise ValueError("The weight function cannot find the" + + " weight of " + str(e) + ".") + + elif g_sage.weighted(): + for u,v,w in g_sage.edge_iterator(): + try: + g.add_edge(vertex_to_int[u], vertex_to_int[v], float(w)) + except (ValueError, TypeError): + raise ValueError("The weight function cannot find the" + + " weight of " + str((u,v,w)) + ".") + else: + for u,v in g_sage.edge_iterator(labels=False): + g.add_edge(vertex_to_int[u], vertex_to_int[v], 1) + + + cdef boost_edge_connectivity(BoostVecGenGraph *g): r""" Computes the edge connectivity of the input Boost graph. @@ -362,3 +427,636 @@ cpdef dominator_tree(g, root, return_dict = False): return g else: return Graph(edges) + + +cpdef bandwidth_heuristics(g, algorithm = 'cuthill_mckee'): + r""" + Uses Boost heuristics to approximate the bandwidth of the input graph. + + The bandwidth `bw(M)` of a matrix `M` is the smallest integer `k` such that + all non-zero entries of `M` are at distance `k` from the diagonal. The + bandwidth `bw(g)` of an undirected graph `g` is the minimum bandwidth of + the adjacency matrix of `g`, over all possible relabellings of its vertices + (for more information, see the + :mod:`~sage.graphs.graph_decompositions.bandwidth` + module). + + Unfortunately, exactly computing the bandwidth is NP-hard (and an + exponential algorithm is implemented in Sagemath in routine + :func:`~sage.graphs.graph_decompositions.bandwidth.bandwidth`). Here, we + implement two heuristics to find good orderings: Cuthill-McKee, and King. + + This function works only in undirected graphs, and its running time is + `O(md_{max}\log d_{max})` for the Cuthill-McKee ordering, and + `O(md_{max}^2\log d_{max})` for the King ordering, where `m` is the number + of edges, and `d_{max}` is the maximum degree in the graph. + + INPUT: + + - ``g`` (``Graph``) - the input graph. + + - ``algorithm`` (``'cuthill_mckee'`` or ``'king'``) - the heuristic used to + compute the ordering: Cuthill-McKee, or King. + + OUTPUT: + + A pair ``[bandwidth, ordering]``, where ``ordering`` is the ordering of + vertices, ``bandwidth`` is the bandwidth of that specific ordering (which + is not necessarily the bandwidth of the graph, because this is a heuristic). + + EXAMPLES:: + + sage: from sage.graphs.base.boost_graph import bandwidth_heuristics + sage: bandwidth_heuristics(graphs.PathGraph(10)) + (1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + sage: bandwidth_heuristics(graphs.GridGraph([3,3])) + (3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)]) + sage: bandwidth_heuristics(graphs.GridGraph([3,3]), algorithm='king') + (3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)]) + + TESTS: + + Given an input which is not a graph:: + + sage: from sage.graphs.base.boost_graph import bandwidth_heuristics + sage: bandwidth_heuristics(digraphs.Path(10)) + Traceback (most recent call last): + ... + ValueError: The input g must be a Graph. + sage: bandwidth_heuristics("I am not a graph!") + Traceback (most recent call last): + ... + ValueError: The input g must be a Graph. + + Given a wrong algorithm:: + + from sage.graphs.base.boost_graph import bandwidth_heuristics + sage: bandwidth_heuristics(graphs.PathGraph(3), algorithm='tip top') + Traceback (most recent call last): + ... + ValueError: Algorithm 'tip top' not yet implemented. Please contribute. + + Given a graph with no edges:: + + from sage.graphs.base.boost_graph import bandwidth_heuristics + sage: bandwidth_heuristics(Graph()) + (0, []) + sage: bandwidth_heuristics(graphs.RandomGNM(10,0)) + (0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + + """ + from sage.graphs.graph import Graph + + # Tests for errors and trivial cases + if not isinstance(g, Graph): + raise ValueError("The input g must be a Graph.") + if not algorithm in ['cuthill_mckee', 'king']: + raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + if g.num_edges()==0: + return (0, g.vertices()); + + sig_on() + # These variables are automatically deleted when the function terminates. + cdef BoostVecGraph g_boost + cdef vector[v_index] result + cdef dict vertex_to_int = {v:i for i,v in enumerate(g.vertices())} + cdef dict int_to_vertex = {i:v for i,v in enumerate(g.vertices())} + + boost_graph_from_sage_graph(&g_boost, g) + result = g_boost.bandwidth_ordering(algorithm=='cuthill_mckee') + + cdef int n = g.num_verts() + cdef dict pos = {int_to_vertex[result[i]]:i for i in range(n)} + cdef int bandwidth = max([abs(pos[u]-pos[v]) for u,v in g.edges(labels=False)]) + + sig_off() + return (bandwidth, [int_to_vertex[result[i]] for i in range(n)]) + +cpdef min_spanning_tree(g, + weight_function=None, + algorithm='Kruskal'): + r""" + Uses Boost to compute the minimum spanning tree of the input graph. + + INPUT: + + - ``g`` (``Graph``) - the input graph. + + - ``weight_function`` (function) - a function that inputs an edge ``e`` and + outputs its weight. An edge has the form ``(u,v,l)``, where ``u`` and + ``v`` are vertices, ``l`` is a label (that can be of any kind). The + ``weight_function`` can be used to transform the label into a weight (see + the example below). In particular: + + - if ``weight_function`` is not ``None``, the weight of an edge ``e`` is + ``weight_function(e)``; + + - if ``weight_function`` is ``None`` (default) and ``g`` is weighted (that + is, ``g.weighted()==True``), for each edge ``e=(u,v,l)``, we set weight + ``l``; + + - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set all + weights to 1 (hence, the output can be any spanning tree). + + Note that, if the weight is not convertible to a number with function + ``float()``, an error is raised (see tests below). + + - ``algorithm`` (``'Kruskal'`` or ``'Prim'``) - the algorithm used. + + OUTPUT: + + The edges of a minimum spanning tree of ``g``, if one exists, otherwise + the empty list. + + .. seealso:: + + - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree` + + EXAMPLES:: + + sage: from sage.graphs.base.boost_graph import min_spanning_tree + sage: min_spanning_tree(graphs.PathGraph(4)) + [(0, 1, None), (1, 2, None), (2, 3, None)] + + sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})]) + sage: min_spanning_tree(G, weight_function=lambda e: e[2]['weight']) + [(0, 1, {'name': 'a', 'weight': 1}), (1, 2, {'name': 'b', 'weight': 1})] + + TESTS: + + Given an input which is not a graph:: + + sage: min_spanning_tree("I am not a graph!") + Traceback (most recent call last): + ... + ValueError: The input g must be a Sage Graph. + + Given a wrong algorithm:: + + sage: min_spanning_tree(graphs.PathGraph(3), algorithm='tip top') + Traceback (most recent call last): + ... + ValueError: Algorithm 'tip top' not yet implemented. Please contribute. + + If the weight is not a number:: + + sage: g = Graph([(0,1,1), (1,2,'a')], weighted=True) + sage: min_spanning_tree(g) + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, 'a'). + + sage: g = Graph([(0,1,1), (1,2,[1,2,3])], weighted=True) + sage: min_spanning_tree(g) + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, [1, 2, 3]). + + """ + from sage.graphs.graph import Graph + + if not isinstance(g, Graph): + raise ValueError("The input g must be a Sage Graph.") + if not algorithm in ['Kruskal', 'Prim']: + raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + + if g.allows_loops() or g.allows_multiple_edges(): + g = g.to_simple() + # Now g has no self loops and no multiple edges. + sig_on() + # These variables are automatically deleted when the function terminates. + cdef BoostVecWeightedGraph g_boost + cdef vector[v_index] result + cdef dict vertex_to_int = {v:i for i,v in enumerate(g.vertices())} + cdef dict int_to_vertex = {i:v for i,v in enumerate(g.vertices())} + + try: + boost_weighted_graph_from_sage_graph(&g_boost, g, weight_function) + except Exception, e: + sig_off() + raise e + + if algorithm=='Kruskal': + result = g_boost.kruskal_min_spanning_tree() + elif algorithm=='Prim': + result = g_boost.prim_min_spanning_tree() + + cdef int n = g.num_verts() + sig_off() + + if result.size() != 2 * (n - 1): + return [] + else: + edges = [(int_to_vertex[result[2*i]], int_to_vertex[result[2*i+1]]) for i in range(n-1)] + return sorted([(min(e[0],e[1]), max(e[0],e[1]), g.edge_label(e[0], e[1])) for e in edges]) + + +cpdef shortest_paths(g, start, weight_function=None, algorithm=None): + r""" + Computes the shortest paths from ``start`` to all other vertices. + + This routine outputs all shortest paths from node ``start`` to any other + node in the graph. The input graph can be weighted: if the algorithm is + Dijkstra, no negative weights are allowed, while if the algorithm is + Bellman-Ford, negative weights are allowed, but there must be no negative + cycle (otherwise, the shortest paths might not exist). + + However, Dijkstra algorithm is more efficient: for this reason, we suggest + to use Bellman-Ford only if necessary (which is also the default option). + Note that, if the graph is undirected, a negative edge automatically creates + a negative cycle: for this reason, in this case, Dijkstra algorithm is + always better. + + The running-time is `O(n \log n+m)` for Dijkstra algorithm and `O(mn)` for + Bellman-Ford algorithm, where `n` is the number of nodes and `m` is the + number of edges. + + INPUT: + + - ``g`` (generic_graph) - the input graph. + + - ``start`` (vertex) - the starting vertex to compute shortest paths. + + - ``weight_function`` (function) - a function that associates a weight to + each edge. If ``None`` (default), the weights of ``g`` are used, if + available, otherwise all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'Dijkstra','Dijkstra_Boost'``: the Dijkstra algorithm implemented in + Boost (works only with positive weights). + + - ``'Bellman-Ford','Bellman-Ford_Boost'``: the Bellman-Ford algorithm + implemented in Boost (works also with negative weights, if there is no + negative cycle). + + OUTPUT: + + A pair of dictionaries ``[distances, predecessors]`` such that, for each + vertex ``v``, ``distances[v]`` is the distance from ``start`` to ``v``, + ``predecessors[v]`` is the last vertex in a shortest path from ``start`` to + ``v``. + + EXAMPLES: + + Undirected graphs:: + + sage: from sage.graphs.base.boost_graph import shortest_paths + sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True) + sage: shortest_paths(g, 1) + [{0: 1, 1: 0, 2: 2, 3: 3}, {0: 1, 1: None, 2: 1, 3: 2}] + sage: g = graphs.GridGraph([2,2]) + sage: shortest_paths(g,(0,0),weight_function=lambda e:2) + [{(0, 0): 0, (0, 1): 2, (1, 0): 2, (1, 1): 4}, + {(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 1)}] + + Directed graphs:: + + sage: g = DiGraph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True) + sage: shortest_paths(g, 1) + [{1: 0, 2: 2, 3: 3}, {1: None, 2: 1, 3: 2}] + + TESTS: + + Given an input which is not a graph:: + + sage: shortest_paths("I am not a graph!", 1) + Traceback (most recent call last): + ... + ValueError: The input g must be a Sage Graph or DiGraph. + + If there is a negative cycle:: + + sage: from sage.graphs.base.boost_graph import shortest_paths + sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True) + sage: shortest_paths(g, 1) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + If Dijkstra is used with negative weights:: + + sage: from sage.graphs.base.boost_graph import shortest_paths + sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True) + sage: shortest_paths(g, 1, algorithm='Dijkstra') + Traceback (most recent call last): + ... + ValueError: Dijkstra algorithm does not work with negative weights. Please, use Bellman-Ford. + + Wrong starting vartex:: + + sage: shortest_paths(g, []) + Traceback (most recent call last): + ... + ValueError: The starting vertex [] is not in the graph. + """ + from sage.graphs.generic_graph import GenericGraph + + if not isinstance(g, GenericGraph): + raise ValueError("The input g must be a Sage Graph or DiGraph.") + elif g.num_edges() == 0: + from sage.rings.infinity import Infinity + return [{start:0}, {start:None}] + + + # These variables are automatically deleted when the function terminates. + cdef dict v_to_int = {v:i for i,v in enumerate(g.vertices())} + cdef dict int_to_v = {i:v for i,v in enumerate(g.vertices())} + cdef BoostVecWeightedDiGraphU g_boost_dir + cdef BoostVecWeightedGraph g_boost_und + cdef result_distances result + + if start not in v_to_int.keys(): + raise ValueError("The starting vertex " + str(start) + " is not in " + + "the graph.") + + if algorithm is None: + # Check if there are edges with negative weights + if weight_function is not None: + for e in g.edge_iterator(): + try: + if float(weight_function(e)) < 0: + algorithm = 'Bellman-Ford' + break + except ValueError, TypeError: + raise ValueError("I cannot find the weight of edge " + + str(e) + ".") + + else: + for _,_,w in g.edge_iterator(): + try: + if float(w) < 0: + algorithm = 'Bellman-Ford' + break + except ValueError, TypeError: + raise ValueError("The label '", str(w), "' is not convertible " + + "to a float.") + + if algorithm is None: + algorithm = 'Dijkstra' + + if algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']: + if g.is_directed(): + boost_weighted_graph_from_sage_graph(&g_boost_dir, g, weight_function) + sig_on() + result = g_boost_dir.bellman_ford_shortest_paths(v_to_int[start]) + sig_off() + else: + boost_weighted_graph_from_sage_graph(&g_boost_und, g, weight_function) + sig_on() + result = g_boost_und.bellman_ford_shortest_paths(v_to_int[start]) + sig_off() + if result.distances.size() == 0: + raise ValueError("The graph contains a negative cycle."); + + elif algorithm in ['Dijkstra', 'Dijkstra_Boost']: + if g.is_directed(): + boost_weighted_graph_from_sage_graph(&g_boost_dir, g, weight_function) + sig_on() + result = g_boost_dir.dijkstra_shortest_paths(v_to_int[start]) + sig_off() + else: + boost_weighted_graph_from_sage_graph(&g_boost_und, g, weight_function) + sig_on() + result = g_boost_und.dijkstra_shortest_paths(v_to_int[start]) + sig_off() + if result.distances.size() == 0: + raise ValueError("Dijkstra algorithm does not work with negative weights. Please, use Bellman-Ford."); + + else: + raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + + + dist = {} + pred = {} + + if weight_function is not None: + correct_type = type(weight_function(g.edge_iterator().next())) + elif g.weighted(): + correct_type = type(g.edge_iterator().next()[2]) + else: + correct_type = int + # Needed for rational curves. + from sage.rings.real_mpfr import RealNumber, RR + if correct_type == RealNumber: + correct_type = RR + + import sys + for v in range(g.num_verts()): + if result.distances[v] != sys.float_info.max: + w = int_to_v[v] + dist[w] = correct_type(result.distances[v]) + pred[w] = int_to_v[result.predecessors[v]] if result.predecessors[v] != v else None + return [dist, pred] + +cpdef johnson_shortest_paths(g, weight_function = None): + r""" + Uses Johnson algorithm to solve the all-pairs-shortest-paths. + + This routine outputs the distance between each pair of vertices, using a + dictionary of dictionaries. It works on all kinds of graphs, but it is + designed specifically for graphs with negative weights (otherwise there are + more efficient algorithms, like Dijkstra). + + The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and + `m` is the number of edges. + + INPUT: + + - ``g`` (generic_graph) - the input graph. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + OUTPUT: + + A dictionary of dictionary ``distances`` such that ``distances[v][w]`` is + the distance between vertex ``v`` and vertex ``w``. + + EXAMPLES: + + Undirected graphs:: + + sage: from sage.graphs.base.boost_graph import johnson_shortest_paths + sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True) + sage: johnson_shortest_paths(g) + {0: {0: 0, 1: 1, 2: 3, 3: 4}, + 1: {0: 1, 1: 0, 2: 2, 3: 3}, + 2: {0: 3, 1: 2, 2: 0, 3: 1}, + 3: {0: 4, 1: 3, 2: 1, 3: 0}} + + Directed graphs:: + + sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True) + sage: johnson_shortest_paths(g) + {0: {0: 0, 1: 1, 2: -1, 3: 0}, + 1: {1: 0, 2: -2, 3: -1}, + 2: {2: 0, 3: 1}, + 3: {3: 0}} + + TESTS: + + Given an input which is not a graph:: + + sage: johnson_shortest_paths("I am not a graph!") + Traceback (most recent call last): + ... + ValueError: The input g must be a Sage Graph or DiGraph. + + If there is a negative cycle:: + + sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True) + sage: johnson_shortest_paths(g) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + """ + from sage.graphs.generic_graph import GenericGraph + + if not isinstance(g, GenericGraph): + raise ValueError("The input g must be a Sage Graph or DiGraph.") + elif g.num_edges() == 0: + from sage.rings.infinity import Infinity + return {v:{v:0} for v in g.vertices()} + # These variables are automatically deleted when the function terminates. + cdef dict v_to_int = {v:i for i,v in enumerate(g.vertices())} + cdef dict int_to_v = {i:v for i,v in enumerate(g.vertices())} + cdef BoostVecWeightedDiGraphU g_boost_dir + cdef BoostVecWeightedGraph g_boost_und + cdef int N = g.num_verts() + cdef vector[vector[double]] result + + if g.is_directed(): + boost_weighted_graph_from_sage_graph(&g_boost_dir, g, weight_function) + sig_on() + result = g_boost_dir.johnson_shortest_paths() + sig_off() + else: + boost_weighted_graph_from_sage_graph(&g_boost_und, g, weight_function) + sig_on() + result = g_boost_und.johnson_shortest_paths() + sig_off() + + if result.size() == 0: + raise ValueError("The graph contains a negative cycle.") + + if weight_function is not None: + correct_type = type(weight_function(g.edge_iterator().next())) + elif g.weighted(): + correct_type = type(g.edge_iterator().next()[2]) + else: + correct_type = int + # Needed for rational curves. + from sage.rings.real_mpfr import RealNumber, RR + if correct_type == RealNumber: + correct_type = RR + + import sys + return {int_to_v[v]:{int_to_v[w]:correct_type(result[v][w]) + for w in range(N) if result[v][w] != sys.float_info.max} + for v in range(N)} + +cpdef johnson_closeness_centrality(g, weight_function = None): + r""" + Uses Johnson algorithm to compute the closeness centrality of all vertices. + + This routine is preferrable to :func:`~johnson_shortest_paths` because it + does not create a doubly indexed dictionary of distances, saving memory. + + The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and + `m` is the number of edges. + + INPUT: + + - ``g`` (generic_graph) - the input graph. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + OUTPUT: + + A dictionary associating each vertex ``v`` to its closeness centrality. + + EXAMPLES: + + Undirected graphs:: + + sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality + sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True) + sage: johnson_closeness_centrality(g) + {0: 0.375, 1: 0.5, 2: 0.5, 3: 0.375} + + Directed graphs:: + + sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality + sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True) + sage: johnson_closeness_centrality(g) + {0: inf, 1: -0.4444444444444444, 2: 0.3333333333333333} + + TESTS: + + Given an input which is not a graph:: + + sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality + sage: johnson_closeness_centrality("I am not a graph!") + Traceback (most recent call last): + ... + ValueError: The input g must be a Sage Graph or DiGraph. + + If there is a negative cycle:: + + sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality + sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True) + sage: johnson_closeness_centrality(g) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + """ + from sage.graphs.generic_graph import GenericGraph + + if not isinstance(g, GenericGraph): + raise ValueError("The input g must be a Sage Graph or DiGraph.") + elif g.num_edges() == 0: + from sage.rings.infinity import Infinity + return {} + sig_on() + # These variables are automatically deleted when the function terminates. + cdef BoostVecWeightedDiGraphU g_boost_dir + cdef BoostVecWeightedGraph g_boost_und + cdef int N = g.num_verts() + cdef vector[vector[double]] result + cdef vector[double] closeness + cdef double farness + cdef int i, j, reach + + if g.is_directed(): + boost_weighted_graph_from_sage_graph(&g_boost_dir, g, weight_function) + result = g_boost_dir.johnson_shortest_paths() + else: + boost_weighted_graph_from_sage_graph(&g_boost_und, g, weight_function) + result = g_boost_und.johnson_shortest_paths() + + if result.size() == 0: + sig_off() + raise ValueError("The graph contains a negative cycle.") + + import sys + for i in range(N): + farness = 0 + reach = 0 + for j in range(N): + if result[i][j] != sys.float_info.max: + farness += result[i][j] + reach += 1 + if reach > 1: + closeness.push_back((reach-1) * (reach-1) / ((N-1) * farness)) + else: + closeness.push_back(sys.float_info.max) + sig_off() + return {v: closeness[i] for i,v in enumerate(g.vertices()) if closeness[i] != sys.float_info.max} diff --git a/src/sage/graphs/base/boost_interface.cpp b/src/sage/graphs/base/boost_interface.cpp index cf895eb42b8..1775b51dcbe 100644 --- a/src/sage/graphs/base/boost_interface.cpp +++ b/src/sage/graphs/base/boost_interface.cpp @@ -5,34 +5,46 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include -using namespace std; -using namespace boost; - typedef int v_index; typedef long e_index; // This struct is the output of the edge connectivity Boost algorithm. typedef struct { v_index ec; // The edge connectivity - vector edges; // The edges in a minimum cut, stored as a list of + std::vector edges; // The edges in a minimum cut, stored as a list of // nodes. For instance, if the minimum cut is // {(1,2),(3,4)}, the output vector will be (1,2,3,4). } result_ec; - // This struct is the output of the clustering coefficient Boost algorithm. typedef struct { double average_clustering_coefficient; // The average clustering coefficient - vector clust_of_v; // The clustering coefficient of each node. + std::vector clust_of_v; // The clustering coefficient of each node. } result_cc; +// This struct is the output of the edge connectivity Boost algorithm. +typedef struct { + std::vector distances; // An array with all distances from the starting vertex + std::vector predecessors; // For each vertex v, the first vertex in a shortest + // path from the starting vertex to v. +} result_distances; + + template // How the list of edges is stored + class EdgeListS, // How the list of edges is stored + class EdgeProperty> // Properties of edges (weight) class BoostGraph /* * This generic class wraps a Boost graph, in order to make it Cython-friendly. @@ -49,8 +61,11 @@ class BoostGraph * we use vertex properties, and the syntax is (*graph)[v]. */ { - typedef typename boost::adjacency_list, no_property, no_property, EdgeListS> adjacency_list; +private: + typedef typename boost::adjacency_list< + OutEdgeListS, VertexListS, DirectedS, + boost::property, + EdgeProperty, boost::no_property, EdgeListS> adjacency_list; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename boost::graph_traits::edge_descriptor edge_descriptor; typedef typename std::vector edge_container; @@ -58,12 +73,12 @@ class BoostGraph public: adjacency_list *graph; - vector *vertices; + std::vector *vertices; vertex_to_int_map index; BoostGraph() { graph = new adjacency_list(); - vertices = new vector(); + vertices = new std::vector(); } ~BoostGraph() { @@ -87,10 +102,14 @@ class BoostGraph boost::add_edge((*vertices)[u], (*vertices)[v], *graph); } + void add_edge(v_index u, v_index v, double weight) { + boost::add_edge((*vertices)[u], (*vertices)[v], weight, *graph); + } + result_ec edge_connectivity() { result_ec to_return; edge_container disconnecting_set; - back_insert_iterator inserter(disconnecting_set); + std::back_insert_iterator inserter(disconnecting_set); to_return.ec = boost::edge_connectivity(*graph, inserter); for (v_index i = 0; i < disconnecting_set.size(); i++) { @@ -109,17 +128,17 @@ class BoostGraph result_cc to_return; to_return.clust_of_v.resize(num_verts()); to_return.average_clustering_coefficient = all_clustering_coefficients(*graph, - make_iterator_property_map(to_return.clust_of_v.begin(), index)); + boost::make_iterator_property_map(to_return.clust_of_v.begin(), index)); return to_return; } - vector dominator_tree(v_index v) { - vector fathers(num_verts()); - vector fathers_descr(num_verts(), + std::vector dominator_tree(v_index v) { + std::vector fathers(num_verts()); + std::vector fathers_descr(num_verts(), boost::graph_traits::null_vertex()); lengauer_tarjan_dominator_tree(*graph, (*vertices)[v], - make_iterator_property_map( + boost::make_iterator_property_map( fathers_descr.begin(), index)); for (v_index i = 0; i < num_verts(); i++) { @@ -132,6 +151,109 @@ class BoostGraph } return fathers; } + + // Works only in undirected graphs! + std::vector bandwidth_ordering(bool cuthill) { + std::vector to_return; + std::vector inv_perm(num_vertices(*graph)); + + if (cuthill) { + boost::cuthill_mckee_ordering(*graph, inv_perm.rbegin()); + } else { + boost::king_ordering(*graph, inv_perm.rbegin()); + } + + for (int i = 0; i < inv_perm.size(); i++) { + to_return.push_back(index[inv_perm[i]]); + } + return to_return; + } + + // This function works only on undirected graphs. + std::vector kruskal_min_spanning_tree() { + std::vector to_return; + std::vector spanning_tree; + kruskal_minimum_spanning_tree(*graph, std::back_inserter(spanning_tree)); + + for (unsigned int i = 0; i < spanning_tree.size(); i++) { + to_return.push_back(index[source(spanning_tree[i], *graph)]); + to_return.push_back(index[target(spanning_tree[i], *graph)]); + } + return to_return; + } + + // This function works only on undirected graphs with no parallel edge. + std::vector prim_min_spanning_tree() { + std::vector to_return; + std::vector predecessors(num_verts()); + prim_minimum_spanning_tree(*graph, boost::make_iterator_property_map(predecessors.begin(), index)); + + for (unsigned int i = 0; i < predecessors.size(); i++) { + if (index[predecessors[i]] != i) { + to_return.push_back(i); + to_return.push_back(index[predecessors[i]]); + } + } + return to_return; + } + + result_distances dijkstra_shortest_paths(v_index s) { + v_index N = num_verts(); + result_distances to_return; + std::vector distances(N, (std::numeric_limits::max)()); + std::vector predecessors(N); + try { + boost::dijkstra_shortest_paths(*graph, (*vertices)[s], distance_map(boost::make_iterator_property_map(distances.begin(), index)) + .predecessor_map(boost::make_iterator_property_map(predecessors.begin(), index))); + } catch (boost::exception_detail::clone_impl > e) { + return to_return; + } + + to_return.distances = distances; + + for (int i = 0; i < N; i++) { + to_return.predecessors.push_back(index[predecessors[i]]); + } + + return to_return; + } + + result_distances bellman_ford_shortest_paths(v_index s) { + v_index N = num_verts(); + + std::vector distance(N, (std::numeric_limits::max)()); + std::vector predecessors(N); + result_distances to_return; + typename boost::property_map::type weight = get(boost::edge_weight, (*graph)); + + for (v_index i = 0; i < N; ++i) + predecessors[i] = (*vertices)[i]; + + distance[s] = 0; + bool r = boost::bellman_ford_shortest_paths + (*graph, N, boost::weight_map(weight).distance_map(boost::make_iterator_property_map(distance.begin(), index)).predecessor_map(boost::make_iterator_property_map(predecessors.begin(), index))); + + if (!r) { + return to_return; + } + + to_return.distances = distance; + for (int i = 0; i < N; i++) { + to_return.predecessors.push_back(index[predecessors[i]]); + } + return to_return; + } + + std::vector > johnson_shortest_paths() { + v_index N = num_verts(); + + std::vector > D(N, std::vector(N)); + if (johnson_all_pairs_shortest_paths(*graph, D)) { + return D; + } else { + return std::vector >(); + } + } }; diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index a50261d1fac..50deaa6532d 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -2181,7 +2181,7 @@ cdef class CGraphBackend(GenericGraphBackend): return [] - def bidirectional_dijkstra(self, x, y): + def bidirectional_dijkstra(self, x, y, weight_function=None): r""" Returns the shortest path between ``x`` and ``y`` using a bidirectional version of Dijkstra's algorithm. @@ -2193,6 +2193,10 @@ cdef class CGraphBackend(GenericGraphBackend): - ``y`` -- the end vertex in the shortest path from ``x`` to ``y``. + - ``weight_function`` -- a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If ``None``, we use + the edge label ``l`` as a weight. + OUTPUT: - A list of vertices in the shortest path from ``x`` to ``y``. @@ -2204,6 +2208,9 @@ cdef class CGraphBackend(GenericGraphBackend): ... G.set_edge_label(u,v,1) sage: G.shortest_path(0, 1, by_weight=True) [0, 1] + sage: G = DiGraph([(1,2,{'weight':1}), (1,3,{'weight':5}), (2,3,{'weight':1})]) + sage: G.shortest_path(1, 3, weight_function=lambda e:e[2]['weight']) + [1, 2, 3] TEST: @@ -2264,6 +2271,9 @@ cdef class CGraphBackend(GenericGraphBackend): cdef int meeting_vertex = -1 cdef float shortest_path_length + if weight_function is None: + weight_function = lambda e:e[2] + # As long as the current side (x or y) is not totally explored ... while queue: (distance, side, pred, v) = heappop(queue) @@ -2299,7 +2309,9 @@ cdef class CGraphBackend(GenericGraphBackend): if w not in dist_current: v_obj = self.vertex_label(v) w_obj = self.vertex_label(w) - edge_label = self.get_edge_label(v_obj, w_obj) if side == 1 else self.get_edge_label(w_obj, v_obj) + edge_label = weight_function((v_obj, w_obj, self.get_edge_label(v_obj, w_obj))) if side == 1 else weight_function((w_obj, v_obj, self.get_edge_label(w_obj, v_obj))) + if edge_label < 0: + raise ValueError("The graph contains an edge with negative weight!") heappush(queue, (distance + edge_label, side, v, w)) # No meeting point has been found diff --git a/src/sage/graphs/base/dense_graph.pyx b/src/sage/graphs/base/dense_graph.pyx index f1abc3d4128..29bf399164d 100644 --- a/src/sage/graphs/base/dense_graph.pyx +++ b/src/sage/graphs/base/dense_graph.pyx @@ -102,18 +102,16 @@ from ``CGraph`` (for explanation, refer to the documentation there):: It also contains the following variables:: - cdef int radix_div_shift - cdef int radix_mod_mask cdef int num_longs cdef unsigned long *edges The array ``edges`` is a series of bits which are turned on or off, and due to this, dense graphs only support graphs without edge labels and with no multiple -edges. The ints ``radix_div_shift`` and ``radix_mod_mask`` are simply for doing -efficient division by powers of two, and ``num_longs`` stores the length of the -``edges`` array. Recall that this length reflects the number of available -vertices, not the number of "actual" vertices. For more details about this, -refer to the documentation for ``CGraph``. +edges. ``num_longs`` stores the length of the ``edges`` array. Recall that this +length reflects the number of available vertices, not the number of "actual" +vertices. For more details about this, refer to the documentation for +``CGraph``. + """ #******************************************************************************* @@ -125,6 +123,11 @@ refer to the documentation for ``CGraph``. include 'sage/data_structures/bitset.pxi' +from libc.string cimport memcpy + +cdef int radix = sizeof(unsigned long) * 8 # number of bits per 'unsigned long' +cdef int radix_mod_mask = radix - 1 # (assumes that radis is a power of 2) + cdef class DenseGraph(CGraph): """ Compiled dense graphs. @@ -151,7 +154,6 @@ cdef class DenseGraph(CGraph): for use in pickling. """ - def __cinit__(self, int nverts, int extra_vertices = 10, verts = None, arcs = None): """ Allocation and initialization happen in one place. @@ -167,35 +169,24 @@ cdef class DenseGraph(CGraph): """ if nverts == 0 and extra_vertices == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') - cdef int radix = sizeof(unsigned long) << 3 - self.radix_mod_mask = radix - 1 - cdef int i = 0 - while ((1)<> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - self.edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) - self.in_degrees = sage_malloc(total_verts * sizeof(int)) - self.out_degrees = sage_malloc(total_verts * sizeof(int)) + self.edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) + self.in_degrees = sage_calloc(total_verts, sizeof(int)) + self.out_degrees = sage_calloc(total_verts, sizeof(int)) if not self.edges or not self.in_degrees or not self.out_degrees: - if self.edges: sage_free(self.edges) - if self.in_degrees: sage_free(self.in_degrees) - if self.out_degrees: sage_free(self.out_degrees) + sage_free(self.edges) + sage_free(self.in_degrees) + sage_free(self.out_degrees) raise MemoryError - for i from 0 <= i < self.num_longs * total_verts: - self.edges[i] = 0 - for i from 0 <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + bitset_init(self.active_vertices, total_verts) bitset_set_first_n(self.active_vertices, self.num_verts) @@ -261,6 +252,7 @@ cdef class DenseGraph(CGraph): cdef int i, j if total_verts == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') + cdef bitset_t bits cdef int min_verts, min_longs, old_longs = self.num_longs if total_verts < self.active_vertices.size: @@ -276,42 +268,27 @@ cdef class DenseGraph(CGraph): min_verts = self.active_vertices.size min_longs = self.num_longs - i = total_verts >> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i - if min_longs == -1: min_longs = self.num_longs + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - cdef unsigned long *new_edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) + if min_longs == -1: + min_longs = self.num_longs + # Resize of self.edges + cdef unsigned long *new_edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) for i from 0 <= i < min_verts: - for j from 0 <= j < min_longs: - new_edges[i*self.num_longs + j] = self.edges[i*old_longs + j] - for j from min_longs <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 - for i from min_verts <= i < total_verts: - for j from 0 <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 + memcpy(new_edges+i*self.num_longs, self.edges+i*old_longs, min_longs*sizeof(unsigned long)) + sage_free(self.edges) self.edges = new_edges - self.in_degrees = sage_realloc(self.in_degrees, total_verts * sizeof(int)) + self.in_degrees = sage_realloc(self.in_degrees , total_verts * sizeof(int)) self.out_degrees = sage_realloc(self.out_degrees, total_verts * sizeof(int)) - cdef int first_limb - cdef unsigned long zero_gate - if total_verts > self.active_vertices.size: - first_limb = (self.active_vertices.size >> self.radix_div_shift) - zero_gate = (1) << (self.active_vertices.size & self.radix_mod_mask) - zero_gate -= 1 - for i from 0 <= i < total_verts: - self.edges[first_limb] &= zero_gate - for j from first_limb < j < self.num_longs: - self.edges[j] = 0 - - for i from self.active_vertices.size <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + for i in range(self.active_vertices.size, total_verts): + self.in_degrees[i] = 0 + self.out_degrees[i] = 0 + bitset_realloc(self.active_vertices, total_verts) ################################### @@ -326,8 +303,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if not self.edges[place] & word: self.in_degrees[v] += 1 self.out_degrees[u] += 1 @@ -373,9 +350,9 @@ cdef class DenseGraph(CGraph): 1 -- True """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) - return (self.edges[place] & word) >> (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) + return (self.edges[place] & word) >> (v & radix_mod_mask) cpdef bint has_arc(self, int u, int v) except -1: """ @@ -409,8 +386,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers, must be in self """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if self.edges[place] & word: self.in_degrees[v] -= 1 self.out_degrees[u] -= 1 @@ -444,6 +421,43 @@ cdef class DenseGraph(CGraph): self.check_vertex(v) self.del_arc_unsafe(u,v) + def complement(self): + r""" + Replaces the graph with its complement + + .. NOTE:: + + Assumes that the graph has no loop. + + EXAMPLE:: + + sage: from sage.graphs.base.dense_graph import DenseGraph + sage: G = DenseGraph(5) + sage: G.add_arc(0,1) + sage: G.has_arc(0,1) + True + sage: G.complement() + sage: G.has_arc(0,1) + False + """ + cdef int num_arcs_old = self.num_arcs + + # The following cast assumes that mp_limb_t is an unsigned long. + # (this assumption is already made in bitset.pxi) + cdef unsigned long * active_vertices_bitset + active_vertices_bitset = self.active_vertices.bits + + cdef int i,j + for i in range(self.active_vertices.size): + if bitset_in(self.active_vertices,i): + self.add_arc_unsafe(i,i) + for j in range(self.num_longs): # the actual job + self.edges[i*self.num_longs+j] ^= active_vertices_bitset[j] + self.in_degrees[i] = self.num_verts-self.in_degrees[i] + self.out_degrees[i] = self.num_verts-self.out_degrees[i] + + self.num_arcs = self.num_verts*(self.num_verts-1) - num_arcs_old + ################################### # Neighbor functions ################################### @@ -527,8 +541,8 @@ cdef class DenseGraph(CGraph): there were more """ - cdef int place = v >> self.radix_div_shift - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = v / radix + cdef unsigned long word = (1) << (v & radix_mod_mask) cdef int i, num_nbrs = 0 for i from 0 <= i < self.active_vertices.size: if self.edges[place + i*self.num_longs] & word: diff --git a/src/sage/graphs/base/static_sparse_backend.pyx b/src/sage/graphs/base/static_sparse_backend.pyx index bf99d056cdc..580755c0645 100644 --- a/src/sage/graphs/base/static_sparse_backend.pyx +++ b/src/sage/graphs/base/static_sparse_backend.pyx @@ -44,7 +44,7 @@ from c_graph cimport CGraphBackend from sage.data_structures.bitset cimport FrozenBitset from libc.stdint cimport uint32_t include 'sage/data_structures/bitset.pxi' -from libc.stdlib cimport calloc,free +include "sage/ext/stdsage.pxi" cdef class StaticSparseCGraph(CGraph): """ @@ -67,10 +67,10 @@ cdef class StaticSparseCGraph(CGraph): sage: g = StaticSparseCGraph(graphs.PetersenGraph()) """ cdef int i, j, tmp - has_labels = any(not l is None for _,_,l in G.edge_iterator()) + has_labels = any(l is not None for _,_,l in G.edge_iterator()) self._directed = G.is_directed() - init_short_digraph(self.g, G, edge_labelled = has_labels) + init_short_digraph(self.g, G, edge_labelled=has_labels) if self._directed: init_reverse(self.g_rev,self.g) @@ -78,10 +78,11 @@ cdef class StaticSparseCGraph(CGraph): elif not G.has_loops(): self.number_of_loops = NULL else: - self.number_of_loops = calloc(sizeof(int), self.g.n) - if self.number_of_loops == NULL: + try: + self.number_of_loops = check_calloc(self.g.n, sizeof(int)) + except MemoryError: free_short_digraph(self.g) - raise MemoryError + raise for i in range(self.g.n): for tmp in range(out_degree(self.g,i)): j = self.g.neighbors[i][tmp] @@ -105,7 +106,7 @@ cdef class StaticSparseCGraph(CGraph): """ bitset_free(self.active_vertices) free_short_digraph(self.g) - free(self.number_of_loops) + sage_free(self.number_of_loops) if self.g_rev != NULL: free_short_digraph(self.g_rev) diff --git a/src/sage/graphs/base/static_sparse_graph.pxd b/src/sage/graphs/base/static_sparse_graph.pxd index 9290064d637..9cbd8631abf 100644 --- a/src/sage/graphs/base/static_sparse_graph.pxd +++ b/src/sage/graphs/base/static_sparse_graph.pxd @@ -28,3 +28,5 @@ cdef int init_reverse(short_digraph dst, short_digraph src) except -1 cdef int out_degree(short_digraph g, int u) cdef inline uint32_t * has_edge(short_digraph g, int u, int v) cdef inline object edge_label(short_digraph g, uint32_t * edge) +cdef int tarjan_strongly_connected_components_C(short_digraph g, int *scc) +cdef void strongly_connected_components_digraph_C(short_digraph g, int nscc, int *scc, short_digraph output) diff --git a/src/sage/graphs/base/static_sparse_graph.pyx b/src/sage/graphs/base/static_sparse_graph.pyx index 005c032cf60..834ceb09b11 100644 --- a/src/sage/graphs/base/static_sparse_graph.pyx +++ b/src/sage/graphs/base/static_sparse_graph.pyx @@ -138,10 +138,29 @@ Cython functions component containing ``v`` in ``g``. The variable ``g_reversed`` is assumed to represent the reverse of ``g``. +``tarjan_strongly_connected_components_C(short_digraph g, int *scc)`` + + Assuming ``scc`` is already allocated and has size at least ``g.n``, this + method computes the strongly connected components of ``g``, and outputs in + ``scc[v]`` the number of the strongly connected component containing ``v``. + It returns the number of strongly connected components. + +``strongly_connected_components_digraph_C(short_digraph g, int nscc, int *scc, short_digraph output):`` + + Assuming ``nscc`` and ``scc`` are the outputs of + ``tarjan_strongly_connected_components_C`` on ``g``, this routine + sets ``output`` to the + strongly connected component digraph of ``g``, that is, the vertices of + ``output`` are the strongly connected components of ``g`` (numbers are + provided by ``scc``), and ``output`` contains an arc ``(C1,C2)`` if ``g`` + has an arc from a vertex in ``C1`` to a vertex in ``C2``. + What is this module used for ? ------------------------------ -At the moment, it is only used in the :mod:`sage.graphs.distances_all_pairs` module. +At the moment, it is used in the :mod:`sage.graphs.distances_all_pairs` module, +and in the +:meth:`~sage.graphs.digraph.DiGraph.strongly_connected_components` method. Python functions ---------------- @@ -164,10 +183,13 @@ with C arguments). include "sage/data_structures/bitset.pxi" cimport cpython from libc.string cimport memset -from libc.stdint cimport INT32_MAX +from libc.limits cimport INT_MAX from sage.graphs.base.c_graph cimport CGraph from static_sparse_backend cimport StaticSparseCGraph from static_sparse_backend cimport StaticSparseBackend +from sage.ext.memory_allocator cimport MemoryAllocator +from sage.ext.memory cimport check_allocarray +from libcpp.vector cimport vector cdef int init_short_digraph(short_digraph g, G, edge_labelled = False) except -1: r""" @@ -178,8 +200,8 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled = False) except -1 """ g.edge_labels = NULL - if G.order() >= INT32_MAX: - raise ValueError("This structure can handle at most "+str(INT32_MAX)+" vertices !") + if G.order() >= INT_MAX: + raise ValueError("This structure can handle at most "+str(INT_MAX)+" vertices !") else: g.n = G.order() @@ -417,6 +439,327 @@ cdef int can_be_reached_from(short_digraph g, int src, bitset_t reached) except sage_free(stack) +cdef int tarjan_strongly_connected_components_C(short_digraph g, int *scc): + r""" + The Tarjan algorithm to compute strongly connected components (SCCs). + + This routine returns the number of SCCs `k` and, stores in ``scc[v]`` an + integer between `0` and `k-1`, corresponding to the SCC containing v. SCCs + are numbered in reverse topological order, that is, if `(v,w)` is an edge + in the graph, ``scc[v] <= scc[w]``. + + The basic idea of the algorithm is this: a depth-first search (DFS) begins + from an arbitrary start node (and subsequent DFSes are + conducted on any nodes that have not yet been found). As usual with DFSes, + the search visits every node of the graph exactly once, declining to revisit + any node that has already been explored. Thus, the collection of search + trees is a spanning forest of the graph. The strongly connected components + are the subtrees of this spanning forest having no edge directed outside the + subtree. + + To recover these components, during the DFS, we keep the index of a node, + that is, the position in the DFS tree, and the lowlink: as soon as the + subtree rooted at `v` has been fully explored, the lowlink of `v` is the + smallest index reachable from `v` passing from descendants of `v`. If the + subtree rooted at `v` has been fully explored, and the index of `v` equals + the lowlink of `v`, that whole subtree is a new SCC. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef int u,v,w, n = g.n, current_index = 0, currentscc = 0 + cdef int *index = mem.malloc(n * sizeof(int)) + cdef int *pred = mem.malloc(n * sizeof(int)) + cdef int *lowlink = mem.malloc(n * sizeof(int)) + cdef int *dfs_stack = mem.malloc((n_edges(g) + 1) * sizeof(int)) + cdef int dfs_stack_end + cdef int *scc_stack = mem.malloc(n * sizeof(int)) # Used to keep track of which nodes are in the "current" SCC + cdef short *in_scc_stack = mem.calloc(n, sizeof(short)) + cdef uint32_t *p_tmp + cdef short *visited = mem.calloc(n, sizeof(short)) + # The variable visited[v] is 0 if the vertex has never been visited, 1 if + # it is an ancestor of the current vertex, 2 otherwise. + + for u in range(n): + if visited[u]: + continue + + # Perform a DFS from u + dfs_stack_end = 1 + scc_stack_end = 0 + dfs_stack[0] = u + pred[u] = u + + while dfs_stack_end > 0: + v = dfs_stack[dfs_stack_end - 1] + if not visited[v]: + # It means that this is the first time we visit v. + # We set the index and the lowlink to be equal: during the + # algorithm, the lowlink may decrease. + visited[v] = 1 + index[v] = current_index + lowlink[v] = current_index + current_index = current_index + 1 + # We add v to the stack of vertices in the current SCC + scc_stack[scc_stack_end] = v + scc_stack_end = scc_stack_end + 1 + in_scc_stack[v] = 1 + + # We iterate over all neighbors of v + p_tmp = g.neighbors[v] + while p_tmp`. + + EXAMPLE:: + + sage: from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components + sage: tarjan_strongly_connected_components(digraphs.Path(3)) + [[2], [1], [0]] + sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) + sage: D.connected_components() + [[0, 1, 2, 3], [4, 5, 6]] + sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) + sage: D.strongly_connected_components() + [[3], [2], [1], [0], [6], [5], [4]] + sage: D.add_edge([2,0]) + sage: D.strongly_connected_components() + [[3], [0, 1, 2], [6], [5], [4]] + sage: D = DiGraph([('a','b'), ('b','c'), ('c', 'd'), ('d', 'b'), ('c', 'e')]) + sage: D.strongly_connected_components() + [['e'], ['b', 'c', 'd'], ['a']] + + TESTS: + + Checking that the result is correct:: + + sage: from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components + sage: import random + sage: for i in range(10): # long + ....: n = random.randint(2,20) + ....: m = random.randint(1, n*(n-1)) + ....: g = digraphs.RandomDirectedGNM(n,m) + ....: sccs = tarjan_strongly_connected_components(g) + ....: for scc in sccs: + ....: scc_check = g.strongly_connected_component_containing_vertex(scc[0]) + ....: assert(sorted(scc) == sorted(scc_check)) + + Checking against NetworkX:: + + sage: import networkx + sage: for i in range(10): # long + ....: g = digraphs.RandomDirectedGNP(100,.05) + ....: h = g.networkx_graph() + ....: scc1 = g.strongly_connected_components() + ....: scc2 = networkx.strongly_connected_components(h) + ....: s1 = Set(map(Set,scc1)) + ....: s2 = Set(map(Set,scc2)) + ....: if s1 != s2: + ....: print "Ooch !" + """ + from sage.graphs.digraph import DiGraph + + if not isinstance(G, DiGraph): + raise ValueError("G must be a DiGraph.") + + sig_on() + cdef MemoryAllocator mem = MemoryAllocator() + cdef short_digraph g + init_short_digraph(g, G) + cdef int * scc = mem.malloc(g.n * sizeof(int)) + cdef int nscc = tarjan_strongly_connected_components_C(g, scc) + cdef int i + cdef list output = list(list() for i in range(nscc)) # We cannot use [] here + + for i,v in enumerate(G.vertices()): + output[scc[i]].append(v) + sig_off() + return output + +cdef void strongly_connected_components_digraph_C(short_digraph g, int nscc, int *scc, short_digraph output): + r""" + Computes the strongly connected components (SCCs) digraph of `g`. + + The strongly connected components digraph of `g` is a graph having a vertex + for each SCC of `g` and an arc from component `C_1` to component `C_2` if + and only if there is an arc in `g` from a vertex in `C_1` to a vertex in + `C_2`. The strongly connected components digraph is acyclic by definition. + + This routine inputs the graph ``g``, the number of SCCs ``nscc``, and an + array containing in position ``v`` the SCC of vertex ``v`` (these values + must be already computed). The output is stored in variable ``output``, + which should be empty at the beginning. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef int v, w, i + cdef int tmp = nscc + 1 + cdef vector[vector[int]] scc_list = vector[vector[int]](nscc, vector[int]()) + cdef vector[vector[int]] sons = vector[vector[int]](nscc + 1, vector[int]()) + cdef vector[int].iterator iter + cdef short *neighbors = mem.calloc(nscc, sizeof(short)) + cdef long m = 0 + cdef uint32_t degv + cdef uint32_t *p_tmp + + for v in range(nscc): + scc_list[v] = vector[int]() + sons[v] = vector[int]() + sons[nscc] = vector[int]() + + for i in range(g.n): + scc_list[scc[i]].push_back(i) + + for v in range(nscc): + for i in range(scc_list[v].size()): + p_tmp = g.neighbors[scc_list[v][i]] + while p_tmp scc[p_tmp[0]] + p_tmp += 1 + if not (neighbors[w] or w == v): + neighbors[w] = 1 + sons[v].push_back(w) + m += 1 + for w in range(sons[v].size()): + neighbors[sons[v][w]] = 0 + + output.n = nscc + output.m = m + + output.neighbors = check_allocarray((1+output.n), sizeof(uint32_t *)) + + if m == 0: + output.edges = NULL + for v in range(1,nscc + 1): + output.neighbors[v] = NULL + + output.edges = check_allocarray(m, sizeof(uint32_t)) + output.neighbors[0] = output.edges + + for v in range(1,nscc + 1): + degv = sons[v].size() + output.neighbors[v] = output.neighbors[v-1] + sons[v-1].size() + for i in range(sons[v].size()): + output.neighbors[v][i] = sons[v][i] + +def strongly_connected_components_digraph(G): + r""" + Returns the digraph of the strongly connected components (SCCs). + + This routine is used to test ``strongly_connected_components_digraph_C``, + but it is not used by the Sage digraph. It outputs a pair ``[g_scc,scc]``, + where ``g_scc`` is the SCC digraph of g, ``scc`` is a dictionary associating + to each vertex ``v`` the number of the SCC of ``v``, as it appears in + ``g_scc``. + + EXAMPLE:: + + sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components_digraph + sage: strongly_connected_components_digraph(digraphs.Path(3)) + (Digraph on 3 vertices, {0: 2, 1: 1, 2: 0}) + sage: strongly_connected_components_digraph(DiGraph(4)) + (Digraph on 4 vertices, {0: 0, 1: 1, 2: 2, 3: 3}) + + TESTS:: + + sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components_digraph + sage: import random + sage: for i in range(100): + ....: n = random.randint(2,20) + ....: m = random.randint(1, n*(n-1)) + ....: g = digraphs.RandomDirectedGNM(n,m) + ....: scc_digraph,sccs = strongly_connected_components_digraph(g) + ....: assert(scc_digraph.is_directed_acyclic()) + ....: for e in g.edges(): + ....: assert(sccs[e[0]]==sccs[e[1]] or scc_digraph.has_edge(sccs[e[0]],sccs[e[1]])) + ....: assert(sccs[e[0]] >= sccs[e[1]]) + """ + from sage.graphs.digraph import DiGraph + if not isinstance(G, DiGraph): + raise ValueError("G must be a DiGraph.") + + cdef MemoryAllocator mem = MemoryAllocator() + cdef short_digraph g, scc_g + init_short_digraph(g, G) + cdef int * scc = mem.malloc(g.n * sizeof(int)) + cdef int i, j, nscc + cdef list edges = [] + + sig_on() + nscc = tarjan_strongly_connected_components_C(g, scc) + strongly_connected_components_digraph_C(g, nscc, scc, scc_g) + + output = DiGraph(nscc) + + for i in range(scc_g.n): + for j in range(scc_g.neighbors[i+1]-scc_g.neighbors[i]): + edges.append((i, scc_g.neighbors[i][j])) + output.add_edges(edges) + sig_off() + return output, {v:scc[i] for i,v in enumerate(G.vertices())} + + cdef strongly_connected_component_containing_vertex(short_digraph g, short_digraph g_reversed, int v, bitset_t scc): # Computing the set of vertices that can be reached from v in g @@ -438,70 +781,6 @@ cdef void free_short_digraph(short_digraph g): if g.edge_labels != NULL: cpython.Py_XDECREF(g.edge_labels) -def strongly_connected_components(G): - r""" - Returns the strongly connected components of the given DiGraph. - - INPUT: - - - ``G`` -- a DiGraph. - - .. NOTE:: - - This method has been written as an attempt to solve the slowness - reported in :trac:`12235`. It is not the one used by - :meth:`sage.graphs.digraph.DiGraph.strongly_connected_components` as - saving some time on the computation of the strongly connected components - is not worth copying the whole graph, but it is a nice way to test this - module's functions. It is also tested in the doctest or - :meth:`sage.graphs.digraph.DiGraph.strongly_connected_components`. - - EXAMPLE:: - - sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components - sage: g = digraphs.ButterflyGraph(2) - sage: strongly_connected_components(g) - [[('00', 0)], [('00', 1)], [('00', 2)], [('01', 0)], [('01', 1)], [('01', 2)], - [('10', 0)], [('10', 1)], [('10', 2)], [('11', 0)], [('11', 1)], [('11', 2)]] - """ - - if G.order() == 0: - return [[]] - - # To compute the connected component containing a given vertex v, we take - # the intersection of the set of vertices that can be reached from v in G - # and the set of vertices that can be reached from v in G reversed. - # - # That's all that happens here. - - cdef list answer = [] - cdef list vertices = G.vertices() - cdef short_digraph g, gr - - init_short_digraph(g, G) - init_reverse(gr, g) - - cdef bitset_t seen - bitset_init(seen, g.n) - bitset_set_first_n(seen, 0) - - cdef bitset_t scc - bitset_init(scc, g.n) - bitset_set_first_n(scc, 0) - - cdef int v - while bitset_len(seen) < g.n: - v = bitset_first_in_complement(seen) - strongly_connected_component_containing_vertex(g, gr, v, scc) - answer.append([vertices[i] for i in bitset_list(scc)]) - bitset_union(seen, seen, scc) - - bitset_free(seen) - bitset_free(scc) - free_short_digraph(g) - free_short_digraph(gr) - return answer - def triangles_count(G): r""" Return the number of triangles containing `v`, for every `v`. diff --git a/src/sage/graphs/bipartite_graph.py b/src/sage/graphs/bipartite_graph.py index 6713612d359..5dd6026b55d 100644 --- a/src/sage/graphs/bipartite_graph.py +++ b/src/sage/graphs/bipartite_graph.py @@ -919,9 +919,9 @@ def matching_polynomial(self, algorithm="Godsil", name=None): :: - sage: x = polygen(ZZ) + sage: x = polygen(QQ) sage: g = BipartiteGraph(graphs.CompleteBipartiteGraph(16, 16)) - sage: factorial(16)*laguerre(16,x^2) == g.matching_polynomial(algorithm='rook') + sage: bool(factorial(16)*laguerre(16,x^2) == g.matching_polynomial(algorithm='rook')) True Compute the matching polynomial of a line with `60` vertices:: diff --git a/src/sage/graphs/bliss.pyx b/src/sage/graphs/bliss.pyx index 6c64a57cc7c..a0b69588b5e 100644 --- a/src/sage/graphs/bliss.pyx +++ b/src/sage/graphs/bliss.pyx @@ -311,3 +311,4 @@ def canonical_form(G, partition=None, return_graph=False, certify=False): return sorted(edges),relabel return sorted(edges) + diff --git a/src/sage/graphs/centrality.pyx b/src/sage/graphs/centrality.pyx index 5d31cbeb888..9eddc58a5ff 100644 --- a/src/sage/graphs/centrality.pyx +++ b/src/sage/graphs/centrality.pyx @@ -9,6 +9,7 @@ This module is meant for all functions related to centrality in networks. :delim: | :func:`centrality_betweenness` | Return the centrality betweenness of `G` + :func:`centrality_closeness_top_k` | Return the k most closeness central vertices of `G` Functions --------- @@ -22,6 +23,7 @@ from libc.stdint cimport uint32_t from sage.libs.gmp.mpq cimport * from sage.rings.rational cimport Rational from sage.ext.memory cimport check_malloc, check_calloc +from sage.ext.memory_allocator cimport MemoryAllocator ctypedef fused numerical_type: mpq_t @@ -320,3 +322,491 @@ cdef dict centrality_betweenness_C(G, numerical_type _, normalize=True): betweenness_list = [2*x/((n-1)*(n-2)) for x in betweenness_list] return {vv:betweenness_list[i] for i,vv in enumerate(G.vertices())} + +cdef void _estimate_reachable_vertices_dir(short_digraph g, int* reachL, int* reachU): + r""" + For each vertex ``v``, bounds the number of vertices reachable from ``v``. + + The lower bound is stored in reachL[v], while the upper bound is stored + in reachU[v]. These two arrays must be pre-allocated and they must + have size at least `n`, where `n` is the number of nodes of `g`. + + The estimate works as follows: first, we compute the graph of strongly + connected components `\mathcal{G=(V,E)}`, then, for each SCC C, we set: + + .. MATH:: + + L(C)=|C|+\max_{(C,C') \in \mathcal{E}}L(C') \\ + U(C)=|C|+\max_{(C,C') \in \mathcal{E}}L(C') + + By analyzing strongly connected components in reverse topologial order, + we are sure that, as soon as we process component `C`, all components + `C'` appearing on the right hand side have already been processed. + A further improvement on these bounds is obtained by exactly computing + the number of vertices reachable from the biggest strongly connected + component, and handle this component separately. + + Then, for each vertex ``v``, we set ``reachL[v]=L(C)``, where ``C`` is + the strongly connected component containing ``v``. + + INPUT + + ``g`` (short_digraph): the input graph; + + OUTPUT + + ``reachL``, ``reachU``: two arrays that should be allocated outside + this function and that should have size at least ``g.n``. At the end, + ``reachL[v]`` (resp., ``reachU[v]``) will contain the lower (resp., upper) + bound on the number of reachable vertices from ``v``. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef int n = g.n + cdef int* scc = mem.malloc(n * sizeof(int)) + cdef int i, v, w, maxscc = 0 + cdef int nscc = tarjan_strongly_connected_components_C(g, scc) + cdef short_digraph sccgraph + strongly_connected_components_digraph_C(g, nscc, scc, sccgraph) + + cdef int* scc_sizes = mem.calloc(nscc, sizeof(int)) + cdef int nreach_maxscc = 0 + cdef short* reach_max_scc = mem.calloc(nscc, sizeof(short)) + cdef int* reachL_scc = mem.calloc(nscc, sizeof(int)) + cdef uint64_t* reachU_scc = mem.calloc(nscc, sizeof(uint64_t)) + cdef uint64_t* reachU_without_maxscc = mem.calloc(nscc, sizeof(uint64_t)) + # We need uint64_t because these values may become much bigger than g.n, + # up to g.n^2, during the computation. Only at the end, we set reachL and + # reachU as the maximum between g.n and the computed value (so that they + # can be converted to int without overflow). + + # Variables used in BFS from the largest strongly connected component + cdef uint32_t startq, endq + cdef int* q = mem.malloc(nscc * sizeof(int)) + cdef short* reached = mem.calloc(nscc, sizeof(short)) + cdef uint32_t *neigh_start + cdef uint32_t *neigh_end + + # Compute scc_sizes + for i in range(g.n): + scc_sizes[scc[i]] += 1 + + # Compute maxscc + for i in range(nscc): + if scc_sizes[maxscc] < scc_sizes[i]: + maxscc = i + reach_max_scc[maxscc] = 1 + + # BFS to compute number of reachable vertices for the biggest SCC. + q[0] = maxscc + nreach_maxscc = scc_sizes[maxscc] + reached[maxscc] = 1 + startq = 0 + endq = 1 + while startq < endq: + v = q[startq] + startq += 1 + neigh_start = sccgraph.neighbors[v] + neigh_end = sccgraph.neighbors[v+1] + + while (neigh_start < neigh_end): + w = neigh_start[0] + if not reached[w]: + reached[w] = 1 + nreach_maxscc += scc_sizes[w] + q[endq] = w + endq += 1 + neigh_start += 1 + + reachL_scc[maxscc] = nreach_maxscc + reachU_scc[maxscc] = nreach_maxscc + reachU_without_maxscc[maxscc] = 0 + # Dynamic programming to estimate number of reachable vertices for other + # SCCs + for i in range(nscc): + if i == maxscc: + continue + + neigh_start = sccgraph.neighbors[i] + neigh_end = sccgraph.neighbors[i+1] + + while (neigh_start < neigh_end): + w = neigh_start[0] + neigh_start += 1 + + reachL_scc[i] = max(reachL_scc[i], reachL_scc[w]) + reachU_scc[i] += reachU_scc[w] + # Note that this might become much bigger than g.n, up to g.n*g.n. + # Hence we used uint64_t, and only at the end we take the minimum + # between this value and g.n (since g.n is an upper bound on + # the number of reachable vertices). + if not reached[w]: + reachU_without_maxscc[i] += reachU_without_maxscc[w] + reach_max_scc[i] = reach_max_scc[i] or reach_max_scc[w] + + if reach_max_scc[i]: + reachU_scc[i] = reachU_without_maxscc[i] + nreach_maxscc + + reachL_scc[i] += scc_sizes[i] + reachU_scc[i] += scc_sizes[i] + if not reached[i]: + reachU_without_maxscc[i] += scc_sizes[i] + + for i in range(n): + reachL[i] = reachL_scc[scc[i]] + reachU[i] = min(reachU_scc[scc[i]], g.n) + +cdef void _compute_reachable_vertices_undir(short_digraph g, int* reachable): + r""" + For each vertex ``v``, computes the number of vertices reachable from ``v``. + + The number of vertices reachable from ``v`` (which is the size of the + connected component containing ``v``) is stored in variable + ``reachable[v]``. The array ``reachable`` is assumed to be allocated + outside this function, and it is assumed to have size at least ``g.n``. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef int i + cdef int n = g.n + cdef int* q = mem.malloc(n*sizeof(int)) + cdef short* reached = mem.calloc(n, sizeof(short)) + + cdef int v, w + cdef uint32_t *neigh_start + cdef uint32_t *neigh_end + cdef uint32_t startq, endq + cdef list currentcc + + memset(reachable, 0, n * sizeof(int)) + + for i in range(n): + # BFS from i + if reachable[i] != 0: + continue + + reached[i] = 1 + currentcc = [i] + + q[0] = i + startq = 0 + endq = 1 + while startq < endq: + v = q[startq] + startq += 1 + neigh_start = g.neighbors[v] + neigh_end = g.neighbors[v+1] + + while (neigh_start < neigh_end): + w = neigh_start[0] + if not reached[w]: + reached[w] = 1 + currentcc.append(w) + q[endq] = w + endq += 1 + neigh_start += 1 + + for v in currentcc: + reachable[v] = len(currentcc) + +cdef void _sort_vertices_degree(short_digraph g, int *sorted_verts): + r""" + Sorts vertices in decreasing order of degree. + + Uses counting sort, since degrees are between `0` and `n-1`: the running + time is then `O(n)`. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef uint32_t *verts_of_degree = mem.calloc(g.n, sizeof(uint32_t)) + cdef uint32_t *next_vert_of_degree = mem.malloc(g.n * sizeof(uint32_t)) + cdef int d, v + + # Otherwise, segmentation fault + if g.n == 0: + return + + for v in range(g.n): + verts_of_degree[out_degree(g, v)] += 1 + + next_vert_of_degree[g.n - 1] = 0 + for i in range(g.n-2,-1,-1): + next_vert_of_degree[i] = next_vert_of_degree[i+1] + verts_of_degree[i+1] + + for v in range(g.n): + d = out_degree(g, v) + sorted_verts[next_vert_of_degree[d]] = v + next_vert_of_degree[d] += 1 + + +def centrality_closeness_top_k(G, int k=1, int verbose=0): + r""" + Computes the k vertices with largest closeness centrality. + + The algorithm is based on performing a breadth-first-search (BFS) from each + vertex, and to use bounds in order to cut these BFSes as soon as possible. + If k is small, it is much more efficient than computing all centralities + with :meth:`~sage.graphs.generic_graph.GenericGraph.centrality_closeness`. + Conversely, if k is close to the number of nodes, the running-time is + approximately the same (it might even be a bit longer, because more + computations are needed). + For more information, see [BCM15]_. The algorithm does not work on + weighted graphs. + + INPUT: + + - ``G`` a Sage Graph or DiGraph; + + - ``k`` (integer, default: 1): the algorithm will return the ``k`` + vertices with largest closeness centrality. This value should be between + 1 and the number of vertices with positive (out)degree, because the + closeness centrality is not defined for vertices with (out)degree 0. If + ``k`` is bigger than this value, the output will contain all vertices + of positive (out)degree. + + - ``verbose`` (integer, default: 0): an integer defining + how "verbose" the algorithm should be. If + 0, nothing is printed, if 1, we print only the performance ratio at + the end of the algorithm, if 2, we print partial results every 1000 + visits, if 3, we print partial results after every visit. + + OUTPUT: + + An ordered list of ``k`` pairs ``(closv, v)``, where ``v`` is one of the + ``k`` most central vertices, and ``closv`` is its closeness centrality. + If ``k`` is bigger than the number of vertices with positive (out)degree, + the list might be smaller. + + REFERENCES: + + .. [BCM15] Michele Borassi, Pierluigi Crescenzi, and Andrea Marino, + Fast and Simple Computation of Top-k Closeness Centralities. + Preprint on arXiv. + http://arxiv.org/abs/1507.01490 + + EXAMPLES:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: g = graphs.PathGraph(10) + sage: centrality_closeness_top_k(g, 4, 1) + Final performance ratio: 0.711111111111 + [(0.36, 5), + (0.36, 4), + (0.3333333333333333, 6), + (0.3333333333333333, 3)] + sage: g = digraphs.Path(10) + sage: centrality_closeness_top_k(g, 5, 1) + Final performance ratio: 0.422222222222 + [(0.2, 0), + (0.19753086419753085, 1), + (0.19444444444444442, 2), + (0.19047619047619047, 3), + (0.18518518518518517, 4)] + + TESTS: + + If ``k`` or ``verbose`` is not an integer:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: g = digraphs.Path(10) + sage: centrality_closeness_top_k(g, 'abc', 1) + Traceback (most recent call last): + ... + TypeError: an integer is required + sage: centrality_closeness_top_k(g, 1, 'abc') + Traceback (most recent call last): + ... + TypeError: an integer is required + + If ``k`` is bigger than the number of nodes:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: g = graphs.PathGraph(5) + sage: centrality_closeness_top_k(g, 10, 0) + [(0.6666666666666666, 2), + (0.5714285714285714, 3), + (0.5714285714285714, 1), + (0.4, 4), + (0.4, 0)] + + Empty graph:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: g = Graph() + sage: centrality_closeness_top_k(g, 10, 0) + [] + sage: g = Graph(10) + sage: centrality_closeness_top_k(g, 10, 0) + [] + + The result is correct:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: import random + sage: n = 20 + sage: m = random.randint(1, n*(n-1) / 2) + sage: k = random.randint(1, n) + sage: g = graphs.RandomGNM(n,m) + sage: topk = centrality_closeness_top_k(g, k) + sage: centr = g.centrality_closeness(algorithm='BFS') + sage: sorted_centr = sorted(centr.values(), reverse=True) + sage: assert(len(topk)==min(k, len(sorted_centr))) + sage: for i in range(len(topk)): + ....: assert(abs(topk[i][0] - sorted_centr[i]) < 1e-12) + + Directed case:: + + sage: from sage.graphs.centrality import centrality_closeness_top_k + sage: import random + sage: n = 20 + sage: m = random.randint(1, n*(n-1)) + sage: k = random.randint(1, n) + sage: g = digraphs.RandomDirectedGNM(n,m) + sage: topk = centrality_closeness_top_k(g, k) + sage: centr = g.centrality_closeness(algorithm='BFS') + sage: sorted_centr = sorted(centr.values(), reverse=True) + sage: assert(len(topk)==min(k, len(sorted_centr))) + sage: for i in range(len(topk)): + ....: assert(abs(topk[i][0] - sorted_centr[i]) < 1e-12) + """ + + if k >= G.num_verts(): + closeness_dict = G.centrality_closeness(by_weight=False,algorithm='BFS') + return sorted([(closz, z) for z,closz in closeness_dict.iteritems()], reverse=True) + if G.num_verts()==0 or G.num_verts()==1: + return [] + + sig_on() + cdef MemoryAllocator mem = MemoryAllocator() + cdef short_digraph sd + # 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 + init_short_digraph(sd, G) + cdef int n = sd.n + cdef int *reachL = mem.malloc(n * sizeof(int)) + cdef int *reachU + cdef int *pred = mem.calloc(n, sizeof(int)) + cdef double *farness = mem.malloc(n * sizeof(double)) + cdef int d, nd, x, v, w + cdef long f, gamma + cdef int *queue = mem.malloc(n * sizeof(int)) + cdef double tildefL, tildefU + cdef bint stopped + cdef uint32_t * p_tmp + cdef int layer_current_beginning, layer_current_end, layer_next_end=0 + cdef long visited = 0 + cdef int nvis = 0 + cdef short *seen = mem.calloc(n, sizeof(short)) + cdef bint directed = G.is_directed() + + cdef int *topk = mem.malloc(k * sizeof(int)) + for i in range(k): + topk[i] = -1 + for i in range(n): + pred[i] = -1 + + cdef double kth = n + cdef int *sorted_vert = mem.malloc(n * sizeof(int)) + if directed: + reachU = mem.malloc(n * sizeof(int)) + _estimate_reachable_vertices_dir(sd, reachL, reachU) + else: + _compute_reachable_vertices_undir(sd, reachL) + reachU = reachL + _sort_vertices_degree(sd, sorted_vert) + + for x in sorted_vert[:n]: + if out_degree(sd, x) == 0: + break + # We start a BFSCut from x: + + # We reset variable seen: + for v in queue[:layer_next_end]: + seen[v] = 0 + pred[v] = -1 + + layer_current_beginning = 0 + layer_current_end = 1 + layer_next_end = 1 + d = 0 + f = 0 + # We are at level 0, and gamma is the number of arcs exiting level 0 + # (hence, deg(x)). + gamma = out_degree(sd, x) + nd = 1 + queue[0] = x + stopped = False + seen[x] = 1 + nvis += 1 + + # The graph is explored layer by layer. + while layer_current_beginning(reachL[x]-nd))) * (n-1)) / (((reachL[x]-1))*(reachL[x]-1)) + tildefU = ((f - gamma + (d+2) * ((reachU[x]-nd))) * (n-1)) / (((reachU[x]-1))*(reachU[x]-1)) + d += 1 + gamma = 0 + + if tildefL >= kth and tildefU >= kth: + farness[x] = n + stopped = True + break + # Looking for all non-discovered neighbors of some vertex of the + # current layer. + for j in range(layer_current_beginning,layer_current_end): + u = queue[j] + + # List the neighors of u + p_tmp = sd.neighbors[u] + while p_tmp(reachL[x]-1)*(reachL[x]-1)) + tildefU += (n-1) / ((reachU[x]-1)*(reachU[x]-1)) + if tildefL >= kth and tildefU >= kth: + farness[x] = n + stopped = True + if stopped: + break + # 'next_layer' becomes 'current_layer' + layer_current_beginning = layer_current_end + layer_current_end = layer_next_end + + if not stopped: + farness[x] = (( f) * (n-1)) / ((nd-1) * (nd-1)) + + if farness[x] < kth: + for i in range(k): + if topk[i] == -1 or farness[topk[i]] == kth: + topk[i] = x + break + kth = 0 + for i in range(k): + if topk[i] == -1: + kth = n + break + kth = max(kth, farness[topk[i]]) + if verbose >= 3 or (verbose == 2 and nvis % 1000 == 0): + print "Visit {} from {}:".format(nvis, x) + print " Lower bound: {}".format(1 / kth) + print " Perf. ratio: {}".format(visited / (nvis * (sd.neighbors[sd.n]-sd.edges))) + sig_off() + + if verbose > 0: + print "Final performance ratio: {}".format(visited / (n * (sd.neighbors[sd.n]-sd.edges))) + + cdef list V = G.vertices() + return sorted([(1.0/farness[v], V[v]) for v in topk[:k] if v != -1], reverse=True) diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 029b96323a4..e20173a7f4e 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -31,7 +31,7 @@ :meth:`~DiGraph.incoming_edges` | Returns a list of edges arriving at vertices. :meth:`~DiGraph.incoming_edge_iterator` | Return an iterator over all arriving edges from vertices :meth:`~DiGraph.sources` | Returns the list of all sources (vertices without incoming edges) of this digraph. - :meth:`~DiGraph.sinks` | Returns the list of all sinks (vertices without outoing edges) of this digraph. + :meth:`~DiGraph.sinks` | Returns the list of all sinks (vertices without outgoing edges) of this digraph. :meth:`~DiGraph.to_undirected` | Returns an undirected version of the graph. :meth:`~DiGraph.to_directed` | Since the graph is already directed, simply returns a copy of itself. :meth:`~DiGraph.is_directed` | Since digraph is directed, returns True. @@ -119,92 +119,86 @@ class DiGraph(GenericGraph): - """Directed graph. + r""" + Directed graph. A digraph or directed graph is a set of vertices connected by oriented - edges. For more information, see the - `Wikipedia article on digraphs - `_. + edges. See also the :wikipedia:`Directed_graph`. For a collection of + pre-defined digraphs, see the :mod:`~sage.graphs.digraph_generators` module. - One can very easily create a directed graph in Sage by typing:: + A :class:`DiGraph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.digraph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.graph`. - sage: g = DiGraph() - - By typing the name of the digraph, one can get some basic information - about it:: - - sage: g - Digraph on 0 vertices - - This digraph is not very interesting as it is by default the empty - graph. But Sage contains several pre-defined digraph classes that can - be listed this way: - - * Within a Sage sessions, type ``digraphs.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". + INPUT: - You will see a list of methods which will construct named digraphs. For - example:: + By default, a :class:`DiGraph` object is simple (i.e. no *loops* nor + *multiple edges*) and unweighted. This can be easily tuned with the + appropriate flags (see below). - sage: g = digraphs.ButterflyGraph(3) - sage: g.plot() - Graphics object consisting of 81 graphics primitives + - ``data`` -- can be any of the following (see the ``format`` argument): - You can also use the collection of pre-defined graphs, then create a - digraph from them. :: + #. ``DiGraph()`` -- build a digraph on 0 vertices. - sage: g = DiGraph(graphs.PetersenGraph()) - sage: g.plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph(5)`` -- return an edgeless digraph on the 5 vertices 0,...,4. - Calling ``Digraph`` on a graph returns the original graph in which every - edge is replaced by two different edges going toward opposite directions. + #. ``DiGraph([list_of_vertices,list_of_edges])`` -- returns a digraph with + given vertices/edges. - In order to obtain more information about these digraph constructors, - access the documentation by typing ``digraphs.RandomDirectedGNP?``. + To bypass auto-detection, prefer the more explicit + ``DiGraph([V,E],format='vertices_and_edges')``. - Once you have defined the digraph you want, you can begin to work on it - by using the almost 200 functions on graphs and digraphs in the Sage - library! If your digraph is named ``g``, you can list these functions as - previously this way + #. ``DiGraph(list_of_edges)`` -- return a digraph with a given list of + edges (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". + To bypass auto-detection, prefer the more explicit ``DiGraph(L, + format='list_of_edges')``. - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + #. ``DiGraph({1:[2,3,4],3:[4]})`` -- return a digraph by associating to + each vertex the list of its out-neighbors. - If you have defined a digraph ``g`` having several connected components - ( i.e. ``g.is_connected()`` returns False ), you can print each one of its - connected components with only two lines:: + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_lists')``. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a digraph by + associating a list of out-neighbors to each vertex and providing its + edge label. - The same methods works for strongly connected components :: + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_dicts')``. - sage: for component in g.strongly_connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + For digraphs with multiple edges, you can provide a list of labels + instead, e.g.: ``DiGraph({1: {2: ['a1', 'a2'], 3:['b']} + ,3:{2:['c']}})``. + #. ``DiGraph(a_matrix)`` -- return a digraph with given (weighted) adjacency + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - INPUT: + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - - ``data`` - can be any of the following (see the ``format`` keyword): + #. ``DiGraph(a_nonsquare_matrix)`` -- return a digraph with given + incidence matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='incidence_matrix')``. - #. A dictionary of lists + #. ``DiGraph([V, f])`` -- return a digraph with a vertex set ``V`` and an + edge `u,v` whenever ``f(u,v)`` is ``True``. Example: ``DiGraph([ + [1..10], lambda x,y: abs(x-y).is_square()])`` - #. A Sage adjacency matrix or incidence matrix + #. ``DiGraph('FOC@?OC@_?')`` -- return a digraph from a dig6 string (see + documentation of :meth:`dig6_string`). - #. A pygraphviz graph - - #. A NetworkX digraph + #. ``DiGraph(another_digraph)`` -- return a digraph from a Sage (di)graph, + `pygraphviz `__ digraph, `NetworkX + `__ digraph, or `igraph + `__ digraph. - ``pos`` - a positioning dictionary: for example, the spring layout from NetworkX for the 5-cycle is:: @@ -227,33 +221,13 @@ class DiGraph(GenericGraph): - ``weighted`` - whether digraph thinks of itself as weighted or not. See self.weighted() - - ``format`` - if None, DiGraph tries to guess- can be - several values, including: - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``NX`` - data must be a NetworkX DiGraph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX digraph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``boundary`` - a list of boundary vertices, if - empty, digraph is considered as a 'graph without boundary' + - ``format`` - if set to ``None`` (default), :class:`DiGraph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"dig6"``, ``"rule"``, ``"list_of_edges"``, ``"dict_of_lists"``, + ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"incidence_matrix"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -278,7 +252,7 @@ class DiGraph(GenericGraph): ``data_structure='static_sparse'``. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -348,7 +322,7 @@ class DiGraph(GenericGraph): [ 0 1 -1] [ -1 0 -1/2] [ 1 1/2 0] - sage: G = DiGraph(M,sparse=True); G + sage: G = DiGraph(M,sparse=True,weighted=True); G Digraph on 3 vertices sage: G.weighted() True @@ -398,6 +372,31 @@ class DiGraph(GenericGraph): sage: DiGraph(g) Digraph on 5 vertices + #. An igraph directed Graph (see also + :meth:`~sage.graphs.generic_graph.GenericGraph.igraph_graph`):: + + sage: import igraph # optional - python_igraph + sage: g = igraph.Graph([(0,1),(0,2)], directed=True) # optional - python_igraph + sage: DiGraph(g) # optional - python_igraph + Digraph on 3 vertices + + If ``vertex_labels`` is ``True``, the names of the vertices are given by + the vertex attribute ``'name'``, if available:: + + sage: g = igraph.Graph([(0,1),(0,2)], directed=True, vertex_attrs={'name':['a','b','c']}) # optional - python_igraph + sage: DiGraph(g).vertices() # optional - python_igraph + ['a', 'b', 'c'] + sage: g = igraph.Graph([(0,1),(0,2)], directed=True, vertex_attrs={'label':['a','b','c']}) # optional - python_igraph + sage: DiGraph(g).vertices() # optional - python_igraph + [0, 1, 2] + + If the igraph Graph has edge attributes, they are used as edge labels:: + + sage: g = igraph.Graph([(0,1),(0,2)], directed=True, edge_attrs={'name':['a','b'], 'weight':[1,3]}) # optional - python_igraph + sage: DiGraph(g).edges() # optional - python_igraph + [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})] + + TESTS:: sage: DiGraph({0:[1,2,3], 2:[4]}).edges() @@ -407,12 +406,6 @@ class DiGraph(GenericGraph): sage: DiGraph({0:Set([1,2,3]), 2:Set([4])}).edges() [(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)] - Get rid of mutable default argument for `boundary` (:trac:`14794`):: - - sage: D = DiGraph(boundary=None) - sage: D._boundary - [] - Demonstrate that digraphs using the static backend are equal to mutable graphs but can be used as dictionary keys:: @@ -443,11 +436,17 @@ class DiGraph(GenericGraph): sage: type(J_imm._backend) == type(G_imm._backend) True + From a a list of vertices and a list of edges:: + + sage: G = DiGraph([[1,2,3],[(1,2)]]); G + Digraph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = True def __init__(self, data=None, pos=None, loops=None, format=None, - boundary=None, weighted=None, implementation='c_graph', + weighted=None, implementation='c_graph', data_structure="sparse", vertex_labels=True, name=None, multiedges=None, convert_empty_dict_labels_to_None=None, sparse=True, immutable=False): @@ -534,12 +533,20 @@ def __init__(self, data=None, pos=None, loops=None, format=None, sage: copy(g) is g # copy is mutable again False - TESTS:: + Unknown input format:: sage: DiGraph(4,format="HeyHeyHey") Traceback (most recent call last): ... ValueError: Unknown input format 'HeyHeyHey' + + Sage DiGraph from igraph undirected graph:: + + sage: import igraph # optional - python_igraph + sage: DiGraph(igraph.Graph()) # optional - python_igraph + Traceback (most recent call last): + ... + ValueError: A *directed* igraph graph was expected. To build an undirected graph, call the Graph constructor. """ msg = '' GenericGraph.__init__(self) @@ -598,6 +605,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if format is None and isinstance(data,list) and \ len(data)>=2 and callable(data[1]): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -613,6 +629,17 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'NX' elif isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)): format = 'NX' + if (format is None and + hasattr(data, 'vcount') and + hasattr(data, 'get_edgelist')): + try: + import igraph + except ImportError: + raise ImportError("The data seems to be a igraph object, but "+ + "igraph is not installed in Sage. To install "+ + "it, run 'sage -i python_igraph'") + if format is None and isinstance(data, igraph.Graph): + format = 'igraph' if format is None and isinstance(data, (int, Integer)): format = 'int' if format is None and data is None: @@ -638,104 +665,20 @@ def __init__(self, data=None, pos=None, loops=None, format=None, # At this point, format has been set. We build the graph if format == 'dig6': - if weighted is None: weighted = False - if not isinstance(data, str): - raise ValueError('If input format is dig6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_dig6(s, n) - expected = n**2 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + if weighted is None: self._weighted = False self.allow_loops(True if loops else False,check=False) self.allow_multiple_edges(True if multiedges else False,check=False) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(n): - if m[k] == '1': - self._backend.add_edge(i, j, None, True) - k += 1 - elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False + from graph_input import from_dig6 + from_dig6(self, data) - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + elif format == 'adjacency_matrix': + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(True if loops else False,check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - positions = [] - for c in data.columns(): - NZ = c.nonzero_positions() - if len(NZ) != 2: - msg += "There must be two nonzero entries (-1 & 1) per column." - raise ValueError(msg) - L = sorted(set(c.list())) - if L != [-1,0,1]: - msg += "Each column represents an edge: -1 goes to 1." - raise ValueError(msg) - if c[NZ[0]] == -1: - positions.append(tuple(NZ)) - else: - positions.append((NZ[1],NZ[0])) - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = ( len(set(positions)) < total ) - self.allow_loops(True if loops else False,check=False) - self.allow_multiple_edges(multiedges,check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) + from graph_input import from_oriented_incidence_matrix + from_oriented_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'DiGraph': if loops is None: loops = data.allows_loops() elif not loops and data.has_loops(): @@ -762,67 +705,22 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.allow_loops(loops,check=False) self.add_vertices(data[0]) self.add_edges((u,v) for u in data[0] for v in data[0] if f(u,v)) + + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) + elif format == 'dict_of_dicts': - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set(data.keys()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: loops = False - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if v not in verts: verts.add(v) - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: - multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multidigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - self.add_vertices(verts) + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if multiedges: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,labels in Nu.iteritems() for l in labels) - else: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,l in Nu.iteritems()) elif format == 'dict_of_lists': - # convert to a dict of lists if not already one - if not all(isinstance(data[u], list) for u in data): - data = {u: list(v) for u,v in data.iteritems()} - - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False - - if weighted is None: weighted = False + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not multiedges and any(len(set(neighb)) != len(neighb) for neighb in data.itervalues()): - if multiedges is False: - uv = next((u,v) for u,neighb in data.iteritems() for v in neighb if neighb.count(v) > 1) - raise ValueError("Non-multidigraph got several edges (%s,%s)"%(u,v)) - multiedges = True - if multiedges is None: - multiedges = False - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - verts = set().union(data.keys(),*data.values()) - self.add_vertices(verts) - self.add_edges((u,v) for u,Nu in data.iteritems() for v in Nu) elif format == 'NX': # adjust for empty dicts instead of None in NetworkX default edge labels if convert_empty_dict_labels_to_None is None: @@ -850,6 +748,19 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.allow_loops(loops,check=False) self.add_vertices(data.nodes()) self.add_edges((u,v,r(l)) for u,v,l in data.edges_iter(data=True)) + elif format == 'igraph': + if not data.is_directed(): + raise ValueError("A *directed* igraph graph was expected. To "+ + "build an undirected graph, call the Graph " + "constructor.") + + self.add_vertices(range(data.vcount())) + self.add_edges([(e.source, e.target, e.attributes()) for e in data.es()]) + + if vertex_labels and 'name' in data.vertex_attributes(): + vs = data.vs() + self.relabel({v:vs[v]['name'] for v in self}) + elif format == 'int': if weighted is None: weighted = False self.allow_loops(True if loops else False,check=False) @@ -886,13 +797,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self._weighted = weighted self._pos = pos - self._boundary = boundary if boundary is not None else [] + if format != 'DiGraph' or name is not None: self.name(name) if data_structure == "static_sparse": from sage.graphs.base.static_sparse_backend import StaticSparseBackend - ib = StaticSparseBackend(self, loops = loops, multiedges = multiedges) + ib = StaticSparseBackend(self, + loops = self.allows_loops(), + multiedges = self.allows_multiple_edges()) self._backend = ib self._immutable = True @@ -1082,7 +995,6 @@ def to_undirected(self, implementation='c_graph', data_structure=None, from sage.graphs.all import Graph G = Graph(name = self.name(), pos = self._pos, - boundary = self._boundary, multiedges = self.allows_multiple_edges(), loops = self.allows_loops(), implementation = implementation, @@ -2929,7 +2841,7 @@ def layout_acyclic(self, rankdir="up", **options): available (see :meth:`.layout_graphviz`), and using a spring layout with fixed vertical placement of the vertices otherwise (see :meth:`.layout_acyclic_dummy` and - :meth:`~GenericGraph.ranked_layout`). + :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`). Non acyclic graphs are partially supported by ``graphviz``, which then chooses some edges to point down. @@ -2938,8 +2850,9 @@ def layout_acyclic(self, rankdir="up", **options): - ``rankdir`` -- 'up', 'down', 'left', or 'right' (default: 'up'): which direction the edges should point toward - - ``**options`` -- passed down to :meth:`layout_ranked` or - :meth:`layout_graphviz` + - ``**options`` -- passed down to + :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked` or + :meth:`~sage.graphs.generic_graph.GenericGraph.layout_graphviz` EXAMPLES:: @@ -2964,6 +2877,7 @@ def layout_acyclic(self, rankdir="up", **options): sage: pos = H.layout_acyclic(rankdir='left') sage: pos[1][0] < pos[0][0] - .5 True + """ if have_dot2tex(): return self.layout_graphviz(rankdir=rankdir, **options) @@ -2976,16 +2890,17 @@ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options): To this end, the heights of the vertices are set according to the level set decomposition of the graph (see - :meth:`.level_sets`). This is achieved by a spring layout with + :meth:`level_sets`). This is achieved by a spring layout with fixed vertical placement of the vertices otherwise (see - :meth:`.layout_acyclic_dummy` and - :meth:`~GenericGraph.ranked_layout`). + :meth:`layout_acyclic_dummy` and + :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`). INPUT: - ``rankdir`` -- 'up', 'down', 'left', or 'right' (default: 'up'): which direction the edges should point toward - - ``**options`` -- passed down to :meth:`layout_ranked` + - ``**options`` -- passed down to + :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked` EXAMPLES:: @@ -3007,6 +2922,7 @@ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options): Traceback (most recent call last): ... ValueError: `self` should be an acyclic graph + """ if heights is None: if not self.is_directed_acyclic(): @@ -3083,57 +2999,6 @@ def level_sets(self): level = new_level return Levels - def strongly_connected_components(self): - """ - Returns the list of strongly connected components. - - EXAMPLES:: - - sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) - sage: D.connected_components() - [[0, 1, 2, 3], [4, 5, 6]] - sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } ) - sage: D.strongly_connected_components() - [[0], [1], [2], [3], [4], [5], [6]] - sage: D.add_edge([2,0]) - sage: D.strongly_connected_components() - [[0, 1, 2], [3], [4], [5], [6]] - - TESTS: - - Checking against NetworkX, and another of Sage's implementations:: - - sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components - sage: import networkx - sage: for i in range(100): # long - ... g = digraphs.RandomDirectedGNP(100,.05) # long - ... h = g.networkx_graph() # long - ... scc1 = g.strongly_connected_components() # long - ... scc2 = networkx.strongly_connected_components(h) # long - ... scc3 = strongly_connected_components(g) # long - ... s1 = Set(map(Set,scc1)) # long - ... s2 = Set(map(Set,scc2)) # long - ... s3 = Set(map(Set,scc3)) # long - ... if s1 != s2: # long - ... print "Ooch !" # long - ... if s1 != s3: # long - ... print "Oooooch !" # long - - """ - - try: - vertices = set(self.vertices()) - scc = [] - while vertices: - tmp = self.strongly_connected_component_containing_vertex(next(vertices.__iter__())) - vertices.difference_update(set(tmp)) - scc.append(tmp) - return scc - - except AttributeError: - import networkx - return networkx.strongly_connected_components(self.networkx_graph(copy=False)) - def strongly_connected_component_containing_vertex(self, v): """ Returns the strongly connected component containing a given vertex @@ -3219,7 +3084,7 @@ def strongly_connected_components_digraph(self, keep_labels = False): sage: scc_digraph.vertices() [{0}, {3}, {1, 2}] sage: scc_digraph.edges() - [({0}, {3}, None), ({0}, {1, 2}, None), ({1, 2}, {3}, None)] + [({0}, {1, 2}, None), ({0}, {3}, None), ({1, 2}, {3}, None)] By default, the labels are discarded, and the result has no loops nor multiple edges. If ``keep_labels`` is ``True``, then @@ -3234,10 +3099,12 @@ def strongly_connected_components_digraph(self, keep_labels = False): sage: scc_digraph.vertices() [{0}, {3}, {1, 2}] sage: scc_digraph.edges() - [({0}, {3}, '0-3'), ({0}, {1, 2}, '0-12'), - ({1, 2}, {3}, '1-3'), ({1, 2}, {3}, '2-3'), - ({1, 2}, {1, 2}, '1-2'), ({1, 2}, {1, 2}, '2-1')] - + [({0}, {1, 2}, '0-12'), + ({0}, {3}, '0-3'), + ({1, 2}, {1, 2}, '1-2'), + ({1, 2}, {1, 2}, '2-1'), + ({1, 2}, {3}, '1-3'), + ({1, 2}, {3}, '2-3')] """ from sage.sets.set import Set @@ -3589,3 +3456,6 @@ def flow_polytope(self, edges=None, ends=None): import sage.graphs.comparability DiGraph.is_transitive = types.MethodType(sage.graphs.comparability.is_transitive, None, DiGraph) + +from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components +DiGraph.strongly_connected_components = types.MethodType(tarjan_strongly_connected_components, None, DiGraph) diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index e484f84cbde..7201e2f6d3c 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -22,6 +22,7 @@ :meth:`~DiGraphGenerators.ButterflyGraph` | Returns a n-dimensional butterfly graph. :meth:`~DiGraphGenerators.Circuit` | Returns the circuit on `n` vertices. :meth:`~DiGraphGenerators.Circulant` | Returns a circulant digraph on `n` vertices from a set of integers. + :meth:`~DiGraphGenerators.Complete` | Return a complete digraph on `n` vertices. :meth:`~DiGraphGenerators.DeBruijn` | Returns the De Bruijn digraph with parameters `k,n`. :meth:`~DiGraphGenerators.GeneralizedDeBruijn` | Returns the generalized de Bruijn digraph of order `n` and degree `d`. :meth:`~DiGraphGenerators.ImaseItoh` | Returns the digraph of Imase and Itoh of order `n` and degree `d`. @@ -32,10 +33,12 @@ :meth:`~DiGraphGenerators.RandomDirectedGNP` | Returns a random digraph on `n` nodes. :meth:`~DiGraphGenerators.RandomDirectedGN` | Returns a random GN (growing network) digraph with `n` vertices. :meth:`~DiGraphGenerators.RandomDirectedGNR` | Returns a random GNR (growing network with redirection) digraph. + :meth:`~DiGraphGenerators.RandomSemiComplete` | Return a random semi-complete digraph of order `n`. :meth:`~DiGraphGenerators.RandomTournament` | Returns a random tournament on `n` vertices. :meth:`~DiGraphGenerators.TransitiveTournament`| Returns a transitive tournament on `n` vertices. :meth:`~DiGraphGenerators.tournaments_nauty` | Returns all tournaments on `n` vertices using Nauty. + AUTHORS: - Robert L. Miller (2006) @@ -82,8 +85,11 @@ class DiGraphGenerators(): - RandomDirectedGNP - RandomDirectedGNM - RandomDirectedGNR + - RandomTournament + - RandomSemiComplete Families of Graphs: + - Complete - DeBruijn - GeneralizedDeBruijn - Kautz @@ -384,6 +390,12 @@ def RandomTournament(self, n): - ``n`` (integer) -- number of vertices. + .. SEEALSO:: + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.Complete` + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomSemiComplete` + EXAMPLES:: sage: T = digraphs.RandomTournament(10); T @@ -506,6 +518,53 @@ def tournaments_nauty(self, n, yield G + + def Complete(self, n, loops=False): + r""" + Return the complete digraph on `n` vertices. + + INPUT: + + - ``n`` (integer) -- number of vertices. + + - ``loops`` (boolean) -- whether to add loops or not, i.e., edges from + `u` to itself. + + .. SEEALSO:: + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomSemiComplete` + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomTournament` + + EXAMPLES:: + + sage: n = 10 + sage: G = digraphs.Complete(n); G + Complete digraph: Digraph on 10 vertices + sage: G.size() == n*(n-1) + True + sage: G = digraphs.Complete(n, loops=True); G + Complete digraph with loops: Looped digraph on 10 vertices + sage: G.size() == n*n + True + sage: digraphs.Complete(-1) + Traceback (most recent call last): + ... + ValueError: The number of vertices cannot be strictly negative! + """ + G = DiGraph(n, name="Complete digraph"+(" with loops" if loops else ''), loops=loops) + + if loops: + G.add_edges((u,u) for u in range(n)) + + G.add_edges((u,v) for u in range(n) for v in range(n) if u!=v) + + if n: + from sage.graphs.graph_plot import _circle_embedding + _circle_embedding(G, range(n)) + + return G + def Circuit(self,n): r""" Returns the circuit on `n` vertices @@ -1199,6 +1258,63 @@ def RandomDirectedGNR(self, n, p, seed=None): import networkx return DiGraph(networkx.gnc_graph(n, seed=seed)) + def RandomSemiComplete(self, n): + r""" + Return a random semi-complete digraph on `n` vertices. + + A directed graph `G=(V,E)` is *semi-complete* if for any pair of + vertices `u` and `v`, there is *at least* one arc between them. + + To generate randomly a semi-complete digraph, we have to ensure, for any + pair of distinct vertices `u` and `v`, that with probability `1/3` we + have only arc `uv`, with probability `1/3` we have only arc `vu`, and + with probability `1/3` we have both arc `uv` and arc `vu`. We do so by + selecting a random integer `coin` in `[1,3]`. When `coin==1` we select + only arc `uv`, when `coin==3` we select only arc `vu`, and when + `coin==2` we select both arcs. In other words, we select arc `uv` when + `coin\leq 2` and arc `vu` when `coin\geq 2`. + + INPUT: + + - ``n`` (integer) -- the number of nodes + + .. SEEALSO:: + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.Complete` + + - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomTournament` + + EXAMPLES:: + + sage: SC = digraphs.RandomSemiComplete(10); SC + Random Semi-Complete digraph: Digraph on 10 vertices + sage: SC.size() >= binomial(10, 2) + True + sage: digraphs.RandomSemiComplete(-1) + Traceback (most recent call last): + ... + ValueError: The number of vertices cannot be strictly negative! + """ + G = DiGraph(n, name="Random Semi-Complete digraph") + + # For each pair u,v we choose a randon number ``coin`` in [1,3]. + # We select edge `(u,v)` if `coin==1` or `coin==2`. + # We select edge `(v,u)` if `coin==2` or `coin==3`. + import itertools + from sage.misc.prandom import randint + for u,v in itertools.combinations(range(n), 2): + coin = randint(1,3) + if coin<=2: + G.add_edge(u,v) + if coin>=2: + G.add_edge(v,u) + + if n: + from sage.graphs.graph_plot import _circle_embedding + _circle_embedding(G, range(n)) + + return G + ################################################################################ # DiGraph Iterators ################################################################################ diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index c9bcc2193ec..db1238e0af3 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -688,10 +688,7 @@ def distances_and_predecessors_all_pairs(G): for i in range(n): - if c_distances[i] == -1: - t_distance[int_to_vertex[i]] = Infinity - t_predecessor[int_to_vertex[i]] = None - else: + if c_distances[i] != -1: t_distance[int_to_vertex[i]] = c_distances[i] t_predecessor[int_to_vertex[i]] = int_to_vertex[c_predecessor[i]] @@ -1303,11 +1300,12 @@ def diameter(G, method='iFUB', source=None): EXAMPLES:: + sage: from sage.graphs.distances_all_pairs import diameter sage: G = graphs.PetersenGraph() - sage: G.diameter(method='iFUB') + sage: diameter(G, method='iFUB') 2 sage: G = Graph( { 0 : [], 1 : [], 2 : [1] } ) - sage: G.diameter(method='iFUB') + sage: diameter(G, method='iFUB') +Infinity @@ -1315,21 +1313,21 @@ def diameter(G, method='iFUB', source=None): never be negative, we define it to be zero:: sage: G = graphs.EmptyGraph() - sage: G.diameter(method='iFUB') + sage: diameter(G, method='iFUB') 0 Comparison of exact methods:: sage: G = graphs.RandomBarabasiAlbert(100, 2) - sage: d1 = G.diameter(method='standard') - sage: d2 = G.diameter(method='iFUB') - sage: d3 = G.diameter(method='iFUB', source=G.random_vertex()) + sage: d1 = diameter(G, method='standard') + sage: d2 = diameter(G, method='iFUB') + sage: d3 = diameter(G, method='iFUB', source=G.random_vertex()) sage: if d1!=d2 or d1!=d3: print "Something goes wrong!" Comparison of lower bound methods:: - sage: lb2 = G.diameter(method='2sweep') - sage: lbm = G.diameter(method='multi-sweep') + sage: lb2 = diameter(G, method='2sweep') + sage: lbm = diameter(G, method='multi-sweep') sage: if not (lb2<=lbm and lbm<=d3): print "Something goes wrong!" TEST: @@ -1337,7 +1335,7 @@ def diameter(G, method='iFUB', source=None): This was causing a segfault. Fixed in :trac:`17873` :: sage: G = graphs.PathGraph(1) - sage: G.diameter(method='iFUB') + sage: diameter(G, method='iFUB') 0 """ cdef int n = G.order() @@ -1721,18 +1719,19 @@ def floyd_warshall(gg, paths = True, distances = False): if paths: d_prec = {} if distances: d_dist = {} for v_int in gverts: - if paths: tmp_prec = {} - if distances: tmp_dist = {} v = cgb.vertex_label(v_int) + if paths: tmp_prec = {v:None} + if distances: tmp_dist = {v:0} dv = dist[v_int] for u_int in gverts: u = cgb.vertex_label(u_int) - if paths: - tmp_prec[u] = (None if v == u - else cgb.vertex_label(prec[v_int][u_int])) - if distances: - tmp_dist[u] = (dv[u_int] if (dv[u_int] != -1) - else Infinity) + if v != u and dv[u_int] != -1: + if paths: + tmp_prec[u] = cgb.vertex_label(prec[v_int][u_int]) + + if distances: + tmp_dist[u] = dv[u_int] + if paths: d_prec[v] = tmp_prec if distances: d_dist[v] = tmp_dist diff --git a/src/sage/graphs/generators/basic.py b/src/sage/graphs/generators/basic.py index e590f893871..9d548c01a49 100644 --- a/src/sage/graphs/generators/basic.py +++ b/src/sage/graphs/generators/basic.py @@ -25,22 +25,17 @@ def BullGraph(): Returns a bull graph with 5 nodes. A bull graph is named for its shape. It's a triangle with horns. - This constructor depends on `NetworkX `_ - numeric labeling. For more information, see this + For more information, see this :wikipedia:`Wikipedia article on the bull graph `. PLOTTING: - Upon construction, the position dictionary is filled to - override the spring-layout algorithm. By convention, the bull graph - is drawn as a triangle with the first node (0) on the bottom. The - second and third nodes (1 and 2) complete the triangle. Node 3 is - the horn connected to 1 and node 4 is the horn connected to node - 2. - - ALGORITHM: + Upon construction, the position dictionary is filled to override the + spring-layout algorithm. By convention, the bull graph is drawn as a + triangle with the first node (0) on the bottom. The second and third nodes + (1 and 2) complete the triangle. Node 3 is the horn connected to 1 and node + 4 is the horn connected to node 2. - Uses `NetworkX `_. EXAMPLES: @@ -159,8 +154,6 @@ def CircularLadderGraph(n): can be described as two parallel cycle graphs connected at each corresponding node pair. - This constructor depends on NetworkX numeric labels. - PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm. By convention, the circular ladder graph is displayed as an inner and outer cycle pair, with @@ -571,8 +564,6 @@ def DiamondGraph(): A diamond graph is a square with one pair of diagonal nodes connected. - This constructor depends on NetworkX numeric labeling. - PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm. By convention, the diamond graph is drawn as a diamond, with the first node on top, second on @@ -832,6 +823,42 @@ def GridGraph(dim_list): sage: g.name() 'Grid Graph for [2, 4, 3]' + One dimensional grids (i.e., path) have simple vertex labels:: + + sage: g = graphs.GridGraph([5]) + sage: g.vertices() + [0, 1, 2, 3, 4] + + The graph is correct:: + + sage: dim = [randint(1,4) for i in range(4)] + sage: g = graphs.GridGraph(dim) + sage: import networkx + sage: h = Graph( networkx.grid_graph(list(dim)) ) + sage: g.is_isomorphic(h) + True + + Trivial cases:: + + sage: g = graphs.GridGraph([]); g; g.vertices() + Grid Graph for []: Graph on 0 vertices + [] + sage: g = graphs.GridGraph([1]); g; g.vertices() + Grid Graph for [1]: Graph on 1 vertex + [0] + sage: g = graphs.GridGraph([2]); g; g.vertices() + Grid Graph for [2]: Graph on 2 vertices + [0, 1] + sage: g = graphs.GridGraph([1,1]); g; g.vertices() + Grid Graph for [1, 1]: Graph on 1 vertex + [(0, 0)] + sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices() + Grid Graph for [1, 1, 1]: Graph on 1 vertex + [(0, 0, 0)] + sage: g = graphs.GridGraph([1,1,2]); g; g.vertices() + Grid Graph for [1, 1, 2]: Graph on 2 vertices + [(0, 0, 0), (0, 0, 1)] + All dimensions must be positive integers:: sage: g = graphs.GridGraph([2,-1,3]) @@ -839,13 +866,33 @@ def GridGraph(dim_list): ... ValueError: All dimensions must be positive integers ! """ - import networkx dim = [int(a) for a in dim_list] if any(a <= 0 for a in dim): raise ValueError("All dimensions must be positive integers !") - # We give a copy of dim to networkx because it modifies the list - G = networkx.grid_graph(list(dim)) - return graph.Graph(G, name="Grid Graph for " + str(dim)) + + g = Graph() + n_dim = len(dim) + if n_dim==1: + # Vertices are labeled from 0 to dim[0]-1 + g = PathGraph(dim[0]) + elif n_dim==2: + # We use the Grid2dGraph generator to also get the positions + g = Grid2dGraph(*dim) + elif n_dim>2: + # Vertices are tuples of dimension n_dim, and the graph contains at + # least vertex (0, 0, ..., 0) + g.add_vertex(tuple([0]*n_dim)) + import itertools + for u in itertools.product(*[range(d) for d in dim]): + for i in range(n_dim): + if u[i]+1`_. + + INPUT: + + - ``d,q`` (integers) -- note that only even values of `d` are accepted by + the function. + + - ``algorithm`` -- if set to 'gap' then the computation is carried via GAP + library interface, computing totally singular subspaces, which is faster for `q>3`. + Otherwise it is done directly. + + EXAMPLES: + + Computation of the spectrum of `Sp(6,2)`:: + + sage: g = graphs.SymplecticGraph(6,2) + doctest:...: DeprecationWarning: SymplecticGraph is deprecated. Please use sage.graphs.generators.classical_geometries.SymplecticPolarGraph instead. + See http://trac.sagemath.org/19136 for details. + sage: g.is_strongly_regular(parameters=True) + (63, 30, 13, 15) + sage: set(g.spectrum()) == {-5, 3, 30} + True + + The parameters of `Sp(4,q)` are the same as of `O(5,q)`, but they are + not isomorphic if `q` is odd:: + + sage: G = graphs.SymplecticPolarGraph(4,3) + sage: G.is_strongly_regular(parameters=True) + (40, 12, 2, 4) + sage: O=graphs.OrthogonalPolarGraph(5,3) + sage: O.is_strongly_regular(parameters=True) + (40, 12, 2, 4) + sage: O.is_isomorphic(G) + False + sage: graphs.SymplecticPolarGraph(6,4,algorithm="gap").is_strongly_regular(parameters=True) # not tested (long time) + (1365, 340, 83, 85) + + TESTS:: + + sage: graphs.SymplecticPolarGraph(4,4,algorithm="gap").is_strongly_regular(parameters=True) + (85, 20, 3, 5) + sage: graphs.SymplecticPolarGraph(4,4).is_strongly_regular(parameters=True) + (85, 20, 3, 5) + sage: graphs.SymplecticPolarGraph(4,4,algorithm="blah") + Traceback (most recent call last): + ... + ValueError: unknown algorithm! + """ + if d < 1 or d%2 != 0: + raise ValueError("d must be even and greater than 2") + + if algorithm == "gap": # faster for larger (q>3) fields + from sage.libs.gap.libgap import libgap + G = _polar_graph(d, q, libgap.SymplecticGroup(d, q)) + + elif algorithm == None: # faster for small (q<4) fields + from sage.modules.free_module import VectorSpace + from sage.schemes.projective.projective_space import ProjectiveSpace + from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix + + F = FiniteField(q,"x") + M = block_matrix(F, 2, 2, + [zero_matrix(F,d/2), + identity_matrix(F,d/2), + -identity_matrix(F,d/2), + zero_matrix(F,d/2)]) + + V = VectorSpace(F,d) + PV = list(ProjectiveSpace(d-1,F)) + G = Graph([[tuple(_) for _ in PV], lambda x,y:V(x)*(M*V(y)) == 0], loops = False) + + else: + raise ValueError("unknown algorithm!") + + G.name("Symplectic Polar Graph Sp("+str(d)+","+str(q)+")") + G.relabel() + return G + +from sage.misc.superseded import deprecated_function_alias +SymplecticGraph = deprecated_function_alias(19136, SymplecticPolarGraph) + +def AffineOrthogonalPolarGraph(d,q,sign="+"): + r""" + Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. + + Affine Polar graphs are built from a `d`-dimensional vector space over + `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic + according to the value of ``sign``. + + Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)` + is not. + + For more information on Affine Polar graphs, see `Affine Polar + Graphs page of Andries Brouwer's website + `_. + + INPUT: + + - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd + otherwise. + + - ``q`` (integer) -- a power of a prime number, as `F_q` must exist. + + - ``sign`` -- must be equal to ``"+"``, ``"-"``, or ``None`` to compute + (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default + ``sign="+"``. + + .. NOTE:: + + The graph `VO^\epsilon(d,q)` is the graph induced by the + non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph + ` `O^\epsilon(d+2,q)`. + + EXAMPLES: + + The :meth:`Brouwer-Haemers graph ` is isomorphic to + `VO^-(4,3)`:: + + sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-") + sage: g.is_isomorphic(graphs.BrouwerHaemersGraph()) + True + + Some examples from `Brouwer's table or strongly regular graphs + `_:: + + sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g + Affine Polar Graph VO^-(6,2): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 27, 10, 12) + sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g + Affine Polar Graph VO^+(6,2): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 35, 18, 20) + + When ``sign is None``:: + + sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g + Affine Polar Graph VO^-(5,2): Graph on 32 vertices + sage: g.is_strongly_regular(parameters=True) + False + sage: g.is_regular() + True + sage: g.is_vertex_transitive() + True + """ + if sign in ["+","-"]: + s = 1 if sign == "+" else -1 + if d%2 == 1: + raise ValueError("d must be even when sign!=None") + else: + if d%2 == 0: + raise ValueError("d must be odd when sign==None") + s = 0 + + from sage.interfaces.gap import gap + from sage.modules.free_module import VectorSpace + from sage.matrix.constructor import Matrix + from sage.libs.gap.libgap import libgap + from itertools import combinations + + M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(s,d,q))['matrix']) + F = libgap.GF(q).sage() + V = list(VectorSpace(F,d)) + + G = Graph() + G.add_vertices([tuple(_) for _ in V]) + for x,y in combinations(V,2): + if not (x-y)*M*(x-y): + G.add_edge(tuple(x),tuple(y)) + + G.name("Affine Polar Graph VO^"+str('+' if s == 1 else '-')+"("+str(d)+","+str(q)+")") + G.relabel() + return G + +def _orthogonal_polar_graph(m, q, sign="+", point_type=[0]): + r""" + A helper function to build ``OrthogonalPolarGraph`` and ``NO2,3,5`` graphs. + + See see the `page of + Andries Brouwer's website `_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power. + + - ``sign`` -- ``"+"`` or ``"-"`` if `m` is even, ``"+"`` (default) + otherwise. + + - ``point_type`` -- a list of elements from `F_q` + + EXAMPLES: + + Petersen graph:: +` + sage: from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph + sage: g=_orthogonal_polar_graph(3,5,point_type=[2,3]) + sage: g.is_strongly_regular(parameters=True) + (10, 3, 0, 1) + + A locally Petersen graph (a.k.a. Doro graph, a.k.a. Hall graph):: + + sage: g=_orthogonal_polar_graph(4,5,'-',point_type=[2,3]) + sage: g.is_distance_regular(parameters=True) + ([10, 6, 4, None], [None, 1, 2, 5]) + + Various big and slow to build graphs: + + `NO^+(7,3)`:: + + sage: g=_orthogonal_polar_graph(7,3,point_type=[1]) # not tested (long time) + sage: g.is_strongly_regular(parameters=True) # not tested (long time) + (378, 117, 36, 36) + + `NO^-(7,3)`:: + + sage: g=_orthogonal_polar_graph(7,3,point_type=[-1]) # not tested (long time) + sage: g.is_strongly_regular(parameters=True) # not tested (long time) + (351, 126, 45, 45) + + `NO^+(6,3)`:: + + sage: g=_orthogonal_polar_graph(6,3,point_type=[1]) + sage: g.is_strongly_regular(parameters=True) + (117, 36, 15, 9) + + `NO^-(6,3)`:: + + sage: g=_orthogonal_polar_graph(6,3,'-',point_type=[1]) + sage: g.is_strongly_regular(parameters=True) + (126, 45, 12, 18) + + `NO^{-,\perp}(5,5)`:: + + sage: g=_orthogonal_polar_graph(5,5,point_type=[2,3]) # long time + sage: g.is_strongly_regular(parameters=True) # long time + (300, 65, 10, 15) + + `NO^{+,\perp}(5,5)`:: + + sage: g=_orthogonal_polar_graph(5,5,point_type=[1,-1]) # not tested (long time) + sage: g.is_strongly_regular(parameters=True) # not tested (long time) + (325, 60, 15, 10) + + TESTS:: + + sage: g=_orthogonal_polar_graph(5,3,point_type=[-1]) + sage: g.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + sage: g=_orthogonal_polar_graph(5,3,point_type=[1]) + sage: g.is_strongly_regular(parameters=True) + (36, 15, 6, 6) + + """ + from sage.schemes.projective.projective_space import ProjectiveSpace + from sage.modules.free_module_element import free_module_element as vector + from sage.matrix.constructor import Matrix + from sage.libs.gap.libgap import libgap + from itertools import combinations + + if m % 2 == 0: + if sign != "+" and sign != "-": + raise ValueError("sign must be equal to either '-' or '+' when " + "m is even") + else: + if sign != "" and sign != "+": + raise ValueError("sign must be equal to either '' or '+' when " + "m is odd") + sign = "" + + e = {'+': 1, + '-': -1, + '' : 0}[sign] + + M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(e,m,q))['matrix']) + Fq = libgap.GF(q).sage() + PG = map(vector, ProjectiveSpace(m - 1, Fq)) + map(lambda x: x.set_immutable(), PG) + + def F(x): + return x*M*x + + if q % 2 == 0: + def P(x,y): + return F(x-y) + else: + def P(x,y): + return x*M*y+y*M*x + + V = [x for x in PG if F(x) in point_type] + + G = Graph([V,lambda x,y:P(x,y)==0],loops=False) + + G.relabel() + return G + +def OrthogonalPolarGraph(m, q, sign="+"): + r""" + Returns the Orthogonal Polar Graph `O^{\epsilon}(m,q)`. + + For more information on Orthogonal Polar graphs, see see the `page of + Andries Brouwer's website `_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power. + + - ``sign`` -- ``"+"`` or ``"-"`` if `m` is even, ``"+"`` (default) + otherwise. + + EXAMPLES:: + + sage: G = graphs.OrthogonalPolarGraph(6,3,"+"); G + Orthogonal Polar Graph O^+(6, 3): Graph on 130 vertices + sage: G.is_strongly_regular(parameters=True) + (130, 48, 20, 16) + sage: G = graphs.OrthogonalPolarGraph(6,3,"-"); G + Orthogonal Polar Graph O^-(6, 3): Graph on 112 vertices + sage: G.is_strongly_regular(parameters=True) + (112, 30, 2, 10) + sage: G = graphs.OrthogonalPolarGraph(5,3); G + Orthogonal Polar Graph O(5, 3): Graph on 40 vertices + sage: G.is_strongly_regular(parameters=True) + (40, 12, 2, 4) + sage: G = graphs.OrthogonalPolarGraph(8,2,"+"); G + Orthogonal Polar Graph O^+(8, 2): Graph on 135 vertices + sage: G.is_strongly_regular(parameters=True) + (135, 70, 37, 35) + sage: G = graphs.OrthogonalPolarGraph(8,2,"-"); G + Orthogonal Polar Graph O^-(8, 2): Graph on 119 vertices + sage: G.is_strongly_regular(parameters=True) + (119, 54, 21, 27) + + TESTS:: + + sage: G = graphs.OrthogonalPolarGraph(4,3,"") + Traceback (most recent call last): + ... + ValueError: sign must be equal to either '-' or '+' when m is even + sage: G = graphs.OrthogonalPolarGraph(5,3,"-") + Traceback (most recent call last): + ... + ValueError: sign must be equal to either '' or '+' when m is odd + """ + from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph + G = _orthogonal_polar_graph(m, q, sign=sign) + if m % 2 != 0: + sign = "" + G.name("Orthogonal Polar Graph O" + ("^" + sign if sign else "") + str((m, q))) + return G + +def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): + r""" + Returns the Graph `NO^{\epsilon,\perp}_{m}(q)` + + Let the vectorspace of dimension `m` over `F_q` be + endowed with a nondegenerate quadratic form `F`, of type ``sign`` for `m` even. + + * `m` even: assume further that `q=2` or `3`. Returns the graph of the + points (in the underlying projective space) `x` satisfying `F(x)=1`, with adjacency + given by orthogonality w.r.t. `F`. Parameter ``perp`` is ignored. + + * `m` odd: if ``perp`` is not ``None``, then we assume that `q=5` and + return the graph of the points `x` satisfying `F(x)=\pm 1` if ``sign="+"``, + respectively `F(x) \in \{2,3\}` if ``sign="-"``, with adjacency + given by orthogonality w.r.t. `F` (cf. Sect 7.D of [BvL84]_). + Otherwise return the graph + of nongenerate hyperplanes of type ``sign``, adjacent whenever the intersection + is degenerate (cf. Sect. 7.C of [BvL84]_). + Note that for `q=2` one will get a complete graph. + + For more information, see Sect. 9.9 of [BH12]_ and [BvL84]_. Note that the `page of + Andries Brouwer's website `_ + uses different notation. + + INPUT: + + - ``m`` - integer, half the dimension of the underlying vectorspace + + - ``q`` - a power of a prime number, the size of the underlying field + + - ``sign`` -- ``"+"`` (default) or ``"-"``. + + EXAMPLES: + + `NO^-(4,2)` is isomorphic to Petersen graph:: + + sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2,'-'); g + NO^-(4, 2): Graph on 10 vertices + sage: g.is_strongly_regular(parameters=True) + (10, 3, 0, 1) + + `NO^-(6,2)` and `NO^+(6,2)`:: + + sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'-') + sage: g.is_strongly_regular(parameters=True) + (36, 15, 6, 6) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'+'); g + NO^+(6, 2): Graph on 28 vertices + sage: g.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + `NO^+(8,2)`:: + + sage: g=graphs.NonisotropicOrthogonalPolarGraph(8,2,'+') + sage: g.is_strongly_regular(parameters=True) + (120, 63, 30, 36) + + Wilbrink's graphs for `q=5`:: + + sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,perp=1).is_strongly_regular(parameters=True) # long time + (325, 60, 15, 10) + sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,'-',perp=1).is_strongly_regular(parameters=True) # long time + (300, 65, 10, 15) + + Wilbrink's graphs:: + + sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'+') + sage: g.is_strongly_regular(parameters=True) + (136, 75, 42, 40) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'-') + sage: g.is_strongly_regular(parameters=True) + (120, 51, 18, 24) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(7,4,'+'); g # not tested (long time) + NO^+(7, 4): Graph on 2080 vertices + sage: g.is_strongly_regular(parameters=True) # not tested (long time) + (2080, 1071, 558, 544) + + TESTS:: + + sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2); g + NO^+(4, 2): Graph on 6 vertices + sage: graphs.NonisotropicOrthogonalPolarGraph(4,3,'-').is_strongly_regular(parameters=True) + (15, 6, 1, 3) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(3,5,'-',perp=1); g + NO^-,perp(3, 5): Graph on 10 vertices + sage: g.is_strongly_regular(parameters=True) + (10, 3, 0, 1) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'+') # long time + sage: g.is_strongly_regular(parameters=True) # long time + (117, 36, 15, 9) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'-'); g # long time + NO^-(6, 3): Graph on 126 vertices + sage: g.is_strongly_regular(parameters=True) # long time + (126, 45, 12, 18) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'-') # long time + sage: g.is_strongly_regular(parameters=True) # long time + (300, 104, 28, 40) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'+') # long time + sage: g.is_strongly_regular(parameters=True) # long time + (325, 144, 68, 60) + sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,4,'+') + Traceback (most recent call last): + ... + ValueError: for m even q must be 2 or 3 + + """ + from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph + p, k = is_prime_power(q,get_data=True) + if k==0: + raise ValueError('q must be a prime power') + dec = '' + if m % 2 == 0: + if q in [2,3]: + G = _orthogonal_polar_graph(m, q, sign=sign, point_type=[1]) + else: + raise ValueError("for m even q must be 2 or 3") + elif not perp is None: + if q == 5: + G = _orthogonal_polar_graph(m, q, point_type=\ + [-1,1] if sign=='+' else [2,3] if sign=='-' else []) + dec = ",perp" + else: + raise ValueError("for perp not None q must be 5") + else: + if not sign in ['+','-']: + raise ValueError("sign must be '+' or '-'") + from sage.libs.gap.libgap import libgap + g0 = libgap.GeneralOrthogonalGroup(m,q) + g = libgap.Group(libgap.List(g0.GeneratorsOfGroup(),libgap.TransposedMat)) + F=libgap.GF(q) # F_q + W=libgap.FullRowSpace(F, m) # F_q^m + e = 1 if sign=='+' else -1 + n = (m-1)/2 + # we build (q^n(q^n+e)/2, (q^n-e)(q^(n-1)+e), 2(q^(2n-2)-1)+eq^(n-1)(q-1), + # 2q^(n-1)(q^(n-1)+e))-srg + # **use** v and k to select appropriate orbit and orbital + nvert = (q**n)*(q**n+e)/2 # v + deg = (q**n-e)*(q**(n-1)+e) # k + S=map(lambda x: libgap.Elements(libgap.Basis(x))[0], \ + libgap.Elements(libgap.Subspaces(W,1))) + V = filter(lambda x: len(x)==nvert, libgap.Orbits(g,S,libgap.OnLines)) + assert len(V)==1 + V = V[0] + gp = libgap.Action(g,V,libgap.OnLines) # make a permutation group + h = libgap.Stabilizer(gp,1) + Vh = filter(lambda x: len(x)==deg, libgap.Orbits(h,libgap.Orbit(gp,1))) + assert len(Vh)==1 + Vh = Vh[0][0] + L = libgap.Orbit(gp, [1, Vh], libgap.OnSets) + G = Graph() + G.add_edges(L) + G.name("NO^" + sign + dec + str((m, q))) + return G + +def _polar_graph(m, q, g, intersection_size=None): + r""" + The helper function to build graphs `(D)U(m,q)` and `(D)Sp(m,q)` + + Building a graph on an orbit of a group `g` of `m\times m` matrices over `GF(q)` on + the points (or subspaces of dimension ``m//2``) isotropic w.r.t. the form `F` + left invariant by the group `g`. + + The only constraint is that the first ``m//2`` elements of the standard + basis must generate a totally isotropic w.r.t. `F` subspace; this is the case with + these groups coming from GAP; namely, `F` has the anti-diagonal all-1 matrix. + + INPUT: + + - ``m`` -- the dimension of the underlying vector space + + - ``q`` -- the size of the field + + - ``g`` -- the group acting + + - ``intersection_size`` -- if ``None``, build the graph on the isotropic points, with + adjacency being orthogonality w.r.t. `F`. Otherwise, build the graph on the maximal + totally isotropic subspaces, with adjacency specified by ``intersection_size`` being + as given. + + TESTS:: + + sage: from sage.graphs.generators.classical_geometries import _polar_graph + sage: _polar_graph(4, 4, libgap.GeneralUnitaryGroup(4, 2)) + Graph on 45 vertices + sage: _polar_graph(4, 4, libgap.GeneralUnitaryGroup(4, 2), intersection_size=1) + Graph on 27 vertices + """ + from sage.libs.gap.libgap import libgap + from itertools import combinations + W=libgap.FullRowSpace(libgap.GF(q), m) # F_q^m + B=libgap.Elements(libgap.Basis(W)) # the standard basis of W + V = libgap.Orbit(g,B[0],libgap.OnLines) # orbit on isotropic points + gp = libgap.Action(g,V,libgap.OnLines) # make a permutation group + s = libgap.Subspace(W,[B[i] for i in range(m//2)]) # a totally isotropic subspace + # and the points there + sp = [libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1))] + h = libgap.Set(map(lambda x: libgap.Position(V, x), sp)) # indices of the points in s + L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on these subspaces + if intersection_size == None: + G = Graph() + for x in L: # every pair of points in the subspace is adjacent to each other in G + G.add_edges(combinations(x, 2)) + return G + else: + return Graph([L, lambda i,j: libgap.Size(libgap.Intersection(i,j))==intersection_size], + loops=False) + +def UnitaryPolarGraph(m, q, algorithm="gap"): + r""" + Returns the Unitary Polar Graph `U(m,q)`. + + For more information on Unitary Polar graphs, see the `page of + Andries Brouwer's website `_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power. + + - ``algorithm`` -- if set to 'gap' then the computation is carried via GAP + library interface, computing totally singular subspaces, which is faster for + large examples (especially with `q>2`). Otherwise it is done directly. + + EXAMPLES:: + + sage: G = graphs.UnitaryPolarGraph(4,2); G + Unitary Polar Graph U(4, 2); GQ(4, 2): Graph on 45 vertices + sage: G.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + sage: graphs.UnitaryPolarGraph(5,2).is_strongly_regular(parameters=True) + (165, 36, 3, 9) + sage: graphs.UnitaryPolarGraph(6,2) # not tested (long time) + Unitary Polar Graph U(6, 2): Graph on 693 vertices + + TESTS:: + + sage: graphs.UnitaryPolarGraph(4,3, algorithm="gap").is_strongly_regular(parameters=True) + (280, 36, 8, 4) + sage: graphs.UnitaryPolarGraph(4,3).is_strongly_regular(parameters=True) + (280, 36, 8, 4) + sage: graphs.UnitaryPolarGraph(4,3, algorithm="foo") + Traceback (most recent call last): + ... + ValueError: unknown algorithm! + """ + if algorithm == "gap": + from sage.libs.gap.libgap import libgap + G = _polar_graph(m, q**2, libgap.GeneralUnitaryGroup(m, q)) + + elif algorithm == None: # slow on large examples + from sage.schemes.projective.projective_space import ProjectiveSpace + from sage.modules.free_module_element import free_module_element as vector + Fq = FiniteField(q**2, 'a') + PG = map(vector, ProjectiveSpace(m - 1, Fq)) + map(lambda x: x.set_immutable(), PG) + def P(x,y): + return sum(map(lambda j: x[j]*y[m-1-j]**q, xrange(m)))==0 + + V = filter(lambda x: P(x,x), PG) + G = Graph([V,lambda x,y: # bottleneck is here, of course: + P(x,y)], loops=False) + else: + raise ValueError("unknown algorithm!") + + G.relabel() + G.name("Unitary Polar Graph U" + str((m, q))) + if m==4: + G.name(G.name()+'; GQ'+str((q**2,q))) + if m==5: + G.name(G.name()+'; GQ'+str((q**2,q**3))) + return G + +def NonisotropicUnitaryPolarGraph(m, q): + r""" + Returns the Graph `NU(m,q)`. + + Returns the graph on nonisotropic, with respect to a nondegenerate + Hermitean form, points of the `(m-1)`-dimensional projective space over `F_q`, + with points adjacent whenever they lie on a tangent (to the set of isotropic points) + line. + For more information, see Sect. 9.9 of [BH12]_ and series C14 in [Hu75]_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power. + + EXAMPLES:: + + sage: g=graphs.NonisotropicUnitaryPolarGraph(5,2); g + NU(5, 2): Graph on 176 vertices + sage: g.is_strongly_regular(parameters=True) + (176, 135, 102, 108) + + TESTS:: + + sage: graphs.NonisotropicUnitaryPolarGraph(4,2).is_strongly_regular(parameters=True) + (40, 27, 18, 18) + sage: graphs.NonisotropicUnitaryPolarGraph(4,3).is_strongly_regular(parameters=True) # long time + (540, 224, 88, 96) + sage: graphs.NonisotropicUnitaryPolarGraph(6,6) + Traceback (most recent call last): + ... + ValueError: q must be a prime power + + REFERENCE: + + .. [Hu75] X. L. Hubaut. + Strongly regular graphs. + Disc. Math. 13(1975), pp 357--381. + http://dx.doi.org/10.1016/0012-365X(75)90057-6 + """ + p, k = is_prime_power(q,get_data=True) + if k==0: + raise ValueError('q must be a prime power') + from sage.libs.gap.libgap import libgap + from itertools import combinations + F=libgap.GF(q**2) # F_{q^2} + W=libgap.FullRowSpace(F, m) # F_{q^2}^m + B=libgap.Elements(libgap.Basis(W)) # the standard basis of W + if m % 2 != 0: + point = B[(m-1)/2] + else: + if p==2: + point = B[m/2] + F.PrimitiveRoot()*B[(m-2)/2] + else: + point = B[(m-2)/2] + B[m/2] + g = libgap.GeneralUnitaryGroup(m,q) + V = libgap.Orbit(g,point,libgap.OnLines) # orbit on nonisotropic points + gp = libgap.Action(g,V,libgap.OnLines) # make a permutation group + + s = libgap.Subspace(W,[point, point+B[0]]) # a tangent line on point + + # and the points there + sp = [libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1))] + h = libgap.Set(map(lambda x: libgap.Position(V, x), libgap.Intersection(V,sp))) # indices + L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on the tangent lines + G = Graph() + for x in L: # every pair of points in the subspace is adjacent to each other in G + G.add_edges(combinations(x, 2)) + G.relabel() + G.name("NU" + str((m, q))) + return G + +def UnitaryDualPolarGraph(m, q): + r""" + Returns the Dual Unitary Polar Graph `U(m,q)`. + + For more information on Unitary Dual Polar graphs, see [BCN89]_ and + Sect. 2.3.1 of [Co81]_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power. + + EXAMPLES: + + The point graph of a generalized quadrangle (see [GQwiki]_, [PT09]_) of order (8,4):: + + sage: G = graphs.UnitaryDualPolarGraph(5,2); G # long time + Unitary Dual Polar Graph DU(5, 2); GQ(8, 4): Graph on 297 vertices + sage: G.is_strongly_regular(parameters=True) # long time + (297, 40, 7, 5) + + Another way to get the generalized quadrangle of order (2,4):: + + sage: G = graphs.UnitaryDualPolarGraph(4,2); G + Unitary Dual Polar Graph DU(4, 2); GQ(2, 4): Graph on 27 vertices + sage: G.is_isomorphic(graphs.OrthogonalPolarGraph(6,2,'-')) + True + + A bigger graph:: + + sage: G = graphs.UnitaryDualPolarGraph(6,2); G # not tested (long time) + Unitary Dual Polar Graph DU(6, 2): Graph on 891 vertices + sage: G.is_distance_regular(parameters=True) # not tested (long time) + ([42, 40, 32, None], [None, 1, 5, 21]) + + TESTS:: + + sage: graphs.UnitaryDualPolarGraph(6,6) + Traceback (most recent call last): + ... + ValueError: libGAP: Error, must be a prime or a finite field + """ + from sage.libs.gap.libgap import libgap + G = _polar_graph(m, q**2, libgap.GeneralUnitaryGroup(m, q), + intersection_size=(q**(2*(m//2-1))-1)/(q**2-1)) + G.relabel() + G.name("Unitary Dual Polar Graph DU" + str((m, q))) + if m==4: + G.name(G.name()+'; GQ'+str((q,q**2))) + if m==5: + G.name(G.name()+'; GQ'+str((q**3,q**2))) + return G + + +def SymplecticDualPolarGraph(m, q): + r""" + Returns the Symplectic Dual Polar Graph `DSp(m,q)`. + + For more information on Symplectic Dual Polar graphs, see [BCN89]_ and + Sect. 2.3.1 of [Co81]_. + + INPUT: + + - ``m,q`` (integers) -- `q` must be a prime power, and `m` must be even. + + EXAMPLES:: + + sage: G = graphs.SymplecticDualPolarGraph(6,3); G # not tested (long time) + Symplectic Dual Polar Graph DSp(6, 3): Graph on 1120 vertices + sage: G.is_distance_regular(parameters=True) # not tested (long time) + ([39, 36, 27, None], [None, 1, 4, 13]) + + TESTS:: + + sage: G = graphs.SymplecticDualPolarGraph(6,2); G + Symplectic Dual Polar Graph DSp(6, 2): Graph on 135 vertices + sage: G.is_distance_regular(parameters=True) + ([14, 12, 8, None], [None, 1, 3, 7]) + sage: graphs.SymplecticDualPolarGraph(6,6) + Traceback (most recent call last): + ... + ValueError: libGAP: Error, must be a prime or a finite field + + REFERENCE: + + .. [Co81] A. M. Cohen, + `A synopsis of known distance-regular graphs with large diameters + `_, + Stichting Mathematisch Centrum, 1981. + """ + from sage.libs.gap.libgap import libgap + G = _polar_graph(m, q, libgap.SymplecticGroup(m, q), + intersection_size=(q**(m/2-1)-1)/(q-1)) + + G.relabel() + G.name("Symplectic Dual Polar Graph DSp" + str((m, q))) + if m==4: + G.name(G.name()+'; GQ'+str((q,q))) + return G + +def TaylorTwographDescendantSRG(q, clique_partition=None): + r""" + constructing the descendant graph of the Taylor's two-graph for `U_3(q)`, `q` odd + + This is a strongly regular graph with parameters + `(v,k,\lambda,\mu)=(q^3, (q^2+1)(q-1)/2, (q-1)^3/4-1, (q^2+1)(q-1)/4)` + obtained as a two-graph descendant of the + :func:`Taylor's two-graph ` `T`. + This graph admits a partition into cliques of size `q`, which are useful in + :func:`TaylorTwographSRG `, + a strongly regular graph on `q^3+1` vertices in the + Seidel switching class of `T`, for which we need `(q^2+1)/2` cliques. + The cliques are the `q^2` lines on `v_0` of the projective plane containing the unital + for `U_3(q)`, and intersecting the unital (i.e. the vertices of the graph and the point + we remove) in `q+1` points. This is all taken from §7E of [BvL84]_. + + INPUT: + + - ``q`` -- a power of an odd prime number + + - ``clique_partition`` -- if ``True``, return `q^2-1` cliques of size `q` + with empty pairwise intersection. (Removing all of them leaves a clique, too), + and the point removed from the unital. + + EXAMPLES:: + + sage: g=graphs.TaylorTwographDescendantSRG(3); g + Taylor two-graph descendant SRG: Graph on 27 vertices + sage: g.is_strongly_regular(parameters=True) + (27, 10, 1, 5) + sage: from sage.combinat.designs.twographs import taylor_twograph + sage: T = taylor_twograph(3) # long time + sage: g.is_isomorphic(T.descendant(T.ground_set()[1])) # long time + True + sage: g=graphs.TaylorTwographDescendantSRG(5) # not tested (long time) + sage: g.is_strongly_regular(parameters=True) # not tested (long time) + (125, 52, 15, 26) + + TESTS:: + + sage: g,l,_=graphs.TaylorTwographDescendantSRG(3,clique_partition=True) + sage: all(map(lambda x: g.is_clique(x), l)) + True + sage: graphs.TaylorTwographDescendantSRG(4) + Traceback (most recent call last): + ... + ValueError: q must be an odd prime power + sage: graphs.TaylorTwographDescendantSRG(6) + Traceback (most recent call last): + ... + ValueError: q must be an odd prime power + """ + p, k = is_prime_power(q,get_data=True) + if k==0 or p==2: + raise ValueError('q must be an odd prime power') + from sage.schemes.projective.projective_space import ProjectiveSpace + from sage.modules.free_module_element import free_module_element as vector + from sage.rings.finite_rings.integer_mod import mod + from __builtin__ import sum + Fq = FiniteField(q**2, 'a') + PG = map(tuple,ProjectiveSpace(2, Fq)) + def S(x,y): + return sum(map(lambda j: x[j]*y[2-j]**q, xrange(3))) + + V = filter(lambda x: S(x,x)==0, PG) # the points of the unital + v0 = V[0] + V.remove(v0) + if mod(q,4)==1: + G = Graph([V,lambda y,z: not (S(v0,y)*S(y,z)*S(z,v0)).is_square()], loops=False) + else: + G = Graph([V,lambda y,z: (S(v0,y)*S(y,z)*S(z,v0)).is_square()], loops=False) + G.name("Taylor two-graph descendant SRG") + if clique_partition: + lines = map(lambda x: filter(lambda t: t[0]+x*t[1]==0, V), + filter(lambda z: z != 0, Fq)) + return (G, lines, v0) + else: + return G + +def TaylorTwographSRG(q): + r""" + constructing a strongly regular graph from the Taylor's two-graph for `U_3(q)`, `q` odd + + This is a strongly regular graph with parameters + `(v,k,\lambda,\mu)=(q^3+1, q(q^2+1)/2, (q^2+3)(q-1)/4, (q^2+1)(q+1)/4)` + in the Seidel switching class of + :func:`Taylor two-graph `. + Details are in §7E of [BvL84]_. + + INPUT: + + - ``q`` -- a power of an odd prime number + + .. SEEALSO:: + + * :func:`TaylorTwographDescendantSRG ` + + EXAMPLES:: + + sage: t=graphs.TaylorTwographSRG(3); t + Taylor two-graph SRG: Graph on 28 vertices + sage: t.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + """ + G, l, v0 = TaylorTwographDescendantSRG(q, clique_partition=True) + G.add_vertex(v0) + G.seidel_switching(sum(l[:(q**2+1)/2],[])) + G.name("Taylor two-graph SRG") + return G + +def AhrensSzekeresGeneralizedQuadrangleGraph(q, dual=False): + r""" + Return the collinearity graph of the generalized quadrangle `AS(q)`, or of its dual + + Let `q` be an odd prime power. `AS(q)` is a generalized quadrangle [GQwiki]_ of + order `(q-1,q+1)`, see 3.1.5 in [PT09]_. Its points are elements + of `F_q^3`, and lines are sets of size `q` of the form + + * `\{ (\sigma, a, b) \mid \sigma\in F_q \}` + * `\{ (a, \sigma, b) \mid \sigma\in F_q \}` + * `\{ (c \sigma^2 - b \sigma + a, -2 c \sigma + b, \sigma) \mid \sigma\in F_q \}`, + + where `a`, `b`, `c` are arbitrary elements of `F_q`. + + INPUT: + + - ``q`` -- a power of an odd prime number + + - ``dual`` -- if ``False`` (default), return the collinearity graph of `AS(q)`. + Otherwise return the collinearity graph of the dual `AS(q)`. + + EXAMPLES:: + + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5); g + AS(5); GQ(4, 6): Graph on 125 vertices + sage: g.is_strongly_regular(parameters=True) + (125, 28, 3, 7) + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5,dual=True); g + AS(5)*; GQ(6, 4): Graph on 175 vertices + sage: g.is_strongly_regular(parameters=True) + (175, 30, 5, 5) + + REFERENCE: + + .. [GQwiki] `Generalized quadrangle + `__ + + .. [PT09] S. Payne, J. A. Thas. + Finite generalized quadrangles. + European Mathematical Society, + 2nd edition, 2009. + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + p, k = is_prime_power(q,get_data=True) + if k==0 or p==2: + raise ValueError('q must be an odd prime power') + F = FiniteField(q, 'a') + L = [] + for a in F: + for b in F: + L.append(tuple(map(lambda s: (s, a, b), F))) + L.append(tuple(map(lambda s: (a, s, b), F))) + for c in F: + L.append(tuple(map(lambda s: (c*s**2 - b*s + a, -2*c*s + b, s), F))) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('AS('+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('AS('+str(q)+'); GQ'+str((q-1,q+1))) + return G + +def T2starGeneralizedQuadrangleGraph(q, dual=False, hyperoval=None, field=None, check_hyperoval=True): + r""" + Return the collinearity graph of the generalized quadrangle `T_2^*(q)`, or of its dual + + Let `q=2^k` and `\Theta=PG(3,q)`. `T_2^*(q)` is a generalized quadrangle [GQwiki]_ + of order `(q-1,q+1)`, see 3.1.3 in [PT09]_. Fix a plane `\Pi \subset \Theta` and a + `hyperoval `__ + `O \subset \Pi`. The points of `T_2^*(q):=T_2^*(O)` are the points of `\Theta` + outside `\Pi`, and the lines are the lines of `\Theta` outside `\Pi` + that meet `\Pi` in a point of `O`. + + INPUT: + + - ``q`` -- a power of two + + - ``dual`` -- if ``False`` (default), return the graph of `T_2^*(O)`. + Otherwise return the graph of the dual `T_2^*(O)`. + + - ``hyperoval`` -- a hyperoval (i.e. a complete 2-arc; a set of points in the plane + meeting every line in 0 or 2 points) in the plane of points with 0th coordinate + 0 in `PG(3,q)` over the field ``field``. Each point of ``hyperoval`` must be a length 4 + vector over ``field`` with 1st non-0 coordinate equal to 1. By default, ``hyperoval`` and + ``field`` are not specified, and constructed on the fly. In particular, ``hyperoval`` + we build is the classical one, i.e. a conic with the point of intersection of its + tangent lines. + + - ``field`` -- an instance of a finite field of order `q`, must be provided + if ``hyperoval`` is provided. + + - ``check_hyperoval`` -- (default: ``True``) if ``True``, + check ``hyperoval`` for correctness. + + + EXAMPLES: + + using the built-in construction:: + + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4,dual=True); g + T2*(O,4)*; GQ(5, 3): Graph on 96 vertices + sage: g.is_strongly_regular(parameters=True) + (96, 20, 4, 4) + + supplying your own hyperoval:: + + sage: F=GF(4,'b') + sage: O=[vector(F,(0,0,0,1)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: F=GF(4,'b') # repeating a point... + sage: O=[vector(F,(0,1,0,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval size + sage: O=[vector(F,(0,1,1,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from sage.combinat.designs.block_design import ProjectiveGeometryDesign as PG + from sage.modules.free_module_element import free_module_element as vector + + p, k = is_prime_power(q,get_data=True) + if k==0 or p!=2: + raise ValueError('q must be a power of 2') + if field is None: + F = FiniteField(q, 'a') + else: + F = field + + Theta = PG(3, 1, F, point_coordinates=1) + Pi = set(filter(lambda x: x[0]==F.zero(), Theta.ground_set())) + if hyperoval is None: + O = filter(lambda x: x[1]+x[2]*x[3]==0 or (x[1]==1 and x[2]==0 and x[3]==0), Pi) + O = set(O) + else: + map(lambda x: x.set_immutable(), hyperoval) + O = set(hyperoval) + if check_hyperoval: + if len(O) != q+2: + raise RuntimeError("incorrect hyperoval size") + for L in Theta.blocks(): + if set(L).issubset(Pi): + if not len(O.intersection(L)) in [0,2]: + raise RuntimeError("incorrect hyperoval") + L = map(lambda z: filter(lambda y: not y in O, z), + filter(lambda x: len(O.intersection(x)) == 1, Theta.blocks())) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('T2*(O,'+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('T2*(O,'+str(q)+'); GQ'+str((q-1,q+1))) + return G diff --git a/src/sage/graphs/generators/degree_sequence.py b/src/sage/graphs/generators/degree_sequence.py index 02117185b20..21fcafec739 100644 --- a/src/sage/graphs/generators/degree_sequence.py +++ b/src/sage/graphs/generators/degree_sequence.py @@ -115,7 +115,7 @@ def DegreeSequenceBipartite(s1 ,s2 ): Trac ticket #12155:: sage: graphs.DegreeSequenceBipartite([2,2,2,2,2],[5,5]).complement() - complement(): Graph on 7 vertices + Graph on 7 vertices """ from sage.combinat.integer_vector import gale_ryser_theorem diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 2494fcc8b99..c48916e73b9 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -3,11 +3,6 @@ Families of graphs The methods defined here appear in :mod:`sage.graphs.graph_generators`. - -AUTHORS: - -- David Coudert (2012) Ringed Trees - """ ########################################################################### @@ -51,7 +46,7 @@ def JohnsonGraph(n, k): sage: g.is_vertex_transitive() True - The complement of the Johnson graph `J(n,2)` is isomorphic to the Knesser + The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser Graph `K(n,2)`. In paritcular the complement of `J(5,2)` is isomorphic to the Petersen graph. :: @@ -192,9 +187,9 @@ def BalancedTree(r, h): TESTS: - Normally we would only consider balanced trees whose root node - has degree `r \geq 2`, but the construction degenerates - gracefully:: + Normally we would only consider balanced trees whose root node + has degree `r \geq 2`, but the construction degenerates + gracefully:: sage: graphs.BalancedTree(1, 10) Balanced tree: Graph on 2 vertices @@ -462,10 +457,13 @@ def chang_graphs(): information about the Chang graphs, see :wikipedia:`Chang_graphs` or http://www.win.tue.nl/~aeb/graphs/Chang.html. - EXAMPLES:: + EXAMPLES: check that we get 4 non-isomorphic s.r.g.'s with the + same parameters:: sage: chang_graphs = graphs.chang_graphs() - sage: four_srg = chang_graphs + [graphs.CompleteGraph(8).line_graph()] + sage: K8 = graphs.CompleteGraph(8) + sage: T8 = K8.line_graph() + sage: four_srg = chang_graphs + [T8] sage: for g in four_srg: ....: print g.is_strongly_regular(parameters=True) (28, 12, 6, 4) @@ -475,6 +473,18 @@ def chang_graphs(): sage: from itertools import combinations sage: for g1,g2 in combinations(four_srg,2): ....: assert not g1.is_isomorphic(g2) + + Construct the Chang graphs by Seidel switching:: + + sage: c3c5=graphs.CycleGraph(3).disjoint_union(graphs.CycleGraph(5)) + sage: c8=graphs.CycleGraph(8) + sage: s=[K8.subgraph_search(c8).edges(), + ....: [(0,1,None),(2,3,None),(4,5,None),(6,7,None)], + ....: K8.subgraph_search(c3c5).edges()] + sage: map(lambda x,G: T8.seidel_switching(x, inplace=False).is_isomorphic(G), + ....: s, chang_graphs) + [True, True, True] + """ g1 = Graph("[}~~EebhkrRb_~SoLOIiAZ?LBBxDb?bQcggjHKEwoZFAaiZ?Yf[?dxb@@tdWGkwn", loops=False, multiedges=False) @@ -679,6 +689,69 @@ def CubeGraph(n): return r +def GoethalsSeidelGraph(k,r): + r""" + Returns the graph `\text{Goethals-Seidel}(k,r)`. + + The graph `\text{Goethals-Seidel}(k,r)` comes from a construction presented + in Theorem 2.4 of [GS70]_. It relies on a :func:`(v,k)-BIBD + ` with `r` + blocks and a + :func:`~sage.combinat.matrices.hadamard_matrix.hadamard_matrix>` of order + `r+1`. The result is a + :func:`sage.graphs.strongly_regular_db.strongly_regular_graph` on `v(r+1)` + vertices with degree `k=(n+r-1)/2`. + + It appears under this name in Andries Brouwer's `database of strongly + regular graphs `__. + + INPUT: + + - ``k,r`` -- integers + + EXAMPLE:: + + sage: graphs.GoethalsSeidelGraph(3,3) + Graph on 28 vertices + sage: graphs.GoethalsSeidelGraph(3,3).is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + from sage.matrix.constructor import Matrix + from sage.matrix.constructor import block_matrix + from sage.matrix.constructor import identity_matrix + + v = (k-1)*r+1 + n = v*(r+1) + + # N is the (v times b) incidence matrix of a bibd + N = balanced_incomplete_block_design(v,k).incidence_matrix() + + # L is a (r+1 times r) matrix, where r is the row sum of N + L = hadamard_matrix(r+1).submatrix(0,1) + L = [Matrix(C).transpose() for C in L.columns()] + zero = Matrix(r+1,1,[0]*(r+1)) + + # For every row of N, we replace the 0s with a column of zeros, and we + # replace the ith 1 with the ith column of L. The result is P. + P = [] + for row in N: + Ltmp = L[:] + P.append([Ltmp.pop(0) if i else zero + for i in row]) + + P = block_matrix(P) + + # The final graph + PP = P*P.transpose() + for i in range(n): + PP[i,i] = 0 + + G = Graph(PP, format="seidel_adjacency_matrix") + return G + def DorogovtsevGoltsevMendesGraph(n): """ Construct the n-th generation of the Dorogovtsev-Goltsev-Mendes @@ -1515,7 +1588,8 @@ def PaleyGraph(q): """ from sage.rings.finite_rings.integer_mod import mod from sage.rings.finite_rings.constructor import FiniteField - assert q.is_prime_power(), "Parameter q must be a prime power" + from sage.rings.arith import is_prime_power + assert is_prime_power(q), "Parameter q must be a prime power" assert mod(q,4)==1, "Parameter q must be congruent to 1 mod 4" g = Graph([FiniteField(q,'a'), lambda i,j: (i-j).is_square()], loops=False, name = "Paley graph with parameter %d"%q) @@ -2259,220 +2333,3 @@ def RingedTree(k, vertex_labels = True): g.relabel(vertices) return g - -def SymplecticGraph(d,q): - r""" - Returns the Symplectic graph `Sp(d,q)` - - The Symplectic Graph `Sp(d,q)` is built from a projective space of dimension - `d-1` over a field `F_q`, and a symplectic form `f`. Two vertices `u,v` are - made adjacent if `f(u,v)=0`. - - See the `page on symplectic graphs on Andries Brouwer's website - `_. - - INPUT: - - - ``d,q`` (integers) -- note that only even values of `d` are accepted by - the function. - - EXAMPLES:: - - sage: g = graphs.SymplecticGraph(6,2) - sage: g.is_strongly_regular(parameters=True) - (63, 30, 13, 15) - sage: set(g.spectrum()) == {-5, 3, 30} - True - """ - from sage.rings.finite_rings.constructor import FiniteField - from sage.modules.free_module import VectorSpace - from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix - - if d < 1 or d%2 != 0: - raise ValueError("d must be even and greater than 2") - - F = FiniteField(q,"x") - M = block_matrix(F, 2, 2, - [zero_matrix(F,d/2), - identity_matrix(F,d/2), - -identity_matrix(F,d/2), - zero_matrix(F,d/2)]) - - V = VectorSpace(F,d) - PV = list(ProjectiveSpace(d-1,F)) - G = Graph([[tuple(_) for _ in PV], lambda x,y:V(x)*(M*V(y)) == 0], loops = False) - G.name("Symplectic Graph Sp("+str(d)+","+str(q)+")") - G.relabel() - return G - -def AffineOrthogonalPolarGraph(d,q,sign="+"): - r""" - Returns the affine polar graph `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. - - Affine Polar graphs are built from a `d`-dimensional vector space over - `F_q`, and a quadratic form which is hyperbolic, elliptic or parabolic - according to the value of ``sign``. - - Note that `VO^+(d,q),VO^-(d,q)` are strongly regular graphs, while `VO(d,q)` - is not. - - For more information on Affine Polar graphs, see `Affine Polar - Graphs page of Andries Brouwer's website - `_. - - INPUT: - - - ``d`` (integer) -- ``d`` must be even if ``sign != None``, and odd - otherwise. - - - ``q`` (integer) -- a power of a prime number, as `F_q` must exist. - - - ``sign`` -- must be qual to ``"+"``, ``"-"``, or ``None`` to compute - (respectively) `VO^+(d,q),VO^-(d,q)` or `VO(d,q)`. By default - ``sign="+"``. - - .. NOTE:: - - The graph `VO^\epsilon(d,q)` is the graph induced by the - non-neighbors of a vertex in an :meth:`Orthogonal Polar Graph - ` `O^\epsilon(d+2,q)`. - - EXAMPLES: - - The :meth:`Brouwer-Haemers graph ` is isomorphic to - `VO^-(4,3)`:: - - sage: g = graphs.AffineOrthogonalPolarGraph(4,3,"-") - sage: g.is_isomorphic(graphs.BrouwerHaemersGraph()) - True - - Some examples from `Brouwer's table or strongly regular graphs - `_:: - - sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"-"); g - Affine Polar Graph VO^-(6,2): Graph on 64 vertices - sage: g.is_strongly_regular(parameters=True) - (64, 27, 10, 12) - sage: g = graphs.AffineOrthogonalPolarGraph(6,2,"+"); g - Affine Polar Graph VO^+(6,2): Graph on 64 vertices - sage: g.is_strongly_regular(parameters=True) - (64, 35, 18, 20) - - When ``sign is None``:: - - sage: g = graphs.AffineOrthogonalPolarGraph(5,2,None); g - Affine Polar Graph VO^-(5,2): Graph on 32 vertices - sage: g.is_strongly_regular(parameters=True) - False - sage: g.is_regular() - True - sage: g.is_vertex_transitive() - True - """ - if sign in ["+","-"]: - s = 1 if sign == "+" else -1 - if d%2 == 1: - raise ValueError("d must be even when sign!=None") - else: - if d%2 == 0: - raise ValueError("d must be odd when sign==None") - s = 0 - - from sage.interfaces.gap import gap - from sage.rings.finite_rings.constructor import FiniteField - from sage.modules.free_module import VectorSpace - from sage.matrix.constructor import Matrix - from sage.libs.gap.libgap import libgap - from itertools import combinations - - M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(s,d,q))['matrix']) - F = libgap.GF(q).sage() - V = list(VectorSpace(F,d)) - - G = Graph() - G.add_vertices([tuple(_) for _ in V]) - for x,y in combinations(V,2): - if not (x-y)*M*(x-y): - G.add_edge(tuple(x),tuple(y)) - - G.name("Affine Polar Graph VO^"+str('+' if s == 1 else '-')+"("+str(d)+","+str(q)+")") - G.relabel() - return G - -def OrthogonalPolarGraph(m, q, sign="+"): - r""" - Returns the Orthogonal Polar Graph `O^{\epsilon}(m,q)`. - - For more information on Orthogonal Polar graphs, see see the `page of - Andries Brouwer's website `_. - - INPUT: - - - ``m,q`` (integers) -- `q` must be a prime power. - - - ``sign`` -- ``"+"`` or ``"-"`` if `m` is even, ``"+"`` (default) - otherwise. - - EXAMPLES:: - - sage: G = graphs.OrthogonalPolarGraph(6,3,"+"); G - Orthogonal Polar Graph O^+(6, 3): Graph on 130 vertices - sage: G.is_strongly_regular(parameters=True) - (130, 48, 20, 16) - sage: G = graphs.OrthogonalPolarGraph(6,3,"-"); G - Orthogonal Polar Graph O^-(6, 3): Graph on 112 vertices - sage: G.is_strongly_regular(parameters=True) - (112, 30, 2, 10) - sage: G = graphs.OrthogonalPolarGraph(5,3); G - Orthogonal Polar Graph O(5, 3): Graph on 40 vertices - sage: G.is_strongly_regular(parameters=True) - (40, 12, 2, 4) - - TESTS:: - - sage: G = graphs.OrthogonalPolarGraph(4,3,"") - Traceback (most recent call last): - ... - ValueError: sign must be equal to either '-' or '+' when m is even - sage: G = graphs.OrthogonalPolarGraph(5,3,"-") - Traceback (most recent call last): - ... - ValueError: sign must be equal to either '' or '+' when m is odd - """ - from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField - from sage.modules.free_module_element import free_module_element as vector - from sage.matrix.constructor import Matrix - from sage.libs.gap.libgap import libgap - from itertools import combinations - - if m % 2 == 0: - if sign != "+" and sign != "-": - raise ValueError("sign must be equal to either '-' or '+' when " - "m is even") - else: - if sign != "" and sign != "+": - raise ValueError("sign must be equal to either '' or '+' when " - "m is odd") - sign = "" - - e = {'+': 1, - '-': -1, - '' : 0}[sign] - - M = Matrix(libgap.InvariantQuadraticForm(libgap.GeneralOrthogonalGroup(e,m,q))['matrix']) - Fq = libgap.GF(q).sage() - PG = ProjectiveSpace(m - 1, Fq) - m_over_two = m // 2 - - def F(x): - return x*M*x - - V = [x for x in PG if F(vector(x)) == 0] - - G = Graph([V,lambda x,y:F(vector(x)-vector(y))==0],loops=False) - - G.relabel() - G.name("Orthogonal Polar Graph O" + ("^" + sign if sign else "") + str((m, q))) - return G diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index 25c5fd1f60d..246250dc086 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -749,7 +749,7 @@ def RandomToleranceGraph(n): sage: g.clique_number() == g.chromatic_number() True - TEST: + TEST:: sage: g = graphs.RandomToleranceGraph(-2) Traceback (most recent call last): @@ -831,7 +831,8 @@ def rand_unit_vec(): from sage.geometry.polyhedron.plot import ProjectionFuncStereographic from sage.modules.free_module_element import vector proj = ProjectionFuncStereographic([0, 0, 1]) - ppoints = [proj(vector(x)) for x in points] - g.set_pos({i: ppoints[i] for i in range(len(points))}) + g.set_pos({v: proj(vector(v)) + for v in g}) + g.relabel() return g diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index a7f7cd443c5..b6bcebb66be 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -608,6 +608,31 @@ def Cell120(): return g +def SuzukiGraph(): + r""" + Return the Suzuki Graph + + The Suzuki graph has 1782 vertices, and is strongly regular with parameters + `(1782,416,100,96)`. + + .. NOTE:: + + It takes approximately 50 seconds to build this graph. Do not be too + impatient. + + EXAMPLE:: + + sage: g = graphs.SuzukiGraph(); g # optional database_gap internet # not tested + Suzuki graph: Graph on 1782 vertices + sage: g.is_strongly_regular(parameters=True) # optional database_gap internet # not tested + (1782, 416, 100, 96) + """ + from sage.groups.perm_gps.permgroup_named import SuzukiSporadicGroup + g = Graph() + g.add_edges(SuzukiSporadicGroup().orbit((1,2),"OnSets")) + g.relabel() + g.name("Suzuki graph") + return g def HallJankoGraph(from_string=True): r""" @@ -1535,7 +1560,7 @@ def GossetGraph(): Return the Gosset graph. The Gosset graph is the skeleton of the - :meth:`~sage.geometry.polyhedron.library.polytopes.gosset_3_21` polytope. It + :meth:`~sage.geometry.polyhedron.library.Polytopes.Gosset_3_21` polytope. It has with 56 vertices and degree 27. For more information, see the :wikipedia:`Gosset_graph`. @@ -1810,7 +1835,7 @@ def ChvatalGraph(): 2 4 - TEST: + TEST:: sage: import networkx sage: G = graphs.ChvatalGraph() @@ -2647,7 +2672,7 @@ def FruchtGraph(): 'KhCKM?_EGK?L' sage: (graphs.FruchtGraph()).show() # long time - TEST: + TEST:: sage: import networkx sage: G = graphs.FruchtGraph() @@ -2896,7 +2921,7 @@ def HeawoodGraph(): 'MhEGHC@AI?_PC@_G_' sage: (graphs.HeawoodGraph()).show() # long time - TEST: + TEST:: sage: import networkx sage: G = graphs.HeawoodGraph() @@ -3363,7 +3388,7 @@ def KrackhardtKiteGraph(): sage: g = graphs.KrackhardtKiteGraph() sage: g.show() # long time - TEST: + TEST:: sage: import networkx sage: G = graphs.KrackhardtKiteGraph() @@ -3748,11 +3773,11 @@ def McLaughlinGraph(): blocks = [Set(_) for _ in WittDesign(23).blocks()] - B = [b for b in blocks if 0 in b] + B = [b for b in blocks if 0 in b] C = [b for b in blocks if 0 not in b] g = Graph() for b in B: - for x in range(23): + for x in range(1,23): if not x in b: g.add_edge(b, x) @@ -3773,7 +3798,8 @@ def McLaughlinGraph(): if len(b & c) == 3: g.add_edge(b, c) - g.relabel() + # Here we relabel the elements of g in an architecture-independent way + g.relabel({v:i for i,v in enumerate(range(1,23)+sorted(blocks,key=sorted))}) g.name("McLaughlin") return g @@ -4411,6 +4437,50 @@ def TietzeGraph(): return g +def TruncatedIcosidodecahedralGraph(): + r""" + Return the truncated icosidodecahedron. + + The truncated icosidodecahedron is an Archimedean solid with 30 square + faces, 20 regular hexagonal faces, 12 regular decagonal faces, 120 vertices + and 180 edges. For more information, see the + :wikipedia:`Truncated_icosidodecahedron`. + + EXAMPLE:: + + sage: g = graphs.TruncatedIcosidodecahedralGraph(); g + Truncated Icosidodecahedron: Graph on 120 vertices + sage: g.order(), g.size() + (120, 180) + """ + from sage.geometry.polyhedron.library import polytopes + G = polytopes.icosidodecahedron(exact=False).edge_truncation().graph() + G.name("Truncated Icosidodecahedron") + return G + +def TruncatedTetrahedralGraph(): + r""" + Return the truncated tetrahedron. + + The truncated tetrahedron is an Archimedean solid with 12 vertices and 18 + edges. For more information, see the :wikipedia:`Truncated_tetrahedron`. + + EXAMPLE:: + + sage: g = graphs.TruncatedTetrahedralGraph(); g + Truncated Tetrahedron: Graph on 12 vertices + sage: g.order(), g.size() + (12, 18) + sage: g.is_isomorphic(polytopes.simplex(3).edge_truncation().graph()) + True + """ + g = Graph(':K`ESwC_EOyDl\\MCi', loops=False, multiedges=False) + _circle_embedding(g, range(6), radius=1) + _circle_embedding(g, range(6,9), radius=.6, shift=.25) + _circle_embedding(g, range(9,12), radius=.2, shift=.25) + g.name("Truncated Tetrahedron") + return g + def Tutte12Cage(): r""" Returns Tutte's 12-Cage. diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 70b472363c6..b78f3a1da5d 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -12,6 +12,7 @@ :delim: | :meth:`~GenericGraph.networkx_graph` | Create a new NetworkX graph from the Sage graph + :meth:`~GenericGraph.igraph_graph` | Create a new igraph graph from the Sage graph :meth:`~GenericGraph.to_dictionary` | Create a dictionary encoding the graph. :meth:`~GenericGraph.copy` | Return a copy of the graph. :meth:`~GenericGraph.export_to_file` | Export the graph to a file. @@ -20,8 +21,6 @@ :meth:`~GenericGraph.distance_matrix` | Return the distance matrix of the (strongly) connected (di)graph :meth:`~GenericGraph.weighted_adjacency_matrix` | Return the weighted adjacency matrix of the graph :meth:`~GenericGraph.kirchhoff_matrix` | Return the Kirchhoff matrix (a.k.a. the Laplacian) of the graph. - :meth:`~GenericGraph.get_boundary` | Return the boundary of the (di)graph. - :meth:`~GenericGraph.set_boundary` | Set the boundary of the (di)graph. :meth:`~GenericGraph.has_loops` | Return whether there are loops in the (di)graph. :meth:`~GenericGraph.allows_loops` | Return whether loops are permitted in the (di)graph. :meth:`~GenericGraph.allow_loops` | Change whether loops are permitted in the (di)graph. @@ -73,7 +72,7 @@ :meth:`~GenericGraph.remove_loops` | Remove loops on vertices in vertices. If vertices is None, removes all loops. :meth:`~GenericGraph.loop_edges` | Returns a list of all loops in the graph. :meth:`~GenericGraph.number_of_loops` | Return the number of edges that are loops. - :meth:`~GenericGraph.clear` | Empty the graph of vertices and edges and removes name, boundary, associated objects, and position information. + :meth:`~GenericGraph.clear` | Empty the graph of vertices and edges and removes name, associated objects, and position information. :meth:`~GenericGraph.degree` | Return the degree (in + out for digraphs) of a vertex or of vertices. :meth:`~GenericGraph.average_degree` | Return the average degree of the graph. :meth:`~GenericGraph.degree_histogram` | Return a list, whose ith entry is the frequency of degree i. @@ -115,7 +114,6 @@ :meth:`~GenericGraph.eulerian_orientation` | Return a DiGraph which is an Eulerian orientation of the current graph. :meth:`~GenericGraph.eulerian_circuit` | Return a list of edges forming an eulerian circuit if one exists. :meth:`~GenericGraph.cycle_basis` | Return a list of cycles which form a basis of the cycle space of ``self``. - :meth:`~GenericGraph.interior_paths` | Return an exhaustive list of paths (also lists) through only interior vertices from vertex start to vertex end in the (di)graph. :meth:`~GenericGraph.all_paths` | Return a list of all paths (also lists) between a pair of vertices in the (di)graph. :meth:`~GenericGraph.triangles_count` | Return the number of triangles in the (di)graph. @@ -196,6 +194,7 @@ :delim: | :meth:`~GenericGraph.centrality_betweenness` | Return the betweenness centrality + :meth:`~GenericGraph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices) :meth:`~GenericGraph.distance` | Return the (directed) distance from u to v in the (di)graph :meth:`~GenericGraph.distance_all_pairs` | Return the distances between all pairs of vertices. :meth:`~GenericGraph.distances_distribution` | Return the distances distribution of the (di)graph in a dictionary. @@ -457,52 +456,50 @@ def __eq__(self, other): """ # inputs must be (di)graphs: if not isinstance(other, GenericGraph): - raise TypeError("cannot compare graph to non-graph (%s)"%str(other)) + return False from sage.graphs.all import Graph g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph - - if (g1_is_graph != g2_is_graph): - return False - if self.allows_multiple_edges() != other.allows_multiple_edges(): - return False - if self.allows_loops() != other.allows_loops(): - return False - if self.order() != other.order(): - return False - if self.size() != other.size(): - return False + # Fast checks + if (g1_is_graph != g2_is_graph or + self.allows_multiple_edges() != other.allows_multiple_edges() or + self.allows_loops() != other.allows_loops() or + self.order() != other.order() or + self.size() != other.size() or + self.weighted() != other.weighted()): + return False + # Vertices if any(x not in other for x in self): return False - if self.weighted() != other.weighted(): - return False - verts = self.vertices() # Finally, we are prepared to check edges: if not self.allows_multiple_edges(): - for i in verts: - for j in verts: - if self.has_edge(i,j) != other.has_edge(i,j): - return False - if self.has_edge(i,j) and self._weighted and other._weighted: - if self.edge_label(i,j) != other.edge_label(i,j): - return False - return True - else: - for i in verts: - for j in verts: - if self.has_edge(i, j): - edges1 = self.edge_label(i, j) - else: - edges1 = [] - if other.has_edge(i, j): - edges2 = other.edge_label(i, j) - else: - edges2 = [] - if len(edges1) != len(edges2): - return False - if sorted(edges1) != sorted(edges2) and self._weighted and other._weighted: + return all(other.has_edge(*edge) + for edge in self.edge_iterator(labels=self._weighted)) + # The problem with multiple edges is that labels may not have total + # ordering, which makes it difficult to compare lists of labels. + last_i = last_j = None + for i, j in self.edge_iterator(labels=False): + if i == last_i and j == last_j: + continue + last_i, last_j = i, j + # All labels between i and j + labels1 = self.edge_label(i, j) + try: + labels2 = other.edge_label(i, j) + except LookupError: + return False + if len(labels1) != len(labels2): + return False + if self._weighted: + # If there is total ordering, sorting will speed up things + labels1.sort() + labels2.sort() + for l in labels1: + try: + labels2.remove(l) + except ValueError: return False - return True + return True @cached_method def __hash__(self): @@ -910,14 +907,12 @@ def copy(self, weighted=None, implementation='c_graph', data_structure=None, TESTS: - We make copies of the ``_pos`` and ``_boundary`` attributes:: + We make copies of the ``_pos`` attribute:: sage: g = graphs.PathGraph(3) sage: h = copy(g) sage: h._pos is g._pos False - sage: h._boundary is g._boundary - False We make sure that one can make immutable copies by providing the ``data_structure`` optional argument, and that copying an immutable graph @@ -1043,7 +1038,7 @@ def copy(self, weighted=None, implementation='c_graph', data_structure=None, data_structure = "sparse" G = self.__class__(self, name=self.name(), pos=copy(self._pos), - boundary=copy(self._boundary), weighted=weighted, + weighted=weighted, implementation=implementation, data_structure=data_structure) @@ -1310,6 +1305,156 @@ def networkx_graph(self, copy=True): N.add_edge(u,v,weight=l) return N + def igraph_graph(self, vertex_attrs={}, edge_attrs={}): + r""" + Converts the graph into an igraph graph. + + Optionally, it is possible to add vertex attributes and edge attributes + to the output graph. + + .. NOTE:: + + This routine needs the optional package igraph to be installed: + to do so, it is enough to + run ``sage -i python_igraph``. For + more information on the Python version of igraph, see + http://igraph.org/python/. + + INPUT: + + - ``vertex_attrs`` (dictionary) - a dictionary where the key is a string + (the attribute name), and the value is an iterable containing in + position i the label of the ith vertex returned by :meth:`vertices` + (see http://igraph.org/python/doc/igraph.Graph-class.html#__init__ for + more information). + + - ``edge_attrs`` (dictionary) - a dictionary where the key is a string + (the attribute name), and the value is an iterable containing in + position i the label of the ith edge in the list outputted by + :meth:`edge_iterator` (see + http://igraph.org/python/doc/igraph.Graph-class.html#__init__ for more + information). + + .. NOTE:: + + In igraph, a graph is weighted if the edge labels have attribute + ``weight``. Hence, to create a weighted graph, it is enough to add + this attribute. + + .. NOTE:: + + Often, Sage uses its own defined types for integer/floats. These + types may not be igraph-compatible (see example below). + + EXAMPLES: + + Standard conversion:: + + sage: G = graphs.TetrahedralGraph() # optional - python_igraph + sage: H = G.igraph_graph() # optional - python_igraph + sage: H.summary() # optional - python_igraph + 'IGRAPH U--- 4 6 -- ' + sage: G = digraphs.Path(3) # optional - python_igraph + sage: H = G.igraph_graph() # optional - python_igraph + sage: H.summary() # optional - python_igraph + 'IGRAPH D--- 3 2 -- ' + + Adding edge attributes:: + + sage: G = Graph([(1,2,'a'),(2,3,'b')]) # optional - python_igraph + sage: H = G.igraph_graph(edge_attrs = {'label':[e[2] for e in G.edges()]}) # optional - python_igraph + sage: H.es['label'] # optional - python_igraph + ['a', 'b'] + + + If edges have an attribute ``weight``, the igraph graph is considered + weighted:: + + sage: G = Graph([(1,2,{'weight':1}),(2,3,{'weight':2})]) # optional - python_igraph + sage: H = G.igraph_graph(edge_attrs = {'weight':[e[2]['weight'] for e in G.edges()]}) # optional - python_igraph + sage: H.is_weighted() # optional - python_igraph + True + sage: H.es['weight'] # optional - python_igraph + [1, 2] + + Adding vertex attributes:: + + sage: G = graphs.GridGraph([2,2]) # optional - python_igraph + sage: H = G.igraph_graph(vertex_attrs={'name':G.vertices()}) # optional - python_igraph + sage: H.vs()['name'] # optional - python_igraph + [(0, 0), (0, 1), (1, 0), (1, 1)] + + Sometimes, Sage integer/floats are not compatible with igraph:: + + sage: G = Graph([(0,1,2)]) # optional - python_igraph + sage: H = G.igraph_graph(edge_attrs = {'capacity':[e[2] for e in G.edges()]}) # optional - python_igraph + sage: H.maxflow_value(0, 1, 'capacity') # optional - python_igraph + 1.0 + sage: H = G.igraph_graph(edge_attrs = {'capacity':[float(e[2]) for e in G.edges()]}) # optional - python_igraph + sage: H.maxflow_value(0, 1, 'capacity') # optional - python_igraph + 2.0 + + TESTS: + + Converting a DiGraph back and forth:: + + sage: G = DiGraph([('a','b',{'w':1}),('b','c',{'w':2})]) # optional - python_igraph + sage: vertex_attrs={'name':G.vertices()} # optional - python_igraph + sage: edge_attrs={'w':[e[2]['w'] for e in G.edges()]} # optional - python_igraph + sage: H = DiGraph(G.igraph_graph(vertex_attrs, edge_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + True + sage: G.edges() == H.edges() # optional - python_igraph + True + sage: H = DiGraph(G.igraph_graph(edge_attrs=edge_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + False + + When checking for equality, edge labels are not taken into account:: + + sage: H = DiGraph(G.igraph_graph(vertex_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + True + sage: G.edges() == H.edges() # optional - python_igraph + False + + Converting a Graph back and forth:: + + sage: G = Graph([('a','b',{'w':1}),('b','c',{'w':2})]) # optional - python_igraph + sage: vertex_attrs={'name':G.vertices()} # optional - python_igraph + sage: edge_attrs={'w':[e[2]['w'] for e in G.edges()]} # optional - python_igraph + sage: H = Graph(G.igraph_graph(vertex_attrs, edge_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + True + sage: G.edges() == H.edges() # optional - python_igraph + True + sage: H = Graph(G.igraph_graph(edge_attrs=edge_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + False + + When checking for equality, edge labels are not taken into account:: + + sage: H = Graph(G.igraph_graph(vertex_attrs)) # optional - python_igraph + sage: G == H # optional - python_igraph + True + sage: G.edges() == H.edges() # optional - python_igraph + False + """ + try: + import igraph + except ImportError: + raise ImportError("The package igraph is not available. To " + + "install it, run 'sage -i python_igraph'") + + v_to_int = {v:i for i,v in enumerate(self.vertices())} + edges = [(v_to_int[v], v_to_int[w]) for v,w in self.edge_iterator(labels=False)] + + return igraph.Graph(n = self.num_verts(), + edges = edges, + directed=self.is_directed(), + vertex_attrs = vertex_attrs, + edge_attrs = edge_attrs) + def to_dictionary(self, edge_labels=False, multiple_edges=False): r""" Returns the graph as a dictionary. @@ -1475,21 +1620,18 @@ def to_dictionary(self, edge_labels=False, multiple_edges=False): return d - def adjacency_matrix(self, sparse=None, boundary_first=False, vertices=None): + def adjacency_matrix(self, sparse=None, vertices=None): """ Returns the adjacency matrix of the (di)graph. The matrix returned is over the integers. If a different ring is - desired, use either the change_ring function or the matrix + desired, use either :meth:`sage.matrix.matrix0.Matrix.change_ring` method or :func:`matrix` function. INPUT: - ``sparse`` - whether to represent with a sparse matrix - - ``boundary_first`` - whether to represent the boundary vertices in the - upper left block. Cannot be used in conjunction with ``vertices``. - - ``vertices`` (list) -- the ordering of the vertices defining how they should appear in the matrix. By default, the ordering given by :meth:`GenericGraph.vertices` is used. @@ -1578,13 +1720,12 @@ def adjacency_matrix(self, sparse=None, boundary_first=False, vertices=None): sparse=True if self.has_multiple_edges() or n <= 256 or self.density() > 0.05: sparse=False + if vertices is None: - vertices = self.vertices(boundary_first=boundary_first) + vertices = self.vertices() elif (len(vertices) != n or set(vertices) != set(self.vertices())): raise ValueError("``vertices`` must be a permutation of the vertices") - elif boundary_first: - raise ValueError("``boundary`` and ``vertices`` cannot be used simultaneously") new_indices = dict((v,i) for i,v in enumerate(vertices)) D = {} @@ -1776,7 +1917,7 @@ def distance_matrix(self): return ret - def weighted_adjacency_matrix(self, sparse=True, boundary_first=False): + def weighted_adjacency_matrix(self, sparse=True): """ Returns the weighted adjacency matrix of the graph. @@ -1808,7 +1949,7 @@ def weighted_adjacency_matrix(self, sparse=True, boundary_first=False): if self.has_multiple_edges(): raise NotImplementedError("don't know how to represent weights for a multigraph") - verts = self.vertices(boundary_first=boundary_first) + verts = self.vertices() new_indices = dict((v,i) for i,v in enumerate(verts)) D = {} @@ -1895,33 +2036,6 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, **kwd [-1 2 -1 0] [-1 -1 2 0] [-1 0 0 1] - sage: G.set_boundary([2,3]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - sage: M = G.kirchhoff_matrix(weighted=True, boundary_first=True); M - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - [ 5 0 -3 -2] - [ 0 4 -4 0] - [-3 -4 8 -1] - [-2 0 -1 3] - sage: M = G.kirchhoff_matrix(boundary_first=True); M - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - [ 2 0 -1 -1] - [ 0 1 -1 0] - [-1 -1 3 -1] - [-1 0 -1 2] - sage: M = G.laplacian_matrix(boundary_first=True); M - [ 2 0 -1 -1] - [ 0 1 -1 0] - [-1 -1 3 -1] - [-1 0 -1 2] - sage: M = G.laplacian_matrix(boundary_first=True, sparse=False); M - [ 2 0 -1 -1] - [ 0 1 -1 0] - [-1 -1 3 -1] - [-1 0 -1 2] sage: M = G.laplacian_matrix(normalized=True); M [ 1 -1/6*sqrt(3)*sqrt(2) -1/6*sqrt(3)*sqrt(2) -1/3*sqrt(3)] [-1/6*sqrt(3)*sqrt(2) 1 -1/2 0] @@ -1994,48 +2108,6 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, **kwd ### Attributes - def get_boundary(self): - """ - Returns the boundary of the (di)graph. - - EXAMPLES:: - - sage: G = graphs.PetersenGraph() - sage: G.set_boundary([0,1,2,3,4]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - sage: G.get_boundary() - [0, 1, 2, 3, 4] - """ - from sage.misc.superseded import deprecation - deprecation(15494, "The boundary parameter is deprecated and will soon disappear.") - - return self._boundary - - def set_boundary(self, boundary): - """ - Sets the boundary of the (di)graph. - - EXAMPLES:: - - sage: G = graphs.PetersenGraph() - sage: G.set_boundary([0,1,2,3,4]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - sage: G.get_boundary() - [0, 1, 2, 3, 4] - sage: G.set_boundary((1..4)) - sage: G.get_boundary() - [1, 2, 3, 4] - """ - from sage.misc.superseded import deprecation - deprecation(15494, "The boundary parameter is deprecated and will soon disappear.") - - if isinstance(boundary,list): - self._boundary = boundary - else: - self._boundary = list(boundary) - def set_embedding(self, embedding): """ Sets a combinatorial embedding dictionary to ``_embedding`` attribute. @@ -3344,7 +3416,7 @@ def eulerian_circuit(self, return_vertices=False, labels=True, path=False): def min_spanning_tree(self, weight_function=None, - algorithm="Kruskal", + algorithm="Prim_Boost", starting_vertex=None, check=False): r""" @@ -3354,24 +3426,43 @@ def min_spanning_tree(self, graph is directed, a minimum spanning tree of the corresponding undirected graph is returned. + We expect all weights of the graph to be convertible to float. + Otherwise, an exception is raised. + INPUT: - - ``weight_function`` -- A function that takes an edge and returns a - numeric weight. If ``None`` (default), the algorithm uses the edge - weights, if available, otherwise it assigns weight 1 to each edge (in - the latter case, the output can be any spanning tree). + - ``weight_function`` (function) - a function that inputs an edge ``e`` + and outputs its weight. An edge has the form ``(u,v,l)``, where ``u`` + and ``v`` are vertices, ``l`` is a label (that can be of any kind). + The ``weight_function`` can be used to transform the label into a + weight (note that, if the weight returned is not convertible to a + float, an error is raised). In particular: + + - if ``weight_function`` is not ``None``, the weight of an edge ``e`` + is ``weight_function(e)``; + + - if ``weight_function`` is ``None`` (default) and ``g`` is weighted + (that is, ``g.weighted()==True``), for each edge ``e=(u,v,l)``, we + set weight ``l``; + + - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set + all weights to 1 (hence, the output can be any spanning tree). - ``algorithm`` -- The algorithm to use in computing a minimum spanning - tree of ``G``. The default is to use Kruskal's algorithm. The - following algorithms are supported: + tree of ``G``. The following algorithms are supported: - - ``"Kruskal"`` -- Kruskal's algorithm. + - ``"Prim_Boost"`` (default) -- Prim's algorithm + (Boost implementation). - ``"Prim_fringe"`` -- a variant of Prim's algorithm. ``"Prim_fringe"`` ignores the labels on the edges. - ``"Prim_edge"`` -- a variant of Prim's algorithm. + - ``"Kruskal"`` -- Kruskal's algorithm. + + - ``"Kruskal_Boost"`` -- Kruskal's algorithm (Boost implementation). + - ``NetworkX`` -- Uses NetworkX's minimum spanning tree implementation. @@ -3394,6 +3485,7 @@ def min_spanning_tree(self, .. seealso:: - :func:`sage.graphs.spanning_tree.kruskal` + - :func:`sage.graphs.base.boost_graph.min_spanning_tree` EXAMPLES: @@ -3405,12 +3497,13 @@ def min_spanning_tree(self, sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1)) sage: g.min_spanning_tree(weight_function=weight) [(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] + sage: g.min_spanning_tree(weight_function=weight, algorithm='Kruskal_Boost') + [(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] sage: g = graphs.PetersenGraph() sage: g.allow_multiple_edges(True) - sage: g.weighted(True) sage: g.add_edges(g.edges()) sage: g.min_spanning_tree() - [(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 8, None), (4, 9, None)] + [(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), (1, 6, None), (3, 8, None), (5, 7, None), (5, 8, None), (6, 9, None)] Prim's algorithm:: @@ -3419,12 +3512,34 @@ def min_spanning_tree(self, [(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] sage: g.min_spanning_tree(algorithm='Prim_fringe', starting_vertex=2, weight_function=weight) [(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] + sage: g.min_spanning_tree(weight_function=weight, algorithm='Prim_Boost') + [(0, 4, None), (1, 4, None), (2, 4, None), (3, 4, None)] NetworkX algorithm:: sage: g.min_spanning_tree(algorithm='NetworkX') [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None)] + More complicated weights:: + + sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})]) + sage: G.min_spanning_tree(weight_function=lambda e: e[2]['weight']) + [(0, 1, {'name': 'a', 'weight': 1}), (1, 2, {'name': 'b', 'weight': 1})] + + If the graph is not weighted, edge labels are not considered, even if + they are numbers:: + + sage: g = Graph([[1,2,1], [1,3,2], [2,3,1]]) + sage: g.min_spanning_tree() + [(1, 2, 1), (1, 3, 2)] + + In order to use weights, we need to set variable ``weighted`` to + ``True``:: + + sage: g.weighted(True) + sage: g.min_spanning_tree() + [(1, 2, 1), (2, 3, 1)] + TESTS: Check that, if ``weight_function`` is not provided, then edge weights @@ -3434,10 +3549,14 @@ def min_spanning_tree(self, sage: g.add_edges([[0,1,1],[1,2,1],[2,0,10]]) sage: g.min_spanning_tree() [(0, 1, 1), (1, 2, 1)] + sage: g.min_spanning_tree(algorithm='Kruskal_Boost') + [(0, 1, 1), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='Prim_fringe') [(0, 1, 1), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='Prim_edge') [(0, 1, 1), (1, 2, 1)] + sage: g.min_spanning_tree(algorithm='Prim_Boost') + [(0, 1, 1), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='NetworkX') [(0, 1, 1), (1, 2, 1)] @@ -3448,10 +3567,14 @@ def min_spanning_tree(self, sage: weight = lambda e:3-e[0]-e[1] sage: g.min_spanning_tree(weight_function=weight) [(0, 2, 10), (1, 2, 1)] + sage: g.min_spanning_tree(algorithm='Kruskal_Boost', weight_function=weight) + [(0, 2, 10), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='Prim_fringe', weight_function=weight) [(0, 2, 10), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='Prim_edge', weight_function=weight) [(0, 2, 10), (1, 2, 1)] + sage: g.min_spanning_tree(algorithm='Prim_Boost', weight_function=weight) + [(0, 2, 10), (1, 2, 1)] sage: g.min_spanning_tree(algorithm='NetworkX', weight_function=weight) [(0, 2, 10), (1, 2, 1)] @@ -3462,20 +3585,86 @@ def min_spanning_tree(self, [(0, 2, None), (1, 2, None)] sage: g.to_undirected().min_spanning_tree(weight_function=weight) [(0, 2, None), (1, 2, None)] - """ - if algorithm == "Kruskal": - from spanning_tree import kruskal - if self.is_directed(): - return kruskal(self.to_undirected(), wfunction=weight_function, check=check) - else: - return kruskal(self, wfunction=weight_function, check=check) + If at least an edge weight is not convertible to a float, an error is + raised:: + + sage: g = Graph([(0,1,1), (1,2,'a')], weighted=True) + sage: g.min_spanning_tree(algorithm="Prim_Boost") + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, 'a'). + sage: g.min_spanning_tree(algorithm="Prim_fringe") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: a + sage: g.min_spanning_tree(algorithm="Prim_edge") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: a + sage: g.min_spanning_tree(algorithm="Kruskal") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: a + sage: g.min_spanning_tree(algorithm="Kruskal_Boost") + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, 'a'). + sage: g.min_spanning_tree(algorithm="NetworkX") + Traceback (most recent call last): + ... + ValueError: could not convert string to float: a + + sage: g = Graph([(0,1,1), (1,2,[1,2,3])], weighted=True) + + sage: g.min_spanning_tree(algorithm="Prim_Boost") + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, [1, 2, 3]). + sage: g.min_spanning_tree(algorithm="Prim_fringe") + Traceback (most recent call last): + ... + TypeError: float() argument must be a string or a number + sage: g.min_spanning_tree(algorithm="Prim_edge") + Traceback (most recent call last): + ... + TypeError: float() argument must be a string or a number + sage: g.min_spanning_tree(algorithm="Kruskal") + Traceback (most recent call last): + ... + TypeError: float() argument must be a string or a number + sage: g.min_spanning_tree(algorithm="Kruskal_Boost") + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (1, 2, [1, 2, 3]). + sage: g.min_spanning_tree(algorithm="NetworkX") + Traceback (most recent call last): + ... + TypeError: float() argument must be a string or a number + """ if weight_function is None: if self.weighted(): - weight_function = lambda e:self.edge_label(e[0], e[1]) + weight_function = lambda e:e[2] else: weight_function = lambda e:1 + wfunction_float = lambda e:float(weight_function(e)) + + if algorithm in ["Kruskal", "Kruskal_Boost", "Prim_Boost"]: + if self.is_directed(): + g = self.to_undirected() + else: + g = self + + if algorithm == "Kruskal": + from spanning_tree import kruskal + return kruskal(g, wfunction=wfunction_float, check=check) + else: + from sage.graphs.base.boost_graph import min_spanning_tree + return min_spanning_tree(g, + weight_function=wfunction_float, + algorithm=algorithm.split("_")[0]) + if algorithm == "Prim_fringe": if starting_vertex is None: v = next(self.vertex_iterator()) @@ -3486,18 +3675,21 @@ def min_spanning_tree(self, # Initialize fringe_list with v's neighbors. Fringe_list # contains fringe_vertex: (vertex_in_tree, weight) for each # fringe vertex. - fringe_list = dict([u, (weight_function((v, u)), v)] for u in self[v]) + fringe_list = dict([e[0] if e[0]!=v else e[1], (wfunction_float(e), v)] for e in self.edges_incident(v)) cmp_fun = lambda x: fringe_list[x] for i in range(self.order() - 1): # find the smallest-weight fringe vertex u = min(fringe_list, key=cmp_fun) x = fringe_list[u][1] - edges.append((min(x,u), max(x,u), self.edge_label(x, u))) + edges.append((min(x,u), max(x,u), self.edge_label(x,u))) tree.add(u) fringe_list.pop(u) # update fringe list - for neighbor in [v for v in self[u] if v not in tree]: - w = weight_function((u, neighbor)) + for e in self.edges_incident(u): + neighbor = e[0] if e[0]!=u else e[1] + if neighbor in tree: + continue + w = wfunction_float(e) if neighbor not in fringe_list or fringe_list[neighbor][0] > w: fringe_list[neighbor] = (w, u) return sorted(edges) @@ -3507,7 +3699,7 @@ def min_spanning_tree(self, v = next(self.vertex_iterator()) else: v = starting_vertex - sorted_edges = sorted(self.edges(), key=weight_function) + sorted_edges = sorted(self.edges(), key=wfunction_float) tree = set([v]) edges = [] for _ in range(self.order() - 1): @@ -3536,7 +3728,7 @@ def min_spanning_tree(self, elif algorithm == "NetworkX": import networkx - G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()]) + G = networkx.Graph([(u, v, dict(weight=wfunction_float((u, v, l)))) for u, v, l in self.edge_iterator()]) return sorted([(u,v,self.edge_label(u,v)) if u 0 and e.source < e.target) or (f < 0 and e.source > e.target): + flow_digraph.add_edge(e.source, e.target, abs(f)) + elif f != 0: + flow_digraph.add_edge(e.target, e.source, abs(f)) + flow_digraph.relabel({i:vertices[i] for i in flow_digraph}) + return [maxflow.value, flow_digraph] - if method != "LP" and not method is None: - raise ValueError("The method argument has to be equal to either \"FF\", \"LP\" or None") + if method != "LP": + raise ValueError("The method argument has to be equal to either " + + "\"FF\", \"LP\", \"igraph\", or None") from sage.numerical.mip import MixedIntegerLinearProgram @@ -7575,12 +7861,6 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte p=MixedIntegerLinearProgram(maximization=True, solver = solver) flow=p.new_variable(nonnegative=True) - if use_edge_labels: - from sage.rings.real_mpfr import RR - capacity=lambda x: x if x in RR else 1 - else: - capacity=lambda x: 1 - if g.is_directed(): # This function return the balance of flow at X flow_sum=lambda X: p.sum([flow[(X,v)] for (u,v) in g.outgoing_edges([X],labels=None)])-p.sum([flow[(u,X)] for (u,v) in g.incoming_edges([X],labels=None)]) @@ -8965,14 +9245,9 @@ def delete_vertex(self, vertex, in_order=False): [(0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] sage: G = graphs.PathGraph(5) sage: G.set_vertices({0: 'no delete', 1: 'delete'}) - sage: G.set_boundary([1,2]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. sage: G.delete_vertex(1) sage: G.get_vertices() {0: 'no delete', 2: None, 3: None, 4: None} - sage: G.get_boundary() - [2] sage: G.get_pos() {0: (0, 0), 2: (2, 0), 3: (3, 0), 4: (4, 0)} """ @@ -8986,7 +9261,6 @@ def delete_vertex(self, vertex, in_order=False): for attr in attributes_to_update: if hasattr(self, attr) and getattr(self, attr) is not None: getattr(self, attr).pop(vertex, None) - self._boundary = [v for v in self._boundary if v != vertex] def delete_vertices(self, vertices): """ @@ -9019,8 +9293,6 @@ def delete_vertices(self, vertices): for vertex in vertices: attr_dict.pop(vertex, None) - self._boundary = [v for v in self._boundary if v not in vertices] - def has_vertex(self, vertex): """ Return True if vertex is one of the vertices of this graph. @@ -9361,7 +9633,7 @@ def neighbor_iterator(self, vertex): """ return self._backend.iterator_nbrs(vertex) - def vertices(self, key=None, boundary_first=False): + def vertices(self, key=None): r""" Return a list of the vertices. @@ -9371,9 +9643,6 @@ def vertices(self, key=None, boundary_first=False): a vertex as its one argument and returns a value that can be used for comparisons in the sorting algorithm. - - ``boundary_first`` - default: ``False`` - if ``True``, - return the boundary vertices first. - OUTPUT: The vertices of the list. @@ -9439,32 +9708,8 @@ def vertices(self, key=None, boundary_first=False): [t^2 + 2, t^2, 5*t, 4*t^2 - 6] sage: [x.discriminant() for x in verts] [-8, 0, 1, 96] - - If boundary vertices are requested first, then they are sorted - separately from the remainder (which are also sorted). :: - - sage: P = graphs.PetersenGraph() - sage: P.set_boundary((5..9)) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - sage: P.vertices(boundary_first=True) - [5, 6, 7, 8, 9, 0, 1, 2, 3, 4] - sage: P.vertices(boundary_first=True, key=lambda x: -x) - [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] """ - if not boundary_first: - return sorted(list(self.vertex_iterator()), key=key) - - from sage.misc.superseded import deprecation - deprecation(15494, "The boundary parameter is deprecated and will soon disappear.") - bdy_verts = [] - int_verts = [] - for v in self.vertex_iterator(): - if v in self._boundary: - bdy_verts.append(v) - else: - int_verts.append(v) - return sorted(bdy_verts, key=key) + sorted(int_verts, key=key) + return sorted(list(self.vertex_iterator()), key=key) def neighbors(self, vertex): """ @@ -10540,8 +10785,8 @@ def number_of_loops(self): def clear(self): """ - Empties the graph of vertices and edges and removes name, boundary, - associated objects, and position information. + Empties the graph of vertices and edges and removes name, associated + objects, and position information. EXAMPLES:: @@ -11131,8 +11376,6 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None): value = dict([(v, getattr(self, attr).get(v, None)) for v in G]) setattr(G, attr,value) - G._boundary = [v for v in self._boundary if v in G] - return G def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False, @@ -12301,7 +12544,7 @@ def is_independent_set(self, vertices=None): def is_subgraph(self, other, induced=True): """ - Tests whether ``self`` is a subgraph of ``other``. + Return ``True`` if the graph is a subgraph of ``other``, and ``False`` otherwise. .. WARNING:: @@ -12314,31 +12557,34 @@ def is_subgraph(self, other, induced=True): INPUT: - ``induced`` - boolean (default: ``True``) If set to ``True`` tests - whether ``self`` is an *induced* subgraph of ``other`` that is if - the vertices of ``self`` are also vertices of ``other``, and the - edges of ``self`` are equal to the edges of ``other`` between the - vertices contained in ``self`. - If set to ``False`` tests whether ``self`` is a subgraph of ``other`` - that is if all vertices of ``self`` are also in ``other`` and all - edges of ``self`` are also in ``other``. + whether the graph is an *induced* subgraph of ``other`` that is if + the vertices of the graph are also vertices of ``other``, and the + edges of the graph are equal to the edges of ``other`` between the + vertices contained in the graph. + + If set to ``False`` tests whether the graph is a subgraph of + ``other`` that is if all vertices of the graph are also in + ``other`` and all edges of the graph are also in ``other``. OUTPUT: - boolean -- ``True`` iff ``self`` is a (possibly induced) subgraph of ``other``. + boolean -- ``True`` iff the graph is a (possibly induced) + subgraph of ``other``. .. SEEALSO:: - If you are interested in the (possibly induced) subgraphs isomorphic - to ``self`` in ``other``, you are looking for the following methods: + If you are interested in the (possibly induced) subgraphs + isomorphic to the graph in ``other``, you are looking for + the following methods: - - :meth:`~GenericGraph.subgraph_search` -- finds a subgraph - isomorphic to `G` inside of a `self` + - :meth:`~GenericGraph.subgraph_search` -- Find a subgraph + isomorphic to ``other`` inside of the graph. - - :meth:`~GenericGraph.subgraph_search_count` -- Counts the number + - :meth:`~GenericGraph.subgraph_search_count` -- Count the number of such copies. - - :meth:`~GenericGraph.subgraph_search_iterator` -- Iterate over all - the copies of `G` contained in `self`. + - :meth:`~GenericGraph.subgraph_search_iterator` -- + Iterate over all the copies of ``other`` contained in the graph. EXAMPLES:: @@ -12347,8 +12593,8 @@ def is_subgraph(self, other, induced=True): sage: G.is_subgraph(P) True - sage: H=graphs.CycleGraph(5) - sage: G=graphs.PathGraph(5) + sage: H = graphs.CycleGraph(5) + sage: G = graphs.PathGraph(5) sage: G.is_subgraph(H) False sage: G.is_subgraph(H, induced=False) @@ -12368,6 +12614,7 @@ def is_subgraph(self, other, induced=True): Traceback (most recent call last): ... ValueError: The input parameter must be a DiGraph. + """ from sage.graphs.graph import Graph from sage.graphs.digraph import DiGraph @@ -12667,11 +12914,17 @@ def distance(self, u, v, by_weight=False): Returns the (directed) distance from u to v in the (di)graph, i.e. the length of the shortest path from u to v. + This method simply calls + :meth:`~GenericGraph.shortest_path_length`, + with default arguments. For more information, and for more option, we + refer to that method. + INPUT: - - ``by_weight`` - if ``False``, uses a breadth first - search. If True, takes edge weightings into account, using - Dijkstra's algorithm. + - ``by_weight`` - if ``False``, the graph is considered unweighted, and + the distance is the number of edges in a shortest path. If ``True``, + the distance is the sum of edge labels (which are assumed to be + numbers). EXAMPLES:: @@ -12682,38 +12935,62 @@ def distance(self, u, v, by_weight=False): 4 sage: G.distance(0,5) 4 - sage: G = Graph( {0:[], 1:[]} ) + sage: G = Graph({0:[], 1:[]}) sage: G.distance(0,1) +Infinity - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True) + sage: G = Graph({ 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) sage: G.plot(edge_labels=True).show() # long time sage: G.distance(0, 3) 2 sage: G.distance(0, 3, by_weight=True) 3 - """ return self.shortest_path_length(u, v, by_weight = by_weight) - def distance_all_pairs(self, algorithm = "auto"): + def distance_all_pairs(self, by_weight=False, algorithm=None, + weight_function=None, check_weight=True): r""" Returns the distances between all pairs of vertices. INPUT: - - ``"algorithm"`` (string) -- two algorithms are available + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - the Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. - * ``algorithm = "BFS"`` in which case the distances are computed - through `n` different breadth-first-search. + - ``'Floyd-Warshall-Python'`` - the Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). - * ``algorithm = "Floyd-Warshall"``, in which case the - Floyd-Warshall algorithm is used. + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. - * ``algorithm = "auto"``, in which case the Floyd-Warshall - algorithm is used for graphs on less than 20 vertices, and BFS - otherwise. + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). - The default is ``algorithm = "BFS"``. + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Floyd-Warshall-Cython'`` otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. OUTPUT: @@ -12727,7 +13004,13 @@ def distance_all_pairs(self, algorithm = "auto"): dictionary. Everything on the subject is to be found in the :mod:`~sage.graphs.distances_all_pairs` module. - EXAMPLE: + .. NOTE:: + + This algorithm simply calls + :meth:`GenericGraph.shortest_path_all_pairs`, and we suggest to look + at that method for more information and examples. + + EXAMPLES: The Petersen Graph:: @@ -12745,47 +13028,80 @@ def distance_all_pairs(self, algorithm = "auto"): .. SEEALSO:: * :meth:`~sage.graphs.generic_graph.GenericGraph.distance_matrix` + * :meth:`~sage.graphs.generic_graph.GenericGraph.shortest_path_all_pairs` """ - if algorithm == "auto": - if self.order() <= 20: - algorithm = "Floyd-Warshall" - else: - algorithm = "BFS" - - if algorithm == "BFS": - from sage.graphs.distances_all_pairs import distances_all_pairs - return distances_all_pairs(self) + return self.shortest_path_all_pairs(by_weight=by_weight, + algorithm=algorithm, + weight_function=weight_function, + check_weight=check_weight)[0] - elif algorithm == "Floyd-Warshall": - from sage.graphs.distances_all_pairs import floyd_warshall - return floyd_warshall(self,paths = False, distances = True) - - else: - raise ValueError("The algorithm keyword can be equal to either \"BFS\" or \"Floyd-Warshall\" or \"auto\"") - - - def eccentricity(self, v=None, dist_dict=None, with_labels=False): + def eccentricity(self, v=None, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, dist_dict=None, + with_labels=False): """ Return the eccentricity of vertex (or vertices) v. The eccentricity of a vertex is the maximum distance to any other vertex. + For more information and examples on how to use input variables, see + :meth:`~GenericGraph.shortest_paths` + INPUT: + - ``v`` - either a single vertex or a list of vertices. If it is not + specified, then it is taken to be all vertices. - - ``v`` - either a single vertex or a list of - vertices. If it is not specified, then it is taken to be all - vertices. + - ``by_weight`` - if ``True``, edge weights are taken into account; if + False, all edges have weight 1. - - ``dist_dict`` - optional, a dict of dicts of - distance. + - ``algorithm`` (string) - one of the following algorithms: - - ``with_labels`` - Whether to return a list or a - dict. + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False`` + and ``v is None``. - EXAMPLES:: + - ``'Floyd-Warshall-Python'`` - a Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). However, + ``v`` must be ``None``. + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``'From_Dictionary'``: uses the (already computed) distances, that + are provided by input variable ``dist_dict``. + + - ``None`` (default): Sage chooses the best algorithm: + ``'From_Dictionary'`` if ``dist_dict`` is not None, ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'`` otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + - ``dist_dict`` - used only if ``algorithm=='From_Dictionary'`` - a dict + of dicts of distances. + + - ``with_labels`` - Whether to return a list or a dict. + + EXAMPLES:: sage: G = graphs.KrackhardtKiteGraph() sage: G.eccentricity() @@ -12807,47 +13123,178 @@ def eccentricity(self, v=None, dist_dict=None, with_labels=False): sage: G = Graph({0:[], 1:[]}) sage: G.eccentricity(with_labels=True) {0: +Infinity, 1: +Infinity} + sage: G = Graph([(0,1,1), (1,2,1), (0,2,3)]) + sage: G.eccentricity(algorithm = 'BFS') + [1, 1, 1] + sage: G.eccentricity(algorithm = 'Floyd-Warshall-Cython') + [1, 1, 1] + sage: G.eccentricity(by_weight = True, algorithm = 'Dijkstra_NetworkX') + [2, 1, 2] + sage: G.eccentricity(by_weight = True, algorithm = 'Dijkstra_Boost') + [2, 1, 2] + sage: G.eccentricity(by_weight = True, algorithm = 'Johnson_Boost') + [2, 1, 2] + sage: G.eccentricity(by_weight = True, algorithm = 'Floyd-Warshall-Python') + [2, 1, 2] + sage: G.eccentricity(dist_dict = G.shortest_path_all_pairs(by_weight = True)[0]) + [2, 1, 2] + + TESTS: + + A non-implemented algorithm:: + + sage: G.eccentricity(algorithm = 'boh') + Traceback (most recent call last): + ... + ValueError: Algorithm boh not yet implemented. Please, contribute! + + An algorithm that does not work with edge weights:: + + sage: G.eccentricity(by_weight = True, algorithm = 'BFS') + Traceback (most recent call last): + ... + ValueError: Algorithm 'BFS' does not work with weights. + sage: G.eccentricity(by_weight = True, algorithm = 'Floyd-Warshall-Cython') + Traceback (most recent call last): + ... + ValueError: Algorithm 'Floyd-Warshall-Cython' does not work with weights. + + An algorithm that computes the all-pair-shortest-paths when not all + vertices are needed:: + + sage: G.eccentricity(0, algorithm = 'Floyd-Warshall-Cython') + Traceback (most recent call last): + ... + ValueError: Algorithm 'Floyd-Warshall-Cython' works only if all eccentricities are needed. + sage: G.eccentricity(0, algorithm = 'Floyd-Warshall-Python') + Traceback (most recent call last): + ... + ValueError: Algorithm 'Floyd-Warshall-Python' works only if all eccentricities are needed. + sage: G.eccentricity(0, algorithm = 'Johnson_Boost') + Traceback (most recent call last): + ... + ValueError: Algorithm 'Johnson_Boost' works only if all eccentricities are needed. """ + if weight_function is not None: + by_weight = True + elif by_weight: + weight_function = lambda e:e[2] + + if algorithm is None: + if dist_dict is not None: + algorithm='From_Dictionary' + elif not by_weight: + algorithm='BFS' + else: + for e in self.edge_iterator(): + try: + if float(weight_function(e)) < 0: + algorithm='Johnson_Boost' + break + except (ValueError, TypeError): + raise ValueError("The weight function cannot find the" + + " weight of " + str(e) + ".") + if algorithm is None: + algorithm='Dijkstra_Boost' + if v is None: - if dist_dict is None: + # If we want to use BFS, we use the Cython routine + if algorithm=='BFS': + if by_weight: + raise ValueError("Algorithm 'BFS' does not work with weights.") from sage.graphs.distances_all_pairs import eccentricity if with_labels: return dict(zip(self.vertices(), eccentricity(self))) else: return eccentricity(self, method='standard' if self.is_directed() else 'bounds') - + if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: + dist_dict = self.shortest_path_all_pairs(by_weight, algorithm, + weight_function, + check_weight)[0] + algorithm='From_Dictionary' v = self.vertices() - elif not isinstance(v, list): + elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: + raise ValueError("Algorithm '" + algorithm + "' works only if all" + + " eccentricities are needed.") + + if not isinstance(v, list): v = [v] e = {} - infinite = False + + from sage.rings.infinity import Infinity + for u in v: - if dist_dict is None: - length = self.shortest_path_lengths(u) - else: + if algorithm=='From_Dictionary': length = dist_dict[u] + else: + # If algorithm is wrong, the error is raised by the + # shortest_path_lengths function + length = self.shortest_path_lengths(u, by_weight=by_weight, + algorithm=algorithm, weight_function=weight_function, + check_weight=check_weight) + if len(length) != self.num_verts(): - infinite = True - break - e[u] = max(length.values()) - if infinite: - from sage.rings.infinity import Infinity - for u in v: e[u] = Infinity + else: + e[u] = max(length.values()) + if with_labels: return e else: if len(e)==1: return e.values()[0] # return single value return e.values() - def radius(self): - """ + def radius(self, by_weight=False, algorithm=None, weight_function=None, + check_weight=True): + r""" Returns the radius of the (di)graph. The radius is defined to be the minimum eccentricity of any vertex, where the eccentricity is the maximum distance to any other - vertex. + vertex. For more information and examples on how to use input variables, + see :meth:`~GenericGraph.shortest_paths` and + :meth:`~GenericGraph.eccentricity` + + INPUT: + + - ``by_weight`` - if ``True``, edge weights are taken into account; if + False, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - a Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. EXAMPLES: The more symmetric a graph is, the smaller (diameter - radius) is. @@ -12879,16 +13326,177 @@ def radius(self): if self.order() == 0: raise ValueError("This method has no meaning on empty graphs.") - return min(self.eccentricity()) + return min(self.eccentricity(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight, + algorithm=algorithm)) + + def diameter(self, by_weight=False, algorithm = None, weight_function=None, + check_weight=True): + r""" + Returns the diameter of the (di)graph. + + The diameter is defined to be the maximum distance between two vertices. + It is infinite if the (di)graph is not (strongly) connected. + + For more information and examples on how to use input variables, + see :meth:`~GenericGraph.shortest_paths` and + :meth:`~GenericGraph.eccentricity` + + INPUT: + + - ``by_weight`` - if ``True``, edge weights are taken into account; if + False, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - a Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'standard'``, ``'2sweep'``, ``'multi-sweep'``, ``'iFUB'``: these + algorithms are implemented in + :func:`sage.graphs.distances_all_pairs.diameter` + They work only if ``by_weight==False``. See the function + documentation for more information. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'iFUB'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + EXAMPLES: The more symmetric a graph is, the smaller (diameter - + radius) is. + + :: + + sage: G = graphs.BarbellGraph(9, 3) + sage: G.radius() + 3 + sage: G.diameter() + 6 - def center(self): + :: + + sage: G = graphs.OctahedralGraph() + sage: G.radius() + 2 + sage: G.diameter() + 2 + + TEST:: + + sage: g = Graph() + sage: g.diameter() + Traceback (most recent call last): + ... + ValueError: This method has no meaning on empty graphs. + sage: g = Graph([(1,2,{'weight':1})]) + sage: g.diameter(algorithm='iFUB', weight_function=lambda e:e[2]['weight']) + Traceback (most recent call last): + ... + ValueError: Algorithm 'iFUB' does not work on weighted graphs. """ + if weight_function is not None: + by_weight = True + + if self.order() == 0: + raise ValueError("This method has no meaning on empty graphs.") + + if algorithm==None and not by_weight: + algorithm = 'iFUB' + elif algorithm=='BFS': + algorithm = 'standard' + + if algorithm in ['standard', '2sweep', 'multi-sweep', 'iFUB']: + if by_weight: + raise ValueError("Algorithm '" + algorithm + "' does not work" + + " on weighted graphs.") + from distances_all_pairs import diameter + return diameter(self, method=algorithm) + + return max(self.eccentricity(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight, + algorithm=algorithm)) + + def center(self, by_weight=False, algorithm=None, weight_function=None, + check_weight=True): + r""" Returns the set of vertices in the center, i.e. whose eccentricity is equal to the radius of the (di)graph. In other words, the center is the set of vertices achieving the minimum eccentricity. + For more information and examples on how to use input variables, + see :meth:`~GenericGraph.shortest_paths` and + :meth:`~GenericGraph.eccentricity` + + INPUT: + + - ``by_weight`` - if ``True``, edge weights are taken into account; if + False, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - a Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + EXAMPLES:: sage: G = graphs.DiamondGraph() @@ -12908,7 +13516,11 @@ def center(self): sage: G.center() [0] """ - e = self.eccentricity(with_labels=True) + e = self.eccentricity(by_weight=by_weight, + weight_function=weight_function, + algorithm=algorithm, + check_weight=check_weight, + with_labels=True) try: r = min(e.values()) except Exception: @@ -12916,7 +13528,6 @@ def center(self): return [v for v in e if e[v]==r] - def distance_graph(self, dist): r""" Returns the graph on the same vertex set as @@ -13079,7 +13690,7 @@ def distance_graph(self, dist): d = self.distance_all_pairs() for u in self.vertex_iterator(): for v in self.vertex_iterator(): - if d[u][v] in distances: + if d[u].get(v, Infinity) in distances: D.add_edge(u,v) return D @@ -13193,15 +13804,59 @@ def girth(self): return best - - def periphery(self): - """ + def periphery(self, by_weight=False, algorithm=None, weight_function=None, + check_weight=True): + r""" Returns the set of vertices in the periphery, i.e. whose eccentricity is equal to the diameter of the (di)graph. In other words, the periphery is the set of vertices achieving the maximum eccentricity. + For more information and examples on how to use input variables, + see :meth:`~GenericGraph.shortest_paths` and + :meth:`~GenericGraph.eccentricity` + + INPUT: + + - ``by_weight`` - if ``True``, edge weights are taken into account; if + False, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - a Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + EXAMPLES:: sage: G = graphs.DiamondGraph() @@ -13221,7 +13876,11 @@ def periphery(self): sage: G.periphery() [0] """ - e = self.eccentricity(with_labels=True) + e = self.eccentricity(by_weight=by_weight, + weight_function=weight_function, + algorithm=algorithm, + check_weight=check_weight, + with_labels=True) try: r = max(e.values()) except Exception: @@ -13271,7 +13930,7 @@ def centrality_betweenness(self, k=None, normalized=True, weight=None, .. SEEALSO:: - :meth:`~sage.graphs.graph.Graph.centrality_degree` - - :meth:`~sage.graphs.graph.Graph.centrality_closeness` + - :meth:`~centrality_closeness` EXAMPLES:: @@ -13334,97 +13993,304 @@ def centrality_betweenness(self, k=None, normalized=True, weight=None, raise ValueError("'algorithm' can be \"NetworkX\", \"Sage\" or None") - ### Paths + def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, + weight_function=None, check_weight=True): + r""" + Returns the closeness centrality of all vertices in variable ``vert``. - def interior_paths(self, start, end): - """ - Returns an exhaustive list of paths (also lists) through only - interior vertices from vertex start to vertex end in the - (di)graph. + In a (strongly) connected graph, the closeness centrality of a vertex + `v` is equal + to the inverse of the average distance between `v` and other vertices. + If the graph is disconnected, the closeness centrality of `v` is + multiplied by the fraction of reachable vertices in the graph: + this way, central vertices should also reach several other vertices + in the graph [OLJ14]_. In formulas, - Note - start and end do not necessarily have to be boundary - vertices. + .. MATH:: + + c(v)=\frac{r(v)-1}{\sum_{w \in R(v)} d(v,w)}\frac{r(v)-1}{n-1} + + where `R(v)` is the set of vertices reachable from `v`, and + `r(v)` is the cardinality of `R(v)`. + + 'Closeness + centrality may be defined as the total graph-theoretic distance of + a given vertex from all other vertices... Closeness is an inverse + measure of centrality in that a larger value indicates a less + central actor while a smaller value indicates a more central + actor,' [Borgatti95]_. + + For more information, see the :wikipedia:`Centrality`. INPUT: + - ``vert`` - the vertex or the list of vertices we want to analyze. If + ``None`` (default), all vertices are considered. - - ``start`` - the vertex of the graph to search for - paths from + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. - - ``end`` - the vertex of the graph to search for - paths to + - ``algorithm`` (string) - one of the following algorithms: + - ``'BFS'``: performs a BFS from each vertex that has to be analyzed. + Does not work with edge weights. - EXAMPLES:: + - ``'NetworkX'``: the NetworkX algorithm (works only with positive + weights). - sage: eg1 = Graph({0:[1,2], 1:[4], 2:[3,4], 4:[5], 5:[6]}) - sage: sorted(eg1.all_paths(0,6)) - [[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]] - sage: eg2 = copy(eg1) - sage: eg2.set_boundary([0,1,3]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - sage: sorted(eg2.interior_paths(0,6)) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. - [[0, 2, 4, 5, 6]] - sage: sorted(eg2.all_paths(0,6)) - [[0, 1, 4, 5, 6], [0, 2, 4, 5, 6]] - sage: eg3 = graphs.PetersenGraph() - sage: eg3.set_boundary([0,1,2,3,4]) - sage: sorted(eg3.all_paths(1,4)) - [[1, 0, 4], - [1, 0, 5, 7, 2, 3, 4], - [1, 0, 5, 7, 2, 3, 8, 6, 9, 4], - [1, 0, 5, 7, 9, 4], - [1, 0, 5, 7, 9, 6, 8, 3, 4], - [1, 0, 5, 8, 3, 2, 7, 9, 4], - [1, 0, 5, 8, 3, 4], - [1, 0, 5, 8, 6, 9, 4], - [1, 0, 5, 8, 6, 9, 7, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 8, 5, 0, 4], - [1, 2, 3, 8, 5, 7, 9, 4], - [1, 2, 3, 8, 6, 9, 4], - [1, 2, 3, 8, 6, 9, 7, 5, 0, 4], - [1, 2, 7, 5, 0, 4], - [1, 2, 7, 5, 8, 3, 4], - [1, 2, 7, 5, 8, 6, 9, 4], - [1, 2, 7, 9, 4], - [1, 2, 7, 9, 6, 8, 3, 4], - [1, 2, 7, 9, 6, 8, 5, 0, 4], - [1, 6, 8, 3, 2, 7, 5, 0, 4], - [1, 6, 8, 3, 2, 7, 9, 4], - [1, 6, 8, 3, 4], - [1, 6, 8, 5, 0, 4], - [1, 6, 8, 5, 7, 2, 3, 4], - [1, 6, 8, 5, 7, 9, 4], - [1, 6, 9, 4], - [1, 6, 9, 7, 2, 3, 4], - [1, 6, 9, 7, 2, 3, 8, 5, 0, 4], - [1, 6, 9, 7, 5, 0, 4], - [1, 6, 9, 7, 5, 8, 3, 4]] - sage: sorted(eg3.interior_paths(1,4)) - [[1, 6, 8, 5, 7, 9, 4], [1, 6, 9, 4]] - sage: dg = DiGraph({0:[1,3,4], 1:[3], 2:[0,3,4],4:[3]}, boundary=[4]) - sage: sorted(dg.all_paths(0,3)) - [[0, 1, 3], [0, 3], [0, 4, 3]] - sage: sorted(dg.interior_paths(0,3)) - [[0, 1, 3], [0, 3]] - sage: ug = dg.to_undirected() - sage: sorted(ug.all_paths(0,3)) - [[0, 1, 3], [0, 2, 3], [0, 2, 4, 3], [0, 3], [0, 4, 2, 3], [0, 4, 3]] - sage: sorted(ug.interior_paths(0,3)) - [[0, 1, 3], [0, 2, 3], [0, 3]] - """ - from sage.misc.superseded import deprecation - deprecation(15494, "The boundary parameter is deprecated and will soon disappear.") + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). - H = copy(self) - for vertex in self.get_boundary(): - if (vertex != start and vertex != end): - H.delete_edges(H.edges_incident(vertex)) - return H.all_paths(start, end) + - ``'Floyd-Warshall-Cython'`` - the Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False`` and + all centralities are needed. + + - ``'Floyd-Warshall-Python'`` - the Python implementation of + the Floyd-Warshall algorithm. Works only if all centralities are + needed, but it can deal with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'`` otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + OUTPUT: + + If ``vert`` is a vertex, the closeness centrality of that vertex. + Otherwise, a dictionary associating to each vertex in ``vert`` its + closeness centrality. If a vertex has (out)degree 0, its closeness + centrality is not defined, and the vertex is not included in the output. + + .. SEEALSO:: + + - :func:`~sage.graphs.centrality.centrality_closeness_top_k` + - :meth:`~sage.graphs.graph.Graph.centrality_degree` + - :meth:`~centrality_betweenness` + + REFERENCES: + + .. [Borgatti95] Stephen P. Borgatti. (1995). Centrality and AIDS. + [Online] Available: + http://www.analytictech.com/networks/centaids.htm + + .. [OLJ14] Paul W. Olsen, Alan G. Labouseur, Jeong-Hyon Hwang. + Efficient Top-k Closeness Centrality Search + Proceedings of the IEEE 30th International Conference on Data + Engineering (ICDE), 2014 + + EXAMPLES: + + Standard examples:: + + sage: (graphs.ChvatalGraph()).centrality_closeness() + {0: 0.61111111111111..., 1: 0.61111111111111..., 2: 0.61111111111111..., 3: 0.61111111111111..., 4: 0.61111111111111..., 5: 0.61111111111111..., 6: 0.61111111111111..., 7: 0.61111111111111..., 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...} + sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) + sage: D.show(figsize=[2,2]) + sage: D.centrality_closeness(vert=[0,1]) + {0: 1.0, 1: 0.3333333333333333} + sage: D = D.to_undirected() + sage: D.show(figsize=[2,2]) + sage: D.centrality_closeness() + {0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75} + + In a (strongly) connected (di)graph, the closeness centrality of `v` + is inverse of the average distance between `v` and all other vertices:: + + sage: g = graphs.PathGraph(5) + sage: g.centrality_closeness(0) + 0.4 + sage: dist = g.shortest_path_lengths(0).values() + sage: float(len(dist)-1) / sum(dist) + 0.4 + sage: d = g.to_directed() + sage: d.centrality_closeness(0) + 0.4 + sage: dist = d.shortest_path_lengths(0).values() + sage: float(len(dist)-1) / sum(dist) + 0.4 + + If a vertex has (out)degree 0, its closeness centrality is not defined:: + + sage: g = Graph(5) + sage: g.centrality_closeness() + {} + sage: print g.centrality_closeness(0) + None + + Weighted graphs:: + + sage: D = graphs.GridGraph([2,2]) + sage: weight_function = lambda e:10 + sage: D.centrality_closeness([(0,0),(0,1)]) # tol abs 1e-12 + {(0, 0): 0.75, (0, 1): 0.75} + sage: D.centrality_closeness((0,0), weight_function=weight_function) # tol abs 1e-12 + 0.075 + + TESTS: + + The result does not depend on the algorithm:: + + sage: import random + sage: import itertools + sage: for i in range(10): # long time + ....: n = random.randint(2,20) + ....: m = random.randint(0, n*(n-1)/2) + ....: g = graphs.RandomGNM(n,m) + ....: c1 = g.centrality_closeness(algorithm='BFS') + ....: c2 = g.centrality_closeness(algorithm='NetworkX') + ....: c3 = g.centrality_closeness(algorithm='Dijkstra_Boost') + ....: c4 = g.centrality_closeness(algorithm='Floyd-Warshall-Cython') + ....: c5 = g.centrality_closeness(algorithm='Floyd-Warshall-Python') + ....: c6 = g.centrality_closeness(algorithm='Johnson_Boost') + ....: assert(len(c1)==len(c2)==len(c3)==len(c4)==len(c5)==len(c6)) + ....: c = [c1,c2,c3,c4,c5,c6] + ....: for (ci,cj) in itertools.combinations(c, 2): + ....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.degree(v) != 0]) < 1e-12) + + Directed graphs:: + + sage: import random + sage: import itertools + sage: for i in range(10): # long time + ....: n = random.randint(2,20) + ....: m = random.randint(0, n*(n-1)/2) + ....: g = digraphs.RandomDirectedGNM(n,m) + ....: c1 = g.centrality_closeness(algorithm='BFS') + ....: c2 = g.centrality_closeness(algorithm='NetworkX') + ....: c3 = g.centrality_closeness(algorithm='Dijkstra_Boost') + ....: c4 = g.centrality_closeness(algorithm='Floyd-Warshall-Cython') + ....: c5 = g.centrality_closeness(algorithm='Floyd-Warshall-Python') + ....: c6 = g.centrality_closeness(algorithm='Johnson_Boost') + ....: assert(len(c1)==len(c2)==len(c3)==len(c4)==len(c5)==len(c6)) + ....: c = [c1,c2,c3,c4,c5,c6] + ....: for (ci,cj) in itertools.combinations(c, 2): + ....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.out_degree(v) != 0]) < 1e-12) + + Weighted graphs:: + + sage: import random + sage: import itertools + sage: for i in range(10): # long time + ....: n = random.randint(2,20) + ....: m = random.randint(0, n*(n-1)/2) + ....: g = graphs.RandomGNM(n,m) + ....: for v,w in g.edges(labels=False): + ....: g.set_edge_label(v,w,float(random.uniform(1,100))) + ....: c1 = g.centrality_closeness(by_weight=True, algorithm='NetworkX') + ....: c2 = g.centrality_closeness(by_weight=True, algorithm='Dijkstra_Boost') + ....: c3 = g.centrality_closeness(by_weight=True, algorithm='Floyd-Warshall-Python') + ....: c4 = g.centrality_closeness(by_weight=True, algorithm='Johnson_Boost') + ....: assert(len(c1)==len(c2)==len(c3)==len(c4)) + ....: c = [c1,c2,c3,c4] + ....: for (ci,cj) in itertools.combinations(c, 2): + ....: assert(sum([abs(ci[v] - cj[v]) for v in g.vertices() if g.degree(v) != 0]) < 1e-12) + """ + if weight_function is not None: + by_weight=True + elif by_weight: + weight_function = lambda e:e[2] + + onlyone = False + if vert in self.vertices(): + v_iter = iter([vert]) + onlyone = True + elif vert is None: + v_iter = self.vertex_iterator() + else: + v_iter = iter(vert) + + if algorithm is None: + if not by_weight: + algorithm='BFS' + else: + for e in self.edge_iterator(): + try: + if float(weight_function(e)) < 0: + algorithm='Johnson_Boost' + break + except (ValueError, TypeError): + raise ValueError("The weight function cannot find the" + + " weight of " + str(e) + ".") + if algorithm is None: + algorithm='Dijkstra_Boost' + + if algorithm == 'NetworkX': + if by_weight and check_weight: + self._check_weight_function(weight_function) + import networkx + if by_weight: + if self.is_directed(): + G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) + else: + G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) + else: + G = self.networkx_graph(copy=False) + G.add_nodes_from(self.vertices()) + + if vert is None: + closeness = networkx.closeness_centrality(G,vert, + distance = 'weight' + if by_weight + else None) + return {v:c for v,c in closeness.iteritems() if c != 0} + closeness = {} + degree = self.out_degree if self.is_directed else self.degree + for x in v_iter: + if degree(x) != 0: + closeness[x] = networkx.closeness_centrality(G, x, + distance = 'weight' + if by_weight + else None) + if onlyone: + return closeness.get(vert, None) + else: + return closeness + elif algorithm=="Johnson_Boost": + from sage.graphs.base.boost_graph import johnson_closeness_centrality + self.weighted(by_weight) + closeness = johnson_closeness_centrality(self, weight_function) + if onlyone: + return closeness.get(vert, None) + else: + return {v: closeness[v] for v in v_iter if v in closeness} + else: + closeness = dict() + distances = None + if algorithm in ["Floyd-Warshall-Cython", + "Floyd-Warshall-Python"]: + distances = self.shortest_path_all_pairs(by_weight,algorithm, + weight_function, + check_weight)[0] + + for v in v_iter: + if distances is None: + distv = self.shortest_path_lengths(v, by_weight, algorithm, + weight_function, + check_weight) + else: + distv = distances[v] + try: + closeness[v] = float(len(distv) - 1) * (len(distv) - 1) / (float(sum(distv.values())) * (self.num_verts() - 1)) + except ZeroDivisionError: + pass + if onlyone: + return closeness.get(vert, None) + else: + return closeness + + ### Paths def all_paths(self, start, end): """ @@ -13629,267 +14495,783 @@ def triangles_count(self, algorithm=None): else: raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) - def shortest_path(self, u, v, by_weight=False, bidirectional=True): - """ + def shortest_path(self, u, v, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, + bidirectional=None): + r""" Returns a list of vertices representing some shortest path from u to v: if there is no path from u to v, the list is empty. + For more information and more examples, see + :meth:`~GenericGraph.shortest_paths` (the inputs are very similar). + INPUT: + - ``u``, ``v`` (vertices) - the start and the end vertices of the paths. + + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'``: performs a BFS from ``u``. Does not work with edge + weights. + + - ``'BFS_Bid``: performs a BFS from ``u`` and from ``v``. Does not + work with edge weights. + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. Works only with positive weights. + + - ``'Dijkstra_Bid_NetworkX'``: performs a Dijkstra visit from ``u`` + and from ``v`` (NetworkX implementation). Works only with positive + weights. + + - ``'Dijkstra_Bid'``: a Cython implementation that performs + a Dijkstra visit from ``u`` and from ``v``. Works only with positive + weights. - - ``by_weight`` - if False, uses a breadth first - search. If True, takes edge weightings into account, using - Dijkstra's algorithm. + - ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in + Boost. Works also with negative weights, if there is no negative + cycle. - - ``bidirectional`` - if True, the algorithm will - expand vertices from u and v at the same time, making two spheres - of half the usual radius. This generally doubles the speed - (consider the total volume in each case). + - ``None`` (default): Sage chooses the best algorithm: ``'BFS_Bid'`` + if ``by_weight`` is ``False``, ``'Dijkstra_Bid'`` otherwise. + .. NOTE:: + + If there are negative weights and algorithm is ``None``, the + result is not reliable. This occurs because, for performance + reasons, we cannot check whether there are edges with negative + weights before running the algorithm. If there are, the user + should explicitly input ``algorithm='Bellman-Ford_Boost'``. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + - ``bidirectional`` - Deprecated and replaced by Algorithm: now it has + no effect. Before, if it was True, the algorithm would expand vertices + from ``u`` and ``v`` at the same time, making two spheres of half the + usual radius. EXAMPLES:: sage: D = graphs.DodecahedralGraph() sage: D.shortest_path(4, 9) [4, 17, 16, 12, 13, 9] + sage: D.shortest_path(4, 9, algorithm='BFS') + [4, 3, 2, 1, 8, 9] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') + [4, 3, 2, 1, 8] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') + [4, 3, 2, 1, 8] + sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') + [4, 3, 19, 0, 10, 9] sage: D.shortest_path(5, 5) [5] sage: D.delete_edges(D.edges_incident(13)) sage: D.shortest_path(13, 4) [] - sage: G = Graph( { 0: [1], 1: [2], 2: [3], 3: [4], 4: [0] }) + sage: G = Graph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) sage: G.plot(edge_labels=True).show() # long time sage: G.shortest_path(0, 3) [0, 4, 3] - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True) sage: G.shortest_path(0, 3, by_weight=True) [0, 1, 2, 3] + sage: G.shortest_path(0, 3, by_weight=True, algorithm='Dijkstra_NetworkX') + [0, 1, 2, 3] + sage: G.shortest_path(0, 3, by_weight=True, algorithm='Dijkstra_Bid_NetworkX') + [0, 1, 2, 3] + + TESTS: + + If the algorithm is not implemented:: + + sage: G.shortest_path(0, 3, by_weight=True, algorithm='tip top') + Traceback (most recent call last): + ... + ValueError: Algorithm 'tip top' not yet implemented. + + BFS on weighted graphs:: + + sage: G.shortest_path(0, 3, by_weight=True, algorithm='BFS') + Traceback (most recent call last): + ... + ValueError: The 'BFS' algorithm does not work on weighted graphs. + sage: G.shortest_path(0, 3, by_weight=True, algorithm='BFS_Bid') + Traceback (most recent call last): + ... + ValueError: The 'BFS_Bid' algorithm does not work on weighted graphs. """ # TODO- multiple edges?? + if weight_function is not None: + by_weight = True + + if algorithm is None: + algorithm = 'Dijkstra_Bid' if by_weight else 'BFS_Bid' + + if algorithm in ['BFS', 'Dijkstra_NetworkX', 'Bellman-Ford_Boost']: + return self.shortest_paths(u, by_weight, algorithm, weight_function, check_weight)[v] + + if weight_function is None and by_weight: + weight_function = lambda e:e[2] + + if bidirectional is not None: + deprecation(18938, "Variable 'bidirectional' is deprecated and " + + "replaced by 'algorithm'.") + if u == v: # to avoid a NetworkX bug return [u] - import networkx + + if by_weight: - if bidirectional: - try: - L = self._backend.bidirectional_dijkstra(u,v) - except AttributeError: - try: - L = networkx.bidirectional_dijkstra(self.networkx_graph(copy=False), u, v)[1] - except Exception: - L = False + if algorithm == 'BFS_Bid': + raise ValueError("The 'BFS_Bid' algorithm does not " + + "work on weighted graphs.") + if check_weight: + self._check_weight_function(weight_function) + else: + weight_function = lambda e:1 + + if algorithm=="Dijkstra_Bid": + return self._backend.bidirectional_dijkstra(u, v, weight_function) + elif algorithm=="Dijkstra_Bid_NetworkX": + import networkx + if self.is_directed(): + G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) else: - L = networkx.dijkstra_path(self.networkx_graph(copy=False), u, v) + G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) + G.add_nodes_from(self.vertices()) + return networkx.bidirectional_dijkstra(G, u, v)[1] + elif algorithm=="BFS_Bid": + return self._backend.shortest_path(u,v) else: - if bidirectional: - # If the graph is a C_graph, use shortest_path from its backend ! - try: - L = self._backend.shortest_path(u,v) - except AttributeError: - L = networkx.shortest_path(self.networkx_graph(copy=False), u, v) + raise ValueError("Algorithm '" + algorithm + "' not yet implemented.") + + def shortest_path_length(self, u, v, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, + bidirectional=None, weight_sum=None): + r""" + Returns the minimal length of a path from u to v. + + If there is no path from u to v, returns Infinity. + + For more information and more examples, we refer to + :meth:`~GenericGraph.shortest_path` and + :meth:`~GenericGraph.shortest_paths`, which have very similar inputs. + + INPUT: + + - ``u``, ``v`` (vertices) - the start and the end vertices of the paths. + + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'``: performs a BFS from ``u``. Does not work with edge + weights. + + - ``'BFS_Bid``: performs a BFS from ``u`` and from ``v``. Does not + work with edge weights. + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. + + - ``'Dijkstra_Bid_NetworkX'``: performs a Dijkstra visit from ``u`` + and from ``v`` (NetworkX implementation). + + - ``'Dijkstra_Bid'``: a Cython implementation that performs + a Dijkstra visit from ``u`` and from ``v``. + + - ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in + Boost. Works also with negative weights, if there is no negative + cycle. + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS_Bid'`` + if ``by_weight`` is ``False``, ``'Dijkstra_Bid'`` otherwise. + + .. NOTE:: + + If there are negative weights and algorithm is ``None``, the + result is not reliable. This occurs because, for performance + reasons, we cannot check whether there are edges with negative + weights before running the algorithm. If there are, the user + should explicitly input ``algorithm='Bellman-Ford_Boost'``. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + - ``bidirectional`` - Deprecated and replaced by Algorithm: now it has + no effect. Before, if it was True, the algorithm would expand vertices + from ``u`` and ``v`` at the same time, making two spheres of half the + usual radius. + + - ``weight_sum`` - Deprecated: now it has no effect. Before, it was used + to decide if the algorithm should return the number of edges in the + shortest path or the length of the (weighted) path. Now it has the + same value as ``by_weight``. + + EXAMPLES: + + Standard examples:: + + sage: D = graphs.DodecahedralGraph() + sage: D.shortest_path_length(4, 9) + 5 + sage: D.shortest_path_length(4, 9, algorithm='BFS') + 5 + sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_NetworkX') + 5 + sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid_NetworkX') + 5 + sage: D.shortest_path_length(4, 9, algorithm='Dijkstra_Bid') + 5 + sage: D.shortest_path_length(4, 9, algorithm='Bellman-Ford_Boost') + 5 + sage: D.shortest_path_length(5, 5) + 0 + sage: D.delete_edges(D.edges_incident(13)) + sage: D.shortest_path_length(13, 4) + +Infinity + sage: G = Graph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse = True) + sage: G.plot(edge_labels=True).show() # long time + sage: G.shortest_path_length(0, 3) + 2 + sage: G.shortest_path_length(0, 3, by_weight=True) + 3 + sage: G.shortest_path_length(0, 3, by_weight=True, algorithm='Dijkstra_NetworkX') + 3 + sage: G.shortest_path_length(0, 3, by_weight=True, algorithm='Dijkstra_Bid_NetworkX') + 3 + + If Dijkstra is used with negative weights, usually it raises an error:: + + sage: G = DiGraph({0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: -2}}, sparse = True) + sage: G.shortest_path_length(4, 1, by_weight=True, algorithm=None) + Traceback (most recent call last): + ... + ValueError: The graph contains an edge with negative weight! + sage: G.shortest_path_length(4, 1, by_weight=True, algorithm='Bellman-Ford_Boost') + -1 + + However, sometimes the result may be wrong, and no error is raised:: + + sage: G = DiGraph([(0,1,1),(1,2,1),(0,3,1000),(3,4,-3000), (4,2,1000)]) + sage: G.shortest_path_length(0, 2, by_weight=True, algorithm='Bellman-Ford_Boost') + -1000 + sage: G.shortest_path_length(0, 2, by_weight=True) + 2 + """ + if weight_sum is not None: + deprecation(18938, "Now weight_sum is replaced by by_weight.") + + path = self.shortest_path(u, v, by_weight=by_weight, + weight_function=weight_function, + algorithm=algorithm, + check_weight=check_weight, + bidirectional=bidirectional) + return self._path_length(path, by_weight, weight_function) + + + def _check_weight_function(self, weight_function=None): + r""" + Check that an edge weight function outputs only numbers. + + The weight function inputs a labelled edge ``(u, v, l)`` and + outputs its weight. Here, we check that the output is always a number + (otherwise, several functions might have unexpected behavior). If the + function fails the test, an exception is raised. + + INPUT: + + - ``weight_function`` - the weight function to be tested + + EXAMPLES: + + The standard weight function outputs labels:: + + sage: G = Graph([(0,1,1), (1,2,3), (2,3,2)]) + sage: weight_function=lambda e:e[2] + sage: G._check_weight_function(weight_function) + sage: [weight_function(e) for e in G.edges()] + [1, 3, 2] + + However, it might be more complicated:: + + sage: G = Graph([(0,1,{'name':'a', 'weight':1}), (1,2,{'name':'b', 'weight':3}), (2,3,{'name':'c', 'weight':2})]) + sage: weight_function=lambda e:e[2]['weight'] + sage: G._check_weight_function(weight_function) + sage: [weight_function(e) for e in G.edges()] + [1, 3, 2] + + A weight function that does not match labels:: + + sage: G.add_edge((0,3,{'name':'d', 'weight':'d'})) + sage: G._check_weight_function(weight_function) + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (0, 3, {'name': 'd', 'weight': 'd'}). + """ + for e in self.edge_iterator(): + try: + float(weight_function(e)) + except Exception: + raise ValueError("The weight function cannot find the " + + "weight of " + str(e) + ".") + + + def shortest_paths(self, u, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, cutoff=None): + r""" + Returns a dictionary associating to each vertex v a shortest path from u + to v, if it exists. + + If u and v are not connected, vertex v is not present in the dictionary. + + INPUT: + + - ``u`` (vertex) - the starting vertex. + + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'``: performs a BFS from ``u``. Does not work with edge + weights. + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX (works only with positive weights). + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Bellman-Ford_Boost'`` otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + - ``cutoff`` (integer) - integer depth to stop search (used only if + ``algorithm=='BFS'``). + + EXAMPLES: + + Standard example:: + + sage: D = graphs.DodecahedralGraph() + sage: D.shortest_paths(0) + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]} + + All these paths are obviously induced graphs:: + + sage: all([D.subgraph(p).is_isomorphic(graphs.PathGraph(len(p)) )for p in D.shortest_paths(0).values()]) + True + + :: + + sage: D.shortest_paths(0, cutoff=2) + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], 19: [0, 19]} + sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) + sage: G.plot(edge_labels=True).show() # long time + sage: G.shortest_paths(0, by_weight=True) + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 4]} + + Weighted shortest paths:: + + sage: D = DiGraph([(0,1,1),(1,2,3),(0,2,5)]) + sage: D.shortest_paths(0) + {0: [0], 1: [0, 1], 2: [0, 2]} + sage: D.shortest_paths(0, by_weight=True) + {0: [0], 1: [0, 1], 2: [0, 1, 2]} + + Using a weight function (this way, ``by_weight`` is set to ``True``):: + + sage: D = DiGraph([(0,1,{'weight':1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) + sage: weight_function = lambda e:e[2]['weight'] + sage: D.shortest_paths(0, weight_function=weight_function) + {0: [0], 1: [0, 1], 2: [0, 1, 2]} + + If the weight function does not match the label:: + + sage: D.shortest_paths(0, weight_function=lambda e:e[2]) + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (0, 1, {'weight': 1}). + + However, if ``check_weight`` is set to False, unexpected behavior may + occur:: + + sage: D.shortest_paths(0, algorithm='Dijkstra_NetworkX', weight_function=lambda e:e[2], check_weight=False) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'int' and 'dict' + + Negative weights:: + + sage: D = DiGraph([(0,1,1),(1,2,-2),(0,2,4)]) + sage: D.shortest_paths(0, by_weight=True) + {0: [0], 1: [0, 1], 2: [0, 1, 2]} + + Negative cycles:: + + sage: D.add_edge(2,0,0) + sage: D.shortest_paths(0, by_weight=True) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + TESTS: + + If we ask for an unknown algorithm:: + + sage: D = DiGraph([(0,1,1),(1,2,2),(0,2,4)]) + sage: D.shortest_paths(0, algorithm='tip top') + Traceback (most recent call last): + ... + ValueError: Algorithm tip top not yet implemented. Please, contribute! + + If we ask for BFS in a weighted graph:: + + sage: D.shortest_paths(0, algorithm='BFS', by_weight=True) + Traceback (most recent call last): + ... + ValueError: The 'BFS' algorithm does not work on weighted graphs. + + If we run Dijkstra with negative weights:: + + sage: D = DiGraph([(0,1,2),(1,2,-2),(0,2,1)]) + sage: D.shortest_paths(0, algorithm='Dijkstra_Boost', by_weight=True) + Traceback (most recent call last): + ... + ValueError: Dijkstra algorithm does not work with negative weights. Please, use Bellman-Ford. + sage: D.shortest_paths(0, algorithm='Dijkstra_NetworkX', by_weight=True) + Traceback (most recent call last): + ... + ValueError: ('Contradictory paths found:', 'negative weights?') + """ + if weight_function is not None: + by_weight = True + elif by_weight: + weight_function = lambda e:e[2] + else: + weight_function = lambda e:1 + + if algorithm is None and not by_weight: + algorithm = 'BFS' + + if by_weight and check_weight: + self._check_weight_function(weight_function) + + if algorithm=='BFS': + if by_weight: + raise ValueError("The 'BFS' algorithm does not work on " + + "weighted graphs.") + return self._backend.shortest_path_all_vertices(u, cutoff) + + elif algorithm=='Dijkstra_NetworkX': + import networkx + # If this is not present, an error might be raised by NetworkX + if self.num_verts()==1 and self.vertices()[0]==u: + return {u:[u]} + if by_weight: + if self.is_directed(): + G = networkx.DiGraph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) + else: + G = networkx.Graph([(e[0], e[1], dict(weight=weight_function(e))) for e in self.edge_iterator()]) else: - try: - L = networkx.single_source_shortest_path(self.networkx_graph(copy=False), u)[v] - except Exception: - L = False - if L: - return L + # Needed to remove labels. + if self.is_directed(): + G = networkx.DiGraph(self.edges(labels=False)) + else: + G = networkx.Graph(self.edges(labels=False)) + G.add_nodes_from(self.vertices()) + return networkx.single_source_dijkstra_path(G, u) + + elif algorithm in ['Dijkstra_Boost','Bellman-Ford_Boost',None]: + from sage.graphs.base.boost_graph import shortest_paths + _,pred = shortest_paths(self, u, weight_function, algorithm) + paths = {} + for v in pred.keys(): + w = v + path = [w] + while w != u: + w = pred[w] + path.append(w) + path.reverse() + paths[v] = path + return paths + else: - return [] + raise ValueError("Algorithm " + algorithm + " not yet " + + "implemented. Please, contribute!") - def shortest_path_length(self, u, v, by_weight=False, - bidirectional=True, - weight_sum=None): - """ - Returns the minimal length of paths from u to v. + def _path_length(self, path, by_weight=False, weight_function=None): + r""" + Computes the (weighted) length of the path provided. - If there is no path from u to v, returns Infinity. + If the path is empty, returns Infinity. + + .. WARNING:: + + if the graph is unweighted, the algorithm does not check that + the path exists. INPUT: - - ``by_weight`` - if False, uses a breadth first - search. If True, takes edge weightings into account, using - Dijkstra's algorithm. + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. - - ``bidirectional`` - if True, the algorithm will - expand vertices from u and v at the same time, making two spheres - of half the usual radius. This generally doubles the speed - (consider the total volume in each case). + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. - - ``weight_sum`` - if False, returns the number of - edges in the path. If True, returns the sum of the weights of these - edges. Default behavior is to have the same value as by_weight. + EXAMPLES: - EXAMPLES:: + The unweighted case:: - sage: D = graphs.DodecahedralGraph() - sage: D.shortest_path_length(4, 9) + sage: G = graphs.CycleGraph(3) + sage: G._path_length([0,1,2,0,1,2]) 5 - sage: D.shortest_path_length(5, 5) - 0 - sage: D.delete_edges(D.edges_incident(13)) - sage: D.shortest_path_length(13, 4) - +Infinity - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse = True) - sage: G.plot(edge_labels=True).show() # long time - sage: G.shortest_path_length(0, 3) - 2 - sage: G.shortest_path_length(0, 3, by_weight=True) + + The weighted case:: + + sage: G = Graph([(0,1,{'name':'a', 'weight':1}), (1,2,{'name':'b', 'weight':3}), (2,3,{'name':'c', 'weight':2})]) + sage: G._path_length([0,1,2,3]) 3 + sage: G._path_length([0,1,2,3], by_weight=True, weight_function=lambda e:e[2]['weight']) + 6 + + If the path is empty:: + + sage: G._path_length([0,1,2,3], by_weight=True, weight_function=lambda e:e[2]['weight']) + 6 + + If we ask for a path that does not exist:: + + sage: G._path_length([0,3], by_weight=False) + 1 + sage: G._path_length([0,3], by_weight=True, weight_function=lambda e:e[2]['weight']) + Traceback (most recent call last): + ... + LookupError: (0, 3) is not an edge of the graph. """ - if weight_sum is None: - weight_sum = by_weight - path = self.shortest_path(u, v, by_weight, bidirectional) - length = len(path) - 1 - if length == -1: + if len(path) == 0: from sage.rings.infinity import Infinity return Infinity - if weight_sum: + + if by_weight or weight_function is not None: + if weight_function is None: + weight_function = lambda e:e[2] wt = 0 - for j in range(length): - wt += self.edge_label(path[j], path[j+1]) + + for j in range(len(path) - 1): + wt += weight_function((path[j], path[j+1], + self.edge_label(path[j], path[j+1]))) return wt else: - return length + return len(path) - 1 - def shortest_paths(self, u, by_weight=False, cutoff=None): - """ - Returns a dictionary associating to each vertex v a shortest path from u - to v, if it exists. + def shortest_path_lengths(self, u, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, + weight_sums=None): + r""" + Computes the length of a shortest path from u to any other vertex. - INPUT: + Returns a dictionary of shortest path lengths keyed by targets, + escluding all vertices that are not reachable from u. - - ``by_weight`` - if False, uses a breadth first - search. If True, uses Dijkstra's algorithm to find the shortest - paths by weight. + For more information on the input variables and more examples, we refer + to :meth:`~GenericGraph.shortest_paths` + which has the same input variables. - - ``cutoff`` - integer depth to stop search. + INPUT: - (ignored if ``by_weight == True``) + - ``u`` (vertex) - the starting vertex. - EXAMPLES:: + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. - sage: D = graphs.DodecahedralGraph() - sage: D.shortest_paths(0) - {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]} + - ``algorithm`` (string) - one of the following algorithms: - All these paths are obviously induced graphs:: + - ``'BFS'``: performs a BFS from ``u``. Does not work with edge + weights. - sage: all([D.subgraph(p).is_isomorphic(graphs.PathGraph(len(p)) )for p in D.shortest_paths(0).values()]) - True + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX (works only with positive weights). - :: + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). - sage: D.shortest_paths(0, cutoff=2) - {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], 19: [0, 19]} - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) - sage: G.plot(edge_labels=True).show() # long time - sage: G.shortest_paths(0, by_weight=True) - {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 4]} - """ + - ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). - if by_weight: - import networkx - return networkx.single_source_dijkstra_path(self.networkx_graph(copy=False), u) - else: - try: - return self._backend.shortest_path_all_vertices(u, cutoff) - except AttributeError: - return networkx.single_source_shortest_path(self.networkx_graph(copy=False), u, cutoff) + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Bellman-Ford_Boost'`` otherwise. - def shortest_path_lengths(self, u, by_weight=False, weight_sums=None): - """ - Returns a dictionary of shortest path lengths keyed by targets that - are connected by a path from u. + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. - INPUT: + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + - ``weight_sums`` - Deprecated: now this variable has no effect. Before, + it was used to decide whether the number of edges or the sum of their + lengths was outputted. Now we use variable ``by_weight`` to decide. - - ``by_weight`` - if False, uses a breadth first - search. If True, takes edge weightings into account, using - Dijkstra's algorithm. + EXAMPLES: - EXAMPLES:: + Unweighted case:: sage: D = graphs.DodecahedralGraph() sage: D.shortest_path_lengths(0) {0: 0, 1: 1, 2: 2, 3: 2, 4: 3, 5: 4, 6: 3, 7: 3, 8: 2, 9: 2, 10: 1, 11: 2, 12: 3, 13: 3, 14: 4, 15: 5, 16: 4, 17: 3, 18: 2, 19: 1} - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True ) + + Weighted case:: + + sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) sage: G.plot(edge_labels=True).show() # long time sage: G.shortest_path_lengths(0, by_weight=True) {0: 0, 1: 1, 2: 2, 3: 3, 4: 2} - """ - paths = self.shortest_paths(u, by_weight) - if by_weight: - weights = {} - for v in paths: - wt = 0 - path = paths[v] - for j in range(len(path) - 1): - wt += self.edge_label(path[j], path[j+1]) - weights[v] = wt - return weights - else: - lengths = {} - for v in paths: - lengths[v] = len(paths[v]) - 1 - return lengths - def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = "auto"): + Using a weight function:: + + sage: D = DiGraph([(0,1,{'weight':1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) + sage: weight_function = lambda e:e[2]['weight'] + sage: D.shortest_path_lengths(1, algorithm='Dijkstra_NetworkX', by_weight=False) + {1: 0, 2: 1} + sage: D.shortest_path_lengths(0, weight_function=weight_function) + {0: 0, 1: 1, 2: 4} + sage: D.shortest_path_lengths(1, weight_function=weight_function) + {1: 0, 2: 3} + + Negative weights:: + + sage: D = DiGraph([(0,1,{'weight':-1}),(1,2,{'weight':3}),(0,2,{'weight':5})]) + sage: D.shortest_path_lengths(0, weight_function=weight_function) + {0: 0, 1: -1, 2: 2} + + Negative cycles:: + + sage: D = DiGraph([(0,1,{'weight':-5}),(1,2,{'weight':3}),(2,0,{'weight':1})]) + sage: D.shortest_path_lengths(0, weight_function=weight_function) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + Checking that distances are equal regardless of the algorithm used:: + + sage: g = graphs.Grid2dGraph(5,5) + sage: d1 = g.shortest_path_lengths((0,0), algorithm="BFS") + sage: d2 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_NetworkX") + sage: d3 = g.shortest_path_lengths((0,0), algorithm="Dijkstra_Boost") + sage: d4 = g.shortest_path_lengths((0,0), algorithm="Bellman-Ford_Boost") + sage: d1 == d2 == d3 == d4 + True """ + if weight_sums is not None: + deprecation(18938, "Now weight_sums is replaced by by_weight.") + + if algorithm in ['Dijkstra_Boost', 'Bellman-Ford_Boost'] or (algorithm is None and weight_function is None and by_weight): + if weight_function is None and not by_weight: + weight_function = lambda e:1 + self.weighted(True) + from sage.graphs.base.boost_graph import shortest_paths + return shortest_paths(self, u, weight_function, algorithm)[0] + + paths = self.shortest_paths(u, by_weight=by_weight, algorithm=algorithm, + weight_function=weight_function) + return {v:self._path_length(p, by_weight, weight_function) + for v,p in paths.iteritems()} + + + def shortest_path_all_pairs(self, by_weight=False, algorithm=None, + weight_function=None, check_weight=True, + default_weight=None): + r""" Computes a shortest path between each pair of vertices. INPUT: + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. - - ``by_weight`` - Whether to use the labels defined over the edges as - weights. If ``False`` (default), the distance between `u` and `v` is - the minimum number of edges of a path from `u` to `v`. + - ``algorithm`` (string) - one of the following algorithms: - - ``default_weight`` - (defaults to 1) The default weight to assign - edges that don't have a weight (i.e., a label). + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. - Implies ``by_weight == True``. + - ``'Floyd-Warshall-Cython'`` - the Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. - - ``algorithm`` -- four options : + - ``'Floyd-Warshall-Python'`` - the Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). - * ``"BFS"`` -- the computation is done through a BFS - centered on each vertex successively. Only implemented - when ``default_weight = 1`` and ``by_weight = False``. + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. - * ``"Floyd-Warshall-Cython"`` -- through the Cython implementation of - the Floyd-Warshall algorithm. + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). - * ``"Floyd-Warshall-Python"`` -- through the Python implementation of - the Floyd-Warshall algorithm. + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). - * ``"auto"`` -- use the fastest algorithm depending on the input - (``"BFS"`` if possible, and ``"Floyd-Warshall-Python"`` otherwise) + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` if + ``by_weight`` is ``False``, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Floyd-Warshall-Cython'`` otherwise. - This is the default value. + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. - OUTPUT: + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. - A tuple ``(dist, pred)``. They are both dicts of dicts. The first - indicates the length ``dist[u][v]`` of the shortest weighted path - from `u` to `v`. The second is a compact representation of all the - paths- it indicates the predecessor ``pred[u][v]`` of `v` in the - shortest path from `u` to `v`. + - ``default_weight`` - Deprecated: now it has no effect. Before, it was + used to assign a weight to edges with no label. Now it has been + replaced by ``weight_function``. - .. NOTE:: + OUTPUT: - Three different implementations are actually available through this method : + A tuple ``(dist, pred)``. They are both dicts of dicts. The first + indicates the length ``dist[u][v]`` of the shortest weighted path + from `u` to `v`. The second is a compact representation of all the + paths - it indicates the predecessor ``pred[u][v]`` of `v` in the + shortest path from `u` to `v`. If the algorithm used is + ``Johnson_Boost``, predecessors are not computed. - * BFS (Cython) - * Floyd-Warshall (Cython) - * Floyd-Warshall (Python) + .. NOTE:: - The BFS algorithm is the fastest of the three, then comes the Cython - implementation of Floyd-Warshall, and last the Python - implementation. The first two implementations, however, only compute - distances based on the topological distance (each edge is of weight - 1, or equivalently the length of a path is its number of - edges). Besides, they do not deal with graphs larger than 65536 - vertices (which already represents 16GB of ram). + Only reachable vertices are present in the dictionaries. .. NOTE:: @@ -13899,9 +15281,12 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = dictionary. Everything on the subject is to be found in the :mod:`~sage.graphs.distances_all_pairs` module. - EXAMPLES:: + EXAMPLES: - sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True ) + Some standard examples (see :meth:`~GenericGraph.shortest_paths` for + more examples on how to use the input variables):: + + sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) sage: G.plot(edge_labels=True).show() # long time sage: dist, pred = G.shortest_path_all_pairs(by_weight = True) sage: dist @@ -13910,6 +15295,12 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} sage: pred[0] {0: None, 1: 0, 2: 1, 3: 2, 4: 0} + sage: G = Graph( { 0: {1: {'weight':1}}, 1: {2: {'weight':1}}, 2: {3: {'weight':1}}, 3: {4: {'weight':2}}, 4: {0: {'weight':2}} }, sparse=True) + sage: dist, pred = G.shortest_path_all_pairs(weight_function = lambda e:e[2]['weight']) + sage: dist + {0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} + sage: pred + {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} So for example the shortest weighted path from `0` to `3` is obtained as follows. The predecessor of `3` is ``pred[0][3] == 2``, the predecessor @@ -13930,7 +15321,7 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - sage: G.shortest_path_all_pairs(by_weight = True) + sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 1)) ({0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, @@ -13941,7 +15332,17 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - sage: G.shortest_path_all_pairs(default_weight=200) + + Now, ``default_weight`` does not work anymore:: + + sage: G.shortest_path_all_pairs(by_weight = True, default_weight=200) + Traceback (most recent call last): + ... + ValueError: The weight function cannot find the weight of (0, 1, None). + + It can be replaced by choosing an appropriate weight_function:: + + sage: G.shortest_path_all_pairs(weight_function=lambda e:(e[2] if e[2] is not None else 200)) ({0: {0: 0, 1: 200, 2: 5, 3: 4, 4: 2}, 1: {0: 200, 1: 0, 2: 200, 3: 201, 4: 202}, 2: {0: 5, 1: 200, 2: 0, 3: 1, 4: 3}, @@ -13953,16 +15354,45 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = 3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}}) - Checking the distances are equal regardless of the algorithm used:: + Checking that distances are equal regardless of the algorithm used:: sage: g = graphs.Grid2dGraph(5,5) sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS") sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython") sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") - sage: d1 == d2 == d3 + sage: d4, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") + sage: d5, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") + sage: d6, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") + sage: d1 == d2 == d3 == d4 == d5 == d6 + True + + Checking that distances are equal regardless of the algorithm used:: + + sage: g = digraphs.RandomDirectedGNM(6,12) + sage: d1, _ = g.shortest_path_all_pairs(algorithm="BFS") + sage: d2, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython") + sage: d3, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") + sage: d4, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") + sage: d5, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") + sage: d6, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") + sage: d1 == d2 == d3 == d4 == d5 == d6 True - Checking a random path is valid :: + Checking that weighted distances are equal regardless of the algorithm used:: + + sage: g = Graph() + sage: import random + sage: for v in range(5): + ....: for w in range(5): + ....: g.add_edge(v,w,random.uniform(1,10)) + sage: d1, _ = g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Python") + sage: d2, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_NetworkX") + sage: d3, _ = g.shortest_path_all_pairs(algorithm="Dijkstra_Boost") + sage: d4, _ = g.shortest_path_all_pairs(algorithm="Johnson_Boost") + sage: d1 == d2 == d3 == d4 + True + + Checking a random path is valid:: sage: dist, path = g.shortest_path_all_pairs(algorithm="BFS") sage: u,v = g.random_vertex(), g.random_vertex() @@ -13972,6 +15402,49 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = sage: len(p) == dist[u][v] + 2 True + Negative weights:: + + sage: g = DiGraph([(0,1,-2),(1,0,1)], weighted=True) + sage: g.shortest_path_all_pairs(by_weight=True) + Traceback (most recent call last): + ... + ValueError: The graph contains a negative cycle. + + Unreachable vertices are not present in the dictionaries:: + + sage: g = DiGraph([(0,1,1),(1,2,2)]) + sage: g.shortest_path_all_pairs(algorithm='BFS') + ({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, + {0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) + sage: g.shortest_path_all_pairs(algorithm='Dijkstra_NetworkX') + ({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, + {0: {0: None, 1: 1, 2: 1}, 1: {1: None, 2: 2}, 2: {2: None}}) + sage: g.shortest_path_all_pairs(algorithm='Dijkstra_Boost') + ({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, + {0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) + sage: g.shortest_path_all_pairs(algorithm='Floyd-Warshall-Python') + ({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, + {0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) + sage: g.shortest_path_all_pairs(algorithm='Floyd-Warshall-Cython') + ({0: {0: 0, 1: 1, 2: 2}, 1: {1: 0, 2: 1}, 2: {2: 0}}, + {0: {0: None, 1: 0, 2: 1}, 1: {1: None, 2: 1}, 2: {2: None}}) + + In order to change the default behavior if the graph is disconnected, + we can use default values with dictionaries:: + + sage: G = 2*graphs.PathGraph(2) + sage: d,_ = G.shortest_path_all_pairs() + sage: import itertools + sage: from sage.rings.infinity import Infinity + sage: for u,v in itertools.combinations(G.vertices(),2): + ....: print "dist({}, {}) = {}".format(u,v, d[u].get(v,+Infinity)) + dist(0, 1) = 1 + dist(0, 2) = +Infinity + dist(0, 3) = +Infinity + dist(1, 2) = +Infinity + dist(1, 3) = +Infinity + dist(2, 3) = 1 + TESTS: Wrong name for ``algorithm``:: @@ -13979,16 +15452,56 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = sage: g.shortest_path_all_pairs(algorithm="Bob") Traceback (most recent call last): ... - ValueError: The algorithm keyword can only be set to "auto", "BFS", "Floyd-Warshall-Python" or "Floyd-Warshall-Cython" + ValueError: Algorithm Bob is not implemented. + + Algorithms that do not work with weights:: + + sage: g = Graph({0: {1:1}, 1: {2:1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2}}, sparse=True ) + sage: g.shortest_path_all_pairs(algorithm="BFS", by_weight=True) + Traceback (most recent call last): + ... + ValueError: Algorithm 'BFS' does not work with weights. + sage: g.shortest_path_all_pairs(algorithm="Floyd-Warshall-Cython", by_weight=True) + Traceback (most recent call last): + ... + ValueError: Algorithm 'Floyd-Warshall-Cython' does not work with weights. + + Dijkstra with negative weights:: + + sage: g = Graph({0: {1:1}, 1: {2:1}, 2: {3: 1}, 3: {4: -2}, 4: {0: -2}}) + sage: g.shortest_path_all_pairs(algorithm="Dijkstra_Boost", by_weight=True) + Traceback (most recent call last): + ... + ValueError: Dijkstra algorithm does not work with negative weights. Please, use Bellman-Ford. + """ - if default_weight != 1: + if default_weight != None: + deprecation(18938, "Variable default_weight is deprecated: hence," + + " it is ignored. Please, use weight_function, instead.") + + if weight_function is not None: by_weight = True + elif by_weight: + weight_function = lambda e:e[2] - if algorithm == "auto": - if by_weight is False: - algorithm = "BFS" + if algorithm is None: + if by_weight: + for e in self.edges(): + try: + if weight_function(e) < 0: + algorithm = "Floyd-Warshall-Python" + break + except (ValueError, TypeError): + raise ValueError("The weight function cannot find the" + + " weight of " + e + ".") + if algorithm is None: + algorithm = "Dijkstra_Boost" else: - algorithm = "Floyd-Warshall-Python" + algorithm = "BFS" + + if by_weight and algorithm in ['BFS', "Floyd-Warshall-Cython"]: + raise ValueError("Algorithm '" + algorithm + "' does not work " + + "with weights.") if algorithm == "BFS": from sage.graphs.distances_all_pairs import distances_and_predecessors_all_pairs @@ -13998,35 +15511,66 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = from sage.graphs.distances_all_pairs import floyd_warshall return floyd_warshall(self, distances = True) + elif algorithm == "Johnson_Boost": + if not by_weight: + weight_function = lambda e:1 + from sage.graphs.base.boost_graph import johnson_shortest_paths + return [johnson_shortest_paths(self, weight_function), None] + + elif algorithm == "Dijkstra_Boost": + from sage.graphs.base.boost_graph import shortest_paths + dist = dict() + pred = dict() + if by_weight and weight_function is None: + weight_function = lambda e:e[2] + for u in self: + dist[u],pred[u]=shortest_paths(self, u, weight_function, algorithm) + return dist, pred + + elif algorithm == "Dijkstra_NetworkX": + dist = dict() + pred = dict() + for u in self: + paths=self.shortest_paths(u, by_weight=by_weight, + algorithm=algorithm, + weight_function=weight_function) + dist[u] = {v:self._path_length(p, by_weight=by_weight, + weight_function=weight_function) + for v,p in paths.iteritems()} + pred[u] = {v:None if len(p)<=1 else p[1] + for v,p in paths.iteritems()} + return dist, pred + elif algorithm != "Floyd-Warshall-Python": - raise ValueError("The algorithm keyword can only be set to "+ - "\"auto\","+ - " \"BFS\", "+ - "\"Floyd-Warshall-Python\" or "+ - "\"Floyd-Warshall-Cython\"") + raise ValueError("Algorithm " + algorithm + " is not implemented.") from sage.rings.infinity import Infinity + + if by_weight: + if weight_function is None: + weight_function = lambda e:e[2] + if check_weight: + self._check_weight_function(weight_function) + dist = {} pred = {} verts = self.vertices() for u in verts: - du = {} - pu = {} - for v in verts: - if self.has_edge(u, v): - if by_weight is False: - du[v] = 1 - else: - edge_label = self.edge_label(u, v) - if edge_label is None or edge_label == {}: - du[v] = default_weight - else: - du[v] = edge_label - pu[v] = u + du = {u:0} + pu = {u:None} + + if self.is_directed(): + neighbor = self.neighbor_out_iterator(u) + else: + neighbor = self.neighbor_iterator(u) + + for v in neighbor: + if by_weight is False: + du[v] = 1 else: - du[v] = Infinity - pu[v] = None - du[u] = 0 + du[v] = weight_function((u, v, self.edge_label(u, v))) + pu[v] = u + dist[u] = du pred[u] = pu @@ -14035,13 +15579,124 @@ def shortest_path_all_pairs(self, by_weight=False, default_weight=1, algorithm = for u in verts: du = dist[u] for v in verts: - if du[v] > du[w] + dw[v]: + if du.get(v, Infinity) > du.get(w, Infinity) + dw.get(v, Infinity): + if u == v: + raise ValueError("The graph contains a negative cycle.") du[v] = du[w] + dw[v] pred[u][v] = pred[w][v] return dist, pred - def average_distance(self): + def wiener_index(self, by_weight=False, algorithm=None, + weight_function=None, check_weight=True): + r""" + Returns the Wiener index of the graph. + + The Wiener index of a graph `G` is + `W(G) = \frac 1 2 \sum_{u,v\in G} d(u,v)` + where `d(u,v)` denotes the distance between vertices `u` and `v` (see + [KRG96b]_). + + For more information on the input variables and more examples, we refer + to :meth:`~GenericGraph.shortest_paths` and + :meth:`~GenericGraph.shortest_path_all_pairs`, + which have very similar input variables. + + INPUT: + + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - the Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - the Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + + EXAMPLES:: + + sage: G = Graph( { 0: {1: None}, 1: {2: None}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) + sage: G.wiener_index() + 15 + sage: G.wiener_index(weight_function=lambda e:(e[2] if e[2] is not None else 1)) + 20 + sage: G.wiener_index(weight_function=lambda e:(e[2] if e[2] is not None else 200)) + 820 + sage: G.wiener_index(algorithm='BFS') + 15 + sage: G.wiener_index(algorithm='Floyd-Warshall-Cython') + 15 + sage: G.wiener_index(algorithm='Floyd-Warshall-Python') + 15 + sage: G.wiener_index(algorithm='Dijkstra_Boost') + 15 + sage: G.wiener_index(algorithm='Johnson_Boost') + 15 + sage: G.wiener_index(algorithm='Dijkstra_NetworkX') + 15 + + TESTS:: + + sage: G.wiener_index(algorithm='BFS', weight_function=lambda e:(e[2] if e[2] is not None else 200)) + Traceback (most recent call last): + ... + ValueError: BFS algorithm does not work on weighted graphs. + """ + by_weight = by_weight or (weight_function is not None) + + if self.order() < 2: + raise ValueError("The graph must have at least two vertices for this value to be defined") + + if algorithm=='BFS' or (algorithm is None and not by_weight): + if by_weight: + raise ValueError("BFS algorithm does not work on weighted graphs.") + from distances_all_pairs import wiener_index + return wiener_index(self) + + if not self.is_connected(): + from sage.rings.infinity import Infinity + return Infinity + + distances = self.shortest_path_all_pairs(by_weight=by_weight, + algorithm=algorithm, weight_function=weight_function, + check_weight=check_weight)[0] + total = 0 + for u in distances.values(): + total = total + sum(u.values()) + + return total / 2 + + def average_distance(self, by_weight=False, algorithm=None, + weight_function=None): r""" Returns the average distance between vertices of the graph. @@ -14050,6 +15705,51 @@ def average_distance(self): denotes the distance between vertices `u` and `v` and `n` is the number of vertices in `G`. + For more information on the input variables and more examples, we refer + to :meth:`~GenericGraph.wiener_index` and + :meth:`~GenericGraph.shortest_path_all_pairs`, + which have very similar input variables. + + INPUT: + + - ``by_weight`` (boolean) - if ``True``, the edges in the graph are + weighted; if ``False``, all edges have weight 1. + + - ``algorithm`` (string) - one of the following algorithms: + + - ``'BFS'`` - the computation is done through a BFS centered on each + vertex successively. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Cython'`` - the Cython implementation of + the Floyd-Warshall algorithm. Works only if ``by_weight==False``. + + - ``'Floyd-Warshall-Python'`` - the Python implementation of + the Floyd-Warshall algorithm. Works also with weighted graphs, even + with negative weights (but no negative cycle is allowed). + + - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in + NetworkX. It works with weighted graphs, but no negative weight is + allowed. + + - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost + (works only with positive weights). + + - ``'Johnson_Boost'``: the Johnson algorithm, implemented in + Boost (works also with negative weights, if there is no negative + cycle). + + - ``None`` (default): Sage chooses the best algorithm: ``'BFS'`` for + unweighted graphs, ``'Dijkstra_Boost'`` if all weights are + positive, ``'Johnson_Boost'``, otherwise. + + - ``weight_function`` (function) - a function that inputs an edge + ``(u, v, l)`` and outputs its weight. If not ``None``, ``by_weight`` + is automatically set to ``True``. If ``None`` and ``by_weight`` is + ``True``, we use the edge label ``l`` as a weight. + + - ``check_weight`` (boolean) - if ``True``, we check that the + weight_function outputs a number for each edge. + EXAMPLE: From [GYLL93]_:: @@ -14062,8 +15762,8 @@ def average_distance(self): REFERENCE: .. [GYLL93] I. Gutman, Y.-N. Yeh, S.-L. Lee, and Y.-L. Luo. Some recent - results in the theory of the Wiener number. *Indian Journal of - Chemistry*, 32A:651--661, 1993. + results in the theory of the Wiener number. *Indian Journal of + Chemistry*, 32A:651--661, 1993. TEST:: @@ -14073,10 +15773,7 @@ def average_distance(self): ... ValueError: The graph must have at least two vertices for this value to be defined """ - if self.order() < 2: - raise ValueError("The graph must have at least two vertices for this value to be defined") - - return Integer(self.wiener_index())/Integer((self.order()*(self.order()-1))/2) + return 2 * self.wiener_index() / (self.order()*(self.order()-1)) def szeged_index(self): r""" @@ -14149,7 +15846,7 @@ def breadth_first_search(self, start, ignore_direction=False, vertices. For a graph, ``neighbors`` is by default the :meth:`.neighbors` function of the graph. For a digraph, the ``neighbors`` function defaults to the - :meth:`successors` function of the graph. + :meth:`~DiGraph.neighbor_out_iterator` function of the graph. - ``report_distance`` -- (default ``False``) If ``True``, reports pairs (vertex, distance) where distance is the @@ -14244,6 +15941,10 @@ def breadth_first_search(self, start, ignore_direction=False, sage: list(D.breadth_first_search(0, ignore_direction=True)) [0, 1, 2] """ + from sage.rings.semirings.non_negative_integer_semiring import NN + if (distance is not None and distance not in NN): + raise ValueError("distance must be a non-negative integer, not {0}".format(distance)) + # Preferably use the Cython implementation if neighbors is None and not isinstance(start, list) and distance is None and hasattr(self._backend,"breadth_first_search") and not report_distance: for v in self._backend.breadth_first_search(start, ignore_direction=ignore_direction): @@ -14260,6 +15961,12 @@ def breadth_first_search(self, start, ignore_direction=False, else: queue = [(start, 0)] + # Non-existing start vertex is detected later if distance > 0. + if distance == 0: + for v in queue: + if not v[0] in self: + raise LookupError("start vertex ({0}) is not a vertex of the graph".format(v[0])) + for v, d in queue: if report_distance: yield v, d @@ -14282,11 +15989,10 @@ def breadth_first_search(self, start, ignore_direction=False, def depth_first_search(self, start, ignore_direction=False, distance=None, neighbors=None): """ - Returns an iterator over the vertices in a depth-first ordering. + Return an iterator over the vertices in a depth-first ordering. INPUT: - - ``start`` - vertex or list of vertices from which to start the traversal @@ -14294,16 +16000,14 @@ def depth_first_search(self, start, ignore_direction=False, directed graphs. If True, searches across edges in either direction. - - ``distance`` - the maximum distance from the ``start`` nodes - to traverse. The ``start`` nodes are distance zero from - themselves. + - ``distance`` - Deprecated. Broken, do not use. - ``neighbors`` - a function giving the neighbors of a vertex. The function should take a vertex and return a list of vertices. For a graph, ``neighbors`` is by default the :meth:`.neighbors` function of the graph. For a digraph, the ``neighbors`` function defaults to the - :meth:`.successors` function of the graph. + :meth:`~DiGraph.neighbor_out_iterator` function of the graph. .. SEEALSO:: @@ -14331,15 +16035,6 @@ def depth_first_search(self, start, ignore_direction=False, sage: list(D.depth_first_search(0, ignore_direction=True)) [0, 7, 6, 3, 5, 2, 1, 4] - You can specify a maximum distance in which to search. A - distance of zero returns the ``start`` vertices:: - - sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) - sage: list(D.depth_first_search(0,distance=0)) - [0] - sage: list(D.depth_first_search(0,distance=1)) - [0, 3, 2, 1] - Multiple starting vertices can be specified in a list:: sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) @@ -14347,24 +16042,22 @@ def depth_first_search(self, start, ignore_direction=False, [0, 3, 6, 7, 2, 5, 1, 4] sage: list(D.depth_first_search([0,6])) [0, 3, 6, 7, 2, 5, 1, 4] - sage: list(D.depth_first_search([0,6],distance=0)) - [0, 6] - sage: list(D.depth_first_search([0,6],distance=1)) - [0, 3, 2, 1, 6, 7] - sage: list(D.depth_first_search(6,ignore_direction=True,distance=2)) - [6, 7, 5, 0, 3] More generally, you can specify a ``neighbors`` function. For example, you can traverse the graph backwards by setting ``neighbors`` to be the :meth:`.neighbors_in` function of the graph:: - sage: D = DiGraph( { 0: [1,2,3], 1: [4,5], 2: [5], 3: [6], 5: [7], 6: [7], 7: [0]}) - sage: list(D.depth_first_search(5,neighbors=D.neighbors_in, distance=2)) - [5, 2, 0, 1] - sage: list(D.depth_first_search(5,neighbors=D.neighbors_out, distance=2)) - [5, 7, 0] - sage: list(D.depth_first_search(5,neighbors=D.neighbors, distance=2)) - [5, 7, 6, 0, 2, 1, 4] + sage: D = digraphs.Path(10) + sage: D.add_path([22,23,24,5]) + sage: D.add_path([5,33,34,35]) + sage: list(D.depth_first_search(5, neighbors=D.neighbors_in)) + [5, 4, 3, 2, 1, 0, 24, 23, 22] + sage: list(D.breadth_first_search(5, neighbors=D.neighbors_in)) + [5, 24, 4, 23, 3, 22, 2, 1, 0] + sage: list(D.depth_first_search(5, neighbors=D.neighbors_out)) + [5, 6, 7, 8, 9, 33, 34, 35] + sage: list(D.breadth_first_search(5, neighbors=D.neighbors_out)) + [5, 33, 6, 34, 7, 35, 8, 9] TESTS:: @@ -14375,6 +16068,10 @@ def depth_first_search(self, start, ignore_direction=False, [0, 2, 1] """ + from sage.misc.superseded import deprecation + if distance is not None: + deprecation(19227, "Parameter 'distance' is broken. Do not use.") + # Preferably use the Cython implementation if neighbors is None and not isinstance(start,list) and distance is None and hasattr(self._backend,"depth_first_search"): for v in self._backend.depth_first_search(start, ignore_direction = ignore_direction): @@ -14603,8 +16300,7 @@ def add_path(self, vertices): vert1 = v def complement(self): - """ - Returns the complement of the (di)graph. + """Returns the complement of the (di)graph. The complement of a graph has the same vertices, but exactly those edges that are not in the original graph. This is not well defined @@ -14632,7 +16328,10 @@ def complement(self): sage: G.complement() Traceback (most recent call last): ... - TypeError: complement not well defined for (di)graphs with multiple edges + ValueError: This method is not known to work on graphs with + multiedges. Perhaps this method can be updated to handle them, but + in the meantime if you want to use it please disallow multiedges + using allow_multiple_edges(). TESTS: @@ -14641,17 +16340,23 @@ def complement(self): sage: G = graphs.PathGraph(5).copy(immutable=True) sage: G.complement() complement(Path graph): Graph on 5 vertices + + The name is not updated when there was none in the first place:: + + sage: g = Graph(graphs.PetersenGraph().edges()); g + Graph on 10 vertices + sage: g.complement() + Graph on 10 vertices + """ - if self.has_multiple_edges(): - raise TypeError('complement not well defined for (di)graphs with multiple edges') self._scream_if_not_simple() - G = copy(self) - G.delete_edges(G.edges()) - G.name('complement(%s)'%self.name()) - for u in self: - for v in self: - if not self.has_edge(u,v): - G.add_edge(u,v) + + G = self.copy(data_structure='dense') + G._backend.c_graph()[0].complement() + + if self.name(): + G.name("complement({})".format(self.name())) + if getattr(self, '_immutable', False): return G.copy(immutable=True) return G @@ -15989,8 +17694,7 @@ def layout_graphviz(self, dim = 2, prog = 'dot', **options): are in your path. The graphviz suite can be download from http://graphviz.org. - - Download dot2tex-2.8.?.spkg from http://trac.sagemath.org/sage_trac/ticket/7004 - and install it with ``sage -i dot2tex-2.8.?.spkg`` + - Install dot2tex with ``sage -i dot2tex`` .. TODO:: @@ -16059,9 +17763,6 @@ def graphplot(self, **options): sage: g = Graph({}, loops=True, multiedges=True, sparse=True) sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: g.set_boundary([0,1]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. sage: GP = g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.plot() Graphics object consisting of 22 graphics primitives @@ -17767,15 +19468,10 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input = True, sage: G = graphs.PathGraph(5) sage: G.set_vertices({0: 'before', 1: 'delete', 2: 'after'}) - sage: G.set_boundary([1,2,3]) - doctest:...: DeprecationWarning: The boundary parameter is deprecated and will soon disappear. - See http://trac.sagemath.org/15494 for details. sage: G.delete_vertex(1) sage: G.relabel() sage: G.get_vertices() {0: 'before', 1: 'after', 2: None, 3: None} - sage: G.get_boundary() - [1, 2] sage: G.get_pos() {0: (0, 0), 1: (2, 0), 2: (3, 0), 3: (4, 0)} @@ -17887,8 +19583,6 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input = True, setattr(self, attr, new_attr) - self._boundary = [perm[v] for v in self._boundary] - if return_map: return perm @@ -18525,18 +20219,11 @@ def is_hamiltonian(self): sage: g.is_hamiltonian() False - TESTS: - - When no solver is installed, a - ``OptionalPackageNotFoundError`` exception is raised:: + TESTS:: - sage: from sage.misc.exceptions import OptionalPackageNotFoundError - sage: try: - ... g = graphs.ChvatalGraph() - ... if not g.is_hamiltonian(): - ... print "There is something wrong here !" - ... except OptionalPackageNotFoundError: - ... pass + sage: g = graphs.ChvatalGraph() + sage: g.is_hamiltonian() + True :trac:`16210`:: @@ -18560,13 +20247,17 @@ def is_isomorphic(self, other, certify=False, verbosity=0, edge_labels=False): INPUT: + - ``certify`` - if True, then output is `(a, b)`, where `a` + is a boolean and `b` is either a map or ``None``. - - ``certify`` - if True, then output is (a,b), where a - is a boolean and b is either a map or None. - - - ``edge_labels`` - default False, otherwise allows + - ``edge_labels`` - default ``False``, otherwise allows only permutations respecting edge labels. + OUTPUT: + + - either a boolean or, if ``certify`` is ``True``, a tuple consisting + of a boolean and a map or ``None`` + EXAMPLES: Graphs:: @@ -18745,16 +20436,21 @@ def is_isomorphic(self, other, certify=False, verbosity=0, edge_labels=False): sage: g.is_isomorphic(gg) False - Ensure that trac:`14777` is fixed :: + Ensure that :trac:`14777` is fixed :: sage: g = Graph() sage: h = Graph() sage: g.is_isomorphic(h) True + + as well as :trac:`18613`:: + + sage: g.is_isomorphic(h, certify=True) + (True, None) """ if self.order() == other.order() == 0: - return True + return (True, None) if certify else True if (self.is_directed() != other.is_directed() or self.order() != other.order() or self.size() != other.size() or self.degree_sequence() != other.degree_sequence()): @@ -19016,8 +20712,6 @@ def canonical_label(self, partition=None, certify=False, verbosity=0, import sage.graphs.distances_all_pairs GenericGraph.distances_distribution = types.MethodType(sage.graphs.distances_all_pairs.distances_distribution, None, GenericGraph) -GenericGraph.wiener_index = types.MethodType(sage.graphs.distances_all_pairs.wiener_index, None, GenericGraph) -GenericGraph.diameter = types.MethodType(sage.graphs.distances_all_pairs.diameter, None, GenericGraph) from sage.graphs.base.boost_graph import dominator_tree GenericGraph.dominator_tree = types.MethodType(dominator_tree, None, GenericGraph) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 62ac157fbb4..e4d1c773fe2 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1,144 +1,11 @@ +# -*- coding: utf-8 -*- r""" Undirected graphs This module implements functions and operations involving undirected graphs. -**Graph basic operations:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.write_to_eps` | Writes a plot of the graph to ``filename`` in ``eps`` format. - :meth:`~Graph.to_undirected` | Since the graph is already undirected, simply returns a copy of itself. - :meth:`~Graph.to_directed` | Returns a directed version of the graph. - :meth:`~Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string. - :meth:`~Graph.graph6_string` | Returns the graph6 representation of the graph as an ASCII string. - :meth:`~Graph.bipartite_sets` | Returns `(X,Y)` where X and Y are the nodes in each bipartite set of graph. - :meth:`~Graph.bipartite_color` | Returns a dictionary with vertices as the keys and the color class as the values. - :meth:`~Graph.is_directed` | Since graph is undirected, returns False. - :meth:`~Graph.join` | Returns the join of self and other. - - -**Distances:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.centrality_closeness` | Returns the closeness centrality (1/average distance to all vertices) - :meth:`~Graph.centrality_degree` | Returns the degree centrality - - -**Graph properties:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.is_asteroidal_triple_free` | Tests whether the current graph is asteroidal triple free. - :meth:`~Graph.is_prime` | Tests whether the current graph is prime. - :meth:`~Graph.is_split` | Returns ``True`` if the graph is a Split graph, ``False`` otherwise. - :meth:`~Graph.is_triangle_free` | Returns whether ``self`` is triangle-free. - :meth:`~Graph.is_bipartite` | Returns True if graph G is bipartite, False if not. - :meth:`~Graph.is_line_graph` | Tests wether the graph is a line graph. - :meth:`~Graph.is_odd_hole_free` | Tests whether ``self`` contains an induced odd hole. - :meth:`~Graph.is_even_hole_free` | Tests whether ``self`` contains an induced even hole. - :meth:`~Graph.is_cartesian_product` | Tests whether ``self`` is a cartesian product of graphs. - :meth:`~Graph.is_long_hole_free` | Tests whether ``self`` contains an induced cycle of length at least 5. - :meth:`~Graph.is_long_antihole_free` | Tests whether ``self`` contains an induced anticycle of length at least 5. - :meth:`~Graph.is_weakly_chordal` | Tests whether ``self`` is weakly chordal. - :meth:`~Graph.is_strongly_regular` | Tests whether ``self`` is strongly regular. - :meth:`~Graph.is_distance_regular` | Tests whether ``self`` is distance-regular. - :meth:`~Graph.is_tree` | Return True if the graph is a tree. - :meth:`~Graph.is_forest` | Return True if the graph is a forest, i.e. a disjoint union of trees. - :meth:`~Graph.is_overfull` | Tests whether the current graph is overfull. - :meth:`~Graph.odd_girth` | Returns the odd girth of ``self``. - :meth:`~Graph.is_edge_transitive` | Returns true if self is edge-transitive. - :meth:`~Graph.is_arc_transitive` | Returns true if self is arc-transitive. - :meth:`~Graph.is_half_transitive` | Returns true if self is a half-transitive graph. - :meth:`~Graph.is_semi_symmetric` | Returns true if self is a semi-symmetric graph. - -**Connectivity, orientations, trees:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of self. - :meth:`~Graph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree - :meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)` - :meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph. - :meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph. - :meth:`~Graph.bridges` | Returns the list of all bridges. - :meth:`~Graph.spanning_trees` | Returns the list of all spanning trees. - :meth:`~Graph.random_spanning_tree` | Returns a random spanning tree. - -**Clique-related methods:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.clique_complex` | Returns the clique complex of self - :meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex - :meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex - :meth:`~Graph.cliques_get_clique_bipartite` | Returns a bipartite graph constructed such that maximal cliques are the right vertices and the left vertices are retained from the given graph - :meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices. - :meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex. - :meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph. - :meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph. - :meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques - :meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques - :meth:`~Graph.clique_polynomial` | Returns the clique polynomial - -**Algorithmically hard stuff:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.vertex_cover` | Returns a minimum vertex cover. - :meth:`~Graph.independent_set` | Returns a maximum independent set. - :meth:`~Graph.topological_minor` | Returns a topological `H`-minor of ``self`` if one exists. - :meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` object corresponding to ``self``. - :meth:`~Graph.matching_polynomial` | Computes the matching polynomial. - :meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width. - :meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H`. - :meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives. - :meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found. - :meth:`~Graph.has_homomorphism_to` | Checks whether there is a morphism between two graphs. - :meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices. - :meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial. - :meth:`~Graph.chromatic_symmetric_function` | Return the chromatic symmetric function. - :meth:`~Graph.tutte_polynomial` | Returns the Tutte polynomial. - :meth:`~Graph.is_perfect` | Tests whether the graph is perfect. - :meth:`~Graph.treewidth` | Computes the tree-width and provides a decomposition. - - -**Leftovers:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.cores` | Returns the core number for each vertex in an ordered list. - :meth:`~Graph.matching` | Returns a maximum weighted matching of the graph - :meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index. - :meth:`~Graph.lovasz_theta` | Returns the Lovasz number (a.k.a theta). - :meth:`~Graph.kirchhoff_symanzik_polynomial` | Returns the Kirchhoff-Symanzik polynomial. - :meth:`~Graph.modular_decomposition` | Returns the modular decomposition. - :meth:`~Graph.maximum_average_degree` | Returns the Maximum Average Degree (MAD). - :meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors. - :meth:`~Graph.ihara_zeta_function_inverse` | Returns the inverse of the zeta function. +{INDEX_OF_METHODS} AUTHORS: @@ -337,6 +204,13 @@ sage: g Graph on 5 vertices +- an igraph Graph:: + + sage: import igraph # optional - python_igraph + sage: g = Graph(igraph.Graph([(1,3),(3,2),(0,2)])) # optional - python_igraph + sage: g # optional - python_igraph + Graph on 4 vertices + Generators ---------- @@ -543,97 +417,104 @@ from sage.graphs.digraph import DiGraph from sage.graphs.independent_sets import IndependentSets from sage.combinat.combinatorial_map import combinatorial_map - +from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index class Graph(GenericGraph): r""" Undirected graph. A graph is a set of vertices connected by edges. See also the - :wikipedia:`Wikipedia article on graphs `. - - One can very easily create a graph in Sage by typing:: - - sage: g = Graph() - - By typing the name of the graph, one can get some basic information - about it:: + :wikipedia:`Wikipedia article on graphs `. For a + collection of pre-defined graphs, see the + :mod:`~sage.graphs.graph_generators` module. - sage: g - Graph on 0 vertices + A :class:`Graph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.graph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.digraph`. - This graph is not very interesting as it is by default the empty graph. - But Sage contains a large collection of pre-defined graph classes that - can be listed this way: - - * Within a Sage session, type ``graphs.`` - (Do not press "Enter", and do not forget the final period ".") + INPUT: - * Hit "tab". + By default, a :class:`Graph` object is simple (i.e. no *loops* nor *multiple + edges*) and unweighted. This can be easily tuned with the appropriate flags + (see below). - You will see a list of methods which will construct named graphs. For - example:: + - ``data`` -- can be any of the following (see the ``format`` argument): - sage: g = graphs.PetersenGraph() - sage: g.plot() - Graphics object consisting of 26 graphics primitives + #. ``Graph()`` -- build a graph on 0 vertices. - or:: + #. ``Graph(5)`` -- return an edgeless graph on the 5 vertices 0,...,4. - sage: g = graphs.ChvatalGraph() - sage: g.plot() - Graphics object consisting of 37 graphics primitives + #. ``Graph([list_of_vertices,list_of_edges])`` -- returns a graph with + given vertices/edges. - In order to obtain more information about these graph constructors, access - the documentation using the command ``graphs.RandomGNP?``. + To bypass auto-detection, prefer the more explicit + ``Graph([V,E],format='vertices_and_edges')``. - Once you have defined the graph you want, you can begin to work on it - by using the almost 200 functions on graphs in the Sage library! - If your graph is named ``g``, you can list these functions as previously - this way + #. ``Graph(list_of_edges)`` -- return a graph with a given list of edges + (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) + To bypass auto-detection, prefer the more explicit ``Graph(L, + format='list_of_edges')``. - * Hit "tab". + #. ``Graph({1:[2,3,4],3:[4]})`` -- return a graph by associating to each + vertex the list of its neighbors. - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_lists')``. - If you have defined a graph ``g`` having several connected components - (i.e. ``g.is_connected()`` returns False), you can print each one of its - connected components with only two lines:: + #. ``Graph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a graph by + associating a list of neighbors to each vertex and providing its edge + label. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 37 graphics primitives + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_dicts')``. + For graphs with multiple edges, you can provide a list of labels + instead, e.g.: ``Graph({1: {2: ['a1', 'a2'], 3:['b']} ,3:{2:['c']}})``. - INPUT: + #. ``Graph(a_symmetric_matrix)`` -- return a graph with given (weighted) + adjacency matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - - ``data`` -- can be any of the following (see the ``format`` argument): + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - #. An integer specifying the number of vertices + #. ``Graph(a_nonsymmetric_matrix)`` -- return a graph with given incidence + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='incidence_matrix')``. - #. A dictionary of lists + #. ``Graph([V, f])`` -- return a graph from a vertex set ``V`` and a + *symmetric* function ``f``. The graph contains an edge `u,v` whenever + ``f(u,v)`` is ``True``.. Example: ``Graph([ [1..10], lambda x,y: + abs(x-y).is_square()])`` - #. A Sage adjacency matrix or incidence matrix + #. ``Graph(':I`ES@obGkqegW~')`` -- return a graph from a graph6 or sparse6 + string (see documentation of :meth:`graph6_string` or + :meth:`sparse6_string`). - #. A pygraphviz graph + #. ``Graph(a_seidel_matrix, format='seidel_adjacency_matrix')`` -- return + a graph with a given seidel adjacency matrix (see documentation of + :meth:`seidel_adjacency_matrix`). - #. A NetworkX graph + #. ``Graph(another_graph)`` -- return a graph from a Sage (di)graph, + `pygraphviz `__ graph, `NetworkX + `__ graph, or `igraph + `__ graph. - - ``pos`` - a positioning dictionary: for example, the - spring layout from NetworkX for the 5-cycle is:: + - ``pos`` - a positioning dictionary (cf. documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.layout`). For example, to + draw 4 vertices on a square:: - {0: [-0.91679746, 0.88169588], - 1: [ 0.47294849, 1.125 ], - 2: [ 1.125 ,-0.12867615], - 3: [ 0.12743933,-1.125 ], - 4: [-1.125 ,-0.50118505]} + {0: [-1,-1], + 1: [ 1,-1], + 2: [ 1, 1], + 3: [-1, 1]} - ``name`` - (must be an explicitly named parameter, i.e., ``name="complete")`` gives the graph a name @@ -642,54 +523,19 @@ class Graph(GenericGraph): if data is an instance of the ``Graph`` class) - ``multiedges`` - boolean, whether to allow multiple - edges (ignored if data is an instance of the ``Graph`` class) - - - ``weighted`` - whether graph thinks of itself as - weighted or not. See ``self.weighted()`` + edges (ignored if data is an instance of the ``Graph`` class). - - ``format`` - if None, Graph tries to guess- can be - several values, including: + - ``weighted`` - whether graph thinks of itself as weighted or not. See + :meth:`~sage.graphs.generic_graph.GenericGraph.weighted`. - - ``'int'`` - an integer specifying the number of vertices in an - edge-free graph with vertices labelled from 0 to n-1 - - - ``'graph6'`` - Brendan McKay's graph6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'sparse6'`` - Brendan McKay's sparse6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 - - - ``'elliptic_curve_congruence'`` - data must be an - iterable container of elliptic curves, and the graph produced has - each curve as a vertex (it's Cremona label) and an edge E-F - labelled p if and only if E is congruent to F mod p - - - ``NX`` - data must be a NetworkX Graph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX graph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``boundary`` - a list of boundary vertices, if - empty, graph is considered as a 'graph without boundary' + - ``format`` - if set to ``None`` (default), :class:`Graph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"graph6"``, ``"sparse6"``, ``"rule"``, ``"list_of_edges"``, + ``"dict_of_lists"``, ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"seidel_adjacency_matrix"``, + ``"incidence_matrix"``, ``"elliptic_curve_congruence"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -714,7 +560,7 @@ class Graph(GenericGraph): ``data_structure='static_sparse'``. Set to ``False`` by default. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -882,9 +728,7 @@ class Graph(GenericGraph): sage: Graph(Matrix([[1],[1],[1]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(Matrix([[1],[1],[0]])) Graph on 3 vertices @@ -902,9 +746,7 @@ class Graph(GenericGraph): sage: Graph(M) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1] in column 2 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1] in column 2 Check that :trac:`9714` is fixed:: @@ -924,14 +766,20 @@ class Graph(GenericGraph): sage: Graph(M).edges() [(0, 1, None)] + #. A Seidel adjacency matrix:: + + sage: from sage.combinat.matrices.hadamard_matrix import \ + ....: regular_symmetric_hadamard_matrix_with_constant_diagonal as rshcd + sage: m=rshcd(16,1)- matrix.identity(16) + sage: Graph(m,format="seidel_adjacency_matrix").is_strongly_regular(parameters=True) + (16, 6, 2, 2) + #. a list of edges, or labelled edges:: sage: g = Graph([(1,3),(3,8),(5,2)]) sage: g Graph on 5 vertices - :: - sage: g = Graph([(1,2,"Peace"),(7,-9,"and"),(77,2, "Love")]) sage: g Graph on 5 vertices @@ -953,6 +801,45 @@ class Graph(GenericGraph): sage: DiGraph(g) Digraph on 5 vertices + #. An igraph Graph (see also + :meth:`~sage.graphs.generic_graph.GenericGraph.igraph_graph`):: + + sage: import igraph # optional - python_igraph + sage: g = igraph.Graph([(0,1),(0,2)]) # optional - python_igraph + sage: Graph(g) # optional - python_igraph + Graph on 3 vertices + + If ``vertex_labels`` is ``True``, the names of the vertices are given by + the vertex attribute ``'name'``, if available:: + + sage: g = igraph.Graph([(0,1),(0,2)], vertex_attrs={'name':['a','b','c']}) # optional - python_igraph + sage: Graph(g).vertices() # optional - python_igraph + ['a', 'b', 'c'] + sage: g = igraph.Graph([(0,1),(0,2)], vertex_attrs={'label':['a','b','c']}) # optional - python_igraph + sage: Graph(g).vertices() # optional - python_igraph + [0, 1, 2] + + If the igraph Graph has edge attributes, they are used as edge labels:: + + sage: g = igraph.Graph([(0,1),(0,2)], edge_attrs={'name':['a','b'], 'weight':[1,3]}) # optional - python_igraph + sage: Graph(g).edges() # optional - python_igraph + [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})] + + + When defining an undirected graph from a function ``f``, it is *very* + important that ``f`` be symmetric. If it is not, anything can happen:: + + sage: f_sym = lambda x,y : abs(x-y) == 1 + sage: f_nonsym = lambda x,y : (x-y) == 1 + sage: G_sym = Graph([[4,6,1,5,3,7,2,0], f_sym]) + sage: G_sym.is_isomorphic(graphs.PathGraph(8)) + True + sage: G_nonsym = Graph([[4,6,1,5,3,7,2,0], f_nonsym]) + sage: G_nonsym.size() + 4 + sage: G_nonsym.is_isomorphic(G_sym) + False + By default, graphs are mutable and can thus not be used as a dictionary key:: @@ -979,11 +866,38 @@ class Graph(GenericGraph): Traceback (most recent call last): ... ValueError: Unknown input format 'HeyHeyHey' + + sage: Graph(igraph.Graph(directed=True)) # optional - python_igraph + Traceback (most recent call last): + ... + ValueError: An *undirected* igraph graph was expected. To build an directed graph, call the DiGraph constructor. + + sage: m = matrix([[0,-1],[-1,0]]) + sage: Graph(m,format="seidel_adjacency_matrix") + Graph on 2 vertices + sage: m[0,1]=1 + sage: Graph(m,format="seidel_adjacency_matrix") + Traceback (most recent call last): + ... + ValueError: Graph's Seidel adjacency matrix must be symmetric + + sage: m[0,1]=-1; m[1,1]=1 + sage: Graph(m,format="seidel_adjacency_matrix") + Traceback (most recent call last): + ... + ValueError: Graph's Seidel adjacency matrix must have 0s on the main diagonal + + From a a list of vertices and a list of edges:: + + sage: G = Graph([[1,2,3],[(1,2)]]); G + Graph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = False def __init__(self, data=None, pos=None, loops=None, format=None, - boundary=None, weighted=None, implementation='c_graph', + weighted=None, implementation='c_graph', data_structure="sparse", vertex_labels=True, name=None, multiedges=None, convert_empty_dict_labels_to_None=None, sparse=True, immutable=False): @@ -1050,12 +964,6 @@ def __init__(self, data=None, pos=None, loops=None, format=None, sage: grafo4.shortest_path(0,6,by_weight=True) [0, 1, 2, 5, 4, 6] - Get rid of mutable default argument for `boundary` (:trac:`14794`):: - - sage: G = Graph(boundary=None) - sage: G._boundary - [] - Graphs returned when setting ``immutable=False`` are mutable:: sage: g = graphs.PetersenGraph() @@ -1079,9 +987,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, sage: Graph(matrix([[1,1],[1,1],[1,0]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(matrix([[3,1,1],[0,1,1]])) Traceback (most recent call last): ... @@ -1089,7 +995,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, to 2, but column 0 does not """ GenericGraph.__init__(self) - msg = '' + from sage.structure.element import is_Matrix if sparse is False: @@ -1140,7 +1046,6 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'adjacency_matrix' else: format = 'incidence_matrix' - msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: " if format is None and isinstance(data, Graph): format = 'Graph' from sage.graphs.all import DiGraph @@ -1152,6 +1057,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, len(data)>=2 and callable(data[1])): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -1164,9 +1078,20 @@ def __init__(self, data=None, pos=None, loops=None, format=None, import networkx if isinstance(data, (networkx.DiGraph, networkx.MultiDiGraph)): data = data.to_undirected() - format = 'NX' elif isinstance(data, (networkx.Graph, networkx.MultiGraph)): format = 'NX' + + if (format is None and + hasattr(data, 'vcount') and + hasattr(data, 'get_edgelist')): + try: + import igraph + except ImportError: + raise ImportError("The data seems to be a igraph object, but "+ + "igraph is not installed in Sage. To install "+ + "it, run 'sage -i python_igraph'") + if format is None and isinstance(data, igraph.Graph): + format = 'igraph' if format is None and isinstance(data, (int, Integer)): format = 'int' if format is None and data is None: @@ -1195,155 +1120,32 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if weighted is None: weighted = False self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) - if not isinstance(data, str): - raise ValueError('If input format is graph6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_graph6(s, n) - expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(i): - if m[k] == '1': - self._backend.add_edge(i, j, None, False) - k += 1 + from graph_input import from_graph6 + from_graph6(self, data) elif format == 'sparse6': if weighted is None: weighted = False self.allow_loops(False if loops is False else True, check=False) self.allow_multiple_edges(False if multiedges is False else True, check=False) - from math import ceil, floor - from sage.misc.functional import log - n = data.find('\n') - if n == -1: - n = len(data) - s = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(s[1:]) - if n == 0: - edges = [] - else: - k = int(ceil(log(n,2))) - ords = [ord(i) for i in s] - if any(o > 126 or o < 63 for o in ords): - raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) - bits = ''.join([generic_graph_pyx.int_to_binary_string(o-63).zfill(6) for o in ords]) - b = [] - x = [] - for i in xrange(int(floor(len(bits)/(k+1)))): - b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) - x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) - v = 0 - edges = [] - for i in xrange(len(b)): - if b[i] == 1: - v += 1 - if x[i] > v: - v = x[i] - else: - if v < n: - edges.append((x[i],v)) - self.add_vertices(range(n)) - self.add_edges(edges) + from graph_input import from_sparse6 + from_sparse6(self, data) + elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False - - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) - - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - if loops is None: - loops = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - if i <= j: - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - - oriented = any(data[pos] < 0 for pos in data.nonzero_positions(copy=False)) - - positions = [] - for i in range(data.ncols()): - NZ = data.nonzero_positions_in_column(i) - if len(NZ) == 1: - if oriented: - raise ValueError("Column {} of the (oriented) incidence " - "matrix contains only one nonzero value".format(i)) - elif data[NZ[0],i] != 2: - raise ValueError("Each column of a non-oriented incidence " - "matrix must sum to 2, but column {} does not".format(i)) - if loops is None: - loops = True - positions.append((NZ[0],NZ[0])) - elif len(NZ) != 2 or \ - (oriented and not ((data[NZ[0],i] == +1 and data[NZ[1],i] == -1) or \ - (data[NZ[0],i] == -1 and data[NZ[1],i] == +1))) or \ - (not oriented and (data[NZ[0],i] != 1 or data[NZ[1],i] != 1)): - msg += "There must be one or two nonzero entries per column. " - msg += "Got entries {} in column {}".format([data[j,i] for j in NZ], i) - raise ValueError(msg) - else: - positions.append(tuple(NZ)) - - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = (len(set(positions)) < total ) - self.allow_loops(False if loops is None else loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) + from graph_input import from_incidence_matrix + from_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + + elif format == 'seidel_adjacency_matrix': + multiedges = False + weighted = False + loops = False + self.allow_loops(False) + self.allow_multiple_edges(False) + from graph_input import from_seidel_adjacency_matrix + from_seidel_adjacency_matrix(self, data) elif format == 'Graph': if loops is None: loops = data.allows_loops() if multiedges is None: multiedges = data.allows_multiple_edges() @@ -1377,6 +1179,19 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.allow_multiple_edges(multiedges, check=False) self.add_vertices(data.nodes()) self.add_edges((u,v,r(l)) for u,v,l in data.edges_iter(data=True)) + elif format == 'igraph': + if data.is_directed(): + raise ValueError("An *undirected* igraph graph was expected. "+ + "To build an directed graph, call the DiGraph "+ + "constructor.") + + self.add_vertices(range(data.vcount())) + self.add_edges([(e.source, e.target, e.attributes()) for e in data.es()]) + + if vertex_labels and 'name' in data.vertex_attributes(): + vs = data.vs() + self.relabel({v:vs[v]['name'] for v in self}) + elif format == 'rule': f = data[1] verts = data[0] @@ -1388,91 +1203,22 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_vertices(verts) self.add_edges(e for e in combinations(verts,2) if f(*e)) self.add_edges((v,v) for v in verts if f(v,v)) - elif format == 'dict_of_dicts': - # adjust for empty dicts instead of None in NetworkX default edge labels - if convert_empty_dict_labels_to_None is None: - convert_empty_dict_labels_to_None = (format == 'NX') - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False + elif format == 'dict_of_dicts': + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if hash(u) > hash(v): - if v in data and u in data[v]: - if data[u][v] != data[v][u]: - raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v)) - continue - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - verts = set().union(data.keys(), *data.values()) - self.add_vertices(verts) - if convert_empty_dict_labels_to_None: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v] if data[u][v] != {} else None,False) - else: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v],False) elif format == 'dict_of_lists': - if not all(isinstance(data[u], list) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set().union(data.keys(),*data.values()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: - loops = False - if weighted is None: weighted = False - for u in data: - if len(set(data[u])) != len(data[u]): - if multiedges is False: - v = next((v for v in data[u] if data[u].count(v) > 1)) - raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) - if multiedges is None: - multiedges = True - if multiedges is None: multiedges = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(verts) - for u in data: - for v in data[u]: - if (multiedges or hash(u) <= hash(v) or - v not in data or u not in data[v]): - self._backend.add_edge(u,v,None,False) + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'int': self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) @@ -1533,10 +1279,10 @@ def __init__(self, data=None, pos=None, loops=None, format=None, raise ValueError("Unknown input format '{}'".format(format)) if weighted is None: weighted = False - self._weighted = weighted + self._weighted = getattr(self,'_weighted',weighted) self._pos = pos - self._boundary = boundary if boundary is not None else [] + if format != 'Graph' or name is not None: self.name(name) @@ -1549,6 +1295,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self._immutable = True ### Formats + + @doc_index("Basic methods") def graph6_string(self): """ Returns the graph6 representation of the graph as an ASCII string. @@ -1576,6 +1324,7 @@ def graph6_string(self): else: return generic_graph_pyx.small_integer_to_graph6(n) + generic_graph_pyx.binary_string_to_graph6(self._bit_vector()) + @doc_index("Basic methods") def sparse6_string(self): r""" Returns the sparse6 representation of the graph as an ASCII string. @@ -1664,6 +1413,7 @@ def sparse6_string(self): ### Attributes @combinatorial_map(name="partition of connected components") + @doc_index("Deprecated") def to_partition(self): """ Return the partition of connected components of ``self``. @@ -1684,6 +1434,7 @@ def to_partition(self): from sage.combinat.partition import Partition return Partition(sorted([len(y) for y in self.connected_components()], reverse=True)) + @doc_index("Basic methods") def is_directed(self): """ Since graph is undirected, returns False. @@ -1695,6 +1446,7 @@ def is_directed(self): """ return False + @doc_index("Connectivity, orientations, trees") def bridges(self): r""" Returns a list of the bridges (or cut edges). @@ -1720,6 +1472,7 @@ def bridges(self): bridges.extend(gs.edge_boundary(scc)) return bridges + @doc_index("Connectivity, orientations, trees") def spanning_trees(self): """ Returns a list of all spanning trees. @@ -1749,6 +1502,19 @@ def spanning_trees(self): - :meth:`~sage.graphs.graph.Graph.random_spanning_tree` -- returns a random spanning tree. + TESTS: + + Works with looped graphs:: + + sage: g = Graph({i:[i,(i+1)%6] for i in range(6)}) + sage: g.spanning_trees() + [Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices] + REFERENCES: .. [RT75] Read, R. C. and Tarjan, R. E. @@ -1767,7 +1533,7 @@ def _recursive_spanning_trees(G,forest): return [forest.copy()] else: # Pick an edge e from G-forest - for e in G.edges(): + for e in G.edge_iterator(labels=False): if not forest.has_edge(e): break @@ -1800,11 +1566,12 @@ def _recursive_spanning_trees(G,forest): forest = Graph([]) forest.add_vertices(self.vertices()) forest.add_edges(self.bridges()) - return _recursive_spanning_trees(self, forest) + return _recursive_spanning_trees(Graph(self,immutable=False,loops=False), forest) else: return [] ### Properties + @doc_index("Graph properties") def is_tree(self, certificate=False, output='vertex'): """ Tests if the graph is a tree @@ -1935,6 +1702,7 @@ def vertices_to_edges(x): else: return self.num_verts() == self.num_edges() + 1 + @doc_index("Graph properties") def is_forest(self, certificate=False, output='vertex'): """ Tests if the graph is a forest, i.e. a disjoint union of trees. @@ -1986,6 +1754,7 @@ def is_forest(self, certificate=False, output='vertex'): if not isit: return (False, cycle) + @doc_index("Graph properties") def is_overfull(self): r""" Tests whether the current graph is overfull. @@ -2085,6 +1854,7 @@ def is_overfull(self): return (self.order() % 2 == 1) and ( 2 * self.size() > max(self.degree()) * (self.order() - 1)) + @doc_index("Graph properties") def is_even_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced even hole. @@ -2193,6 +1963,7 @@ def is_even_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_odd_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced odd hole. @@ -2272,6 +2043,7 @@ def is_odd_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_bipartite(self, certificate = False): """ Returns ``True`` if graph `G` is bipartite, ``False`` if not. @@ -2352,6 +2124,7 @@ def is_bipartite(self, certificate = False): else: return True + @doc_index("Graph properties") def is_triangle_free(self, algorithm='bitset'): r""" Returns whether ``self`` is triangle-free @@ -2405,6 +2178,7 @@ def is_triangle_free(self, algorithm='bitset'): ... print "That's not good!" Asking for an unknown algorithm:: + sage: g.is_triangle_free(algorithm='tip top') Traceback (most recent call last): ... @@ -2442,6 +2216,7 @@ def is_triangle_free(self, algorithm='bitset'): else: raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + @doc_index("Graph properties") def is_split(self): r""" Returns ``True`` if the graph is a Split graph, ``False`` otherwise. @@ -2515,6 +2290,7 @@ def is_split(self): return left == right + @doc_index("Algorithmically hard stuff") def treewidth(self,k=None,certificate=False): r""" Computes the tree-width of `G` (and provides a decomposition) @@ -2616,6 +2392,15 @@ def treewidth(self,k=None,certificate=False): ....: g.delete_edges(list(combinations(bag,2))) sage: g.size() 0 + + :trac:`19358`:: + + sage: g = Graph() + sage: for i in range(3): + ....: for j in range(2): + ....: g.add_path([i,(i,j),(i+1)%3]) + sage: g.treewidth() + 2 """ from sage.misc.cachefunc import cached_function from sage.sets.set import Set @@ -2661,9 +2446,8 @@ def rec(cut,cc): if len(cc)+len(cut) <= k+1: return [(cut,cut.union(cc))] if certificate else True - # The list of potential vertices that could be added to the current cut - extensions = {v for u in cut for v in g.neighbors(u) if v in cc} - for v in extensions: + # We explore all possible extensions of the cut + for v in cc: # New cuts and connected components, with v respectively added and # removed @@ -2729,6 +2513,7 @@ def rec(cut,cc): return G + @doc_index("Algorithmically hard stuff") def is_perfect(self, certificate = False): r""" Tests whether the graph is perfect. @@ -2857,6 +2642,7 @@ def is_perfect(self, certificate = False): return self_complement.is_odd_hole_free(certificate = certificate) + @doc_index("Graph properties") def odd_girth(self): r""" Returns the odd girth of self. @@ -2924,6 +2710,7 @@ def odd_girth(self): return Infinity + @doc_index("Graph properties") def is_edge_transitive(self): """ Returns true if self is an edge transitive graph. @@ -2970,6 +2757,7 @@ def is_edge_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnSets);") == self.size() + @doc_index("Graph properties") def is_arc_transitive(self): """ Returns true if self is an arc-transitive graph @@ -3012,6 +2800,7 @@ def is_arc_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnTuples);") == 2*self.size() + @doc_index("Graph properties") def is_half_transitive(self): """ Returns true if self is a half-transitive graph. @@ -3051,6 +2840,7 @@ def is_half_transitive(self): self.is_vertex_transitive() and not self.is_arc_transitive()) + @doc_index("Graph properties") def is_semi_symmetric(self): """ Returns true if self is semi-symmetric. @@ -3095,6 +2885,7 @@ def is_semi_symmetric(self): self.is_edge_transitive() and not self.is_vertex_transitive()) + @doc_index("Connectivity, orientations, trees") def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): r""" Returns a degree-constrained subgraph. @@ -3193,15 +2984,15 @@ def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): ### Orientations + @doc_index("Connectivity, orientations, trees") def strong_orientation(self): r""" - Returns a strongly connected orientation of the current graph. See - also the :wikipedia:`Strongly_connected_component`. + Returns a strongly connected orientation of the current graph. - An orientation of an undirected graph is a digraph obtained by - giving an unique direction to each of its edges. An orientation - is said to be strong if there is a directed path between each - pair of vertices. + An orientation of an undirected graph is a digraph obtained by giving an + unique direction to each of its edges. An orientation is said to be + strong if there is a directed path between each pair of vertices. See + also the :wikipedia:`Strongly_connected_component`. If the graph is 2-edge-connected, a strongly connected orientation can be found in linear time. If the given graph is not 2-connected, @@ -3299,6 +3090,7 @@ def strong_orientation(self): return d + @doc_index("Connectivity, orientations, trees") def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0): r""" Returns an orientation of ``self`` with the smallest possible maximum @@ -3407,6 +3199,7 @@ def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verb return O + @doc_index("Connectivity, orientations, trees") def bounded_outdegree_orientation(self, bound): r""" Computes an orientation of ``self`` such that every vertex `v` @@ -3564,6 +3357,7 @@ def bounded_outdegree_orientation(self, bound): ### Coloring + @doc_index("Basic methods") def bipartite_color(self): """ Returns a dictionary with vertices as the keys and the color class @@ -3585,6 +3379,7 @@ def bipartite_color(self): else: raise RuntimeError("Graph is not bipartite.") + @doc_index("Basic methods") def bipartite_sets(self): """ Returns `(X,Y)` where `X` and `Y` are the nodes in each bipartite set of @@ -3611,6 +3406,7 @@ def bipartite_sets(self): return left, right + @doc_index("Algorithmically hard stuff") def chromatic_number(self, algorithm="DLX", verbose = 0): r""" Returns the minimal number of colors needed to color the vertices @@ -3710,6 +3506,7 @@ def chromatic_number(self, algorithm="DLX", verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.") + @doc_index("Algorithmically hard stuff") def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): r""" Returns the first (optimal) proper vertex-coloring found. @@ -3784,6 +3581,7 @@ def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.") + @doc_index("Algorithmically hard stuff") def chromatic_symmetric_function(self, R=None): r""" Return the chromatic symmetric function of ``self``. @@ -3857,6 +3655,7 @@ def chromatic_symmetric_function(self, R=None): ret += (-1)**len(F) * p[la] return ret + @doc_index("Leftovers") def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0): r""" Returns a maximum weighted matching of the graph @@ -3997,6 +3796,7 @@ def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, else: raise ValueError('algorithm must be set to either "Edmonds" or "LP"') + @doc_index("Algorithmically hard stuff") def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): r""" Checks whether there is a homomorphism between two graphs. @@ -4104,6 +3904,7 @@ def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): except MIPSolverException: return False + @doc_index("Leftovers") def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, verbose = 0): r""" Computes the fractional chromatic index of ``self`` @@ -4224,6 +4025,7 @@ def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, ver # Accomplished ! return obj + @doc_index("Leftovers") def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): r""" Returns the Maximum Average Degree (MAD) of the current graph. @@ -4329,6 +4131,7 @@ def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): else: return g_mad + @doc_index("Algorithmically hard stuff") def independent_set_of_representatives(self, family, solver=None, verbose=0): r""" Returns an independent set of representatives. @@ -4457,6 +4260,7 @@ def independent_set_of_representatives(self, family, solver=None, verbose=0): return repr + @doc_index("Algorithmically hard stuff") def minor(self, H, solver=None, verbose=0): r""" Returns the vertices of a minor isomorphic to `H` in the current graph. @@ -4622,6 +4426,7 @@ def minor(self, H, solver=None, verbose=0): ### Convexity + @doc_index("Algorithmically hard stuff") def convexity_properties(self): r""" Returns a ``ConvexityProperties`` object corresponding to ``self``. @@ -4663,6 +4468,7 @@ def convexity_properties(self): return ConvexityProperties(self) # Centrality + @doc_index("Distances") def centrality_degree(self, v=None): r""" Returns the degree centrality of a vertex. @@ -4677,7 +4483,7 @@ def centrality_degree(self, v=None): .. SEEALSO:: - - :meth:`centrality_closeness` + - :meth:`~sage.graphs.generic_graph.GenericGraph.centrality_closeness` - :meth:`~sage.graphs.generic_graph.GenericGraph.centrality_betweenness` EXAMPLES:: @@ -4708,57 +4514,9 @@ def centrality_degree(self, v=None): else: return self.degree(v)/n_minus_one - def centrality_closeness(self, v=None): - r""" - Returns the closeness centrality of a vertex. - - The closeness centrality of a vertex `v` is equal to the inverse of [the - average distance between `v` and other vertices]. - - Measures of the centrality of a vertex within a graph determine the - relative importance of that vertex to its graph. 'Closeness - centrality may be defined as the total graph-theoretic distance of - a given vertex from all other vertices... Closeness is an inverse - measure of centrality in that a larger value indicates a less - central actor while a smaller value indicates a more central - actor,' [Borgatti95]_. - - For more information, see the :wikipedia:`Centraliy`. - - INPUT: - - - ``v`` - a vertex. Set to ``None`` (default) to get a dictionary - associating each vertex with its centrality closeness. - - .. SEEALSO:: - - - :meth:`centrality_degree` - - :meth:`~sage.graphs.generic_graph.GenericGraph.centrality_betweenness` - - REFERENCE: - - .. [Borgatti95] Stephen P Borgatti. (1995). Centrality and AIDS. - [Online] Available: - http://www.analytictech.com/networks/centaids.htm - - EXAMPLES:: - - sage: (graphs.ChvatalGraph()).centrality_closeness() - {0: 0.61111111111111..., 1: 0.61111111111111..., 2: 0.61111111111111..., 3: 0.61111111111111..., 4: 0.61111111111111..., 5: 0.61111111111111..., 6: 0.61111111111111..., 7: 0.61111111111111..., 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...} - sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) - sage: D.show(figsize=[2,2]) - sage: D = D.to_undirected() - sage: D.show(figsize=[2,2]) - sage: D.centrality_closeness() - {0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75} - sage: D.centrality_closeness(v=1) - 1.0 - """ - import networkx - return networkx.closeness_centrality(self.networkx_graph(copy=False), v) - ### Constructors + @doc_index("Basic methods") def to_directed(self, implementation='c_graph', data_structure=None, sparse=None): """ @@ -4810,7 +4568,6 @@ def to_directed(self, implementation='c_graph', data_structure=None, from sage.graphs.all import DiGraph D = DiGraph(name = self.name(), pos = self._pos, - boundary = self._boundary, multiedges = self.allows_multiple_edges(), loops = self.allows_loops(), implementation = implementation, @@ -4830,6 +4587,7 @@ def to_directed(self, implementation='c_graph', data_structure=None, return D + @doc_index("Basic methods") def to_undirected(self): """ Since the graph is already undirected, simply returns a copy of @@ -4842,9 +4600,10 @@ def to_undirected(self): """ return self.copy() + @doc_index("Basic methods") def join(self, other, verbose_relabel=None, labels="pairs"): """ - Returns the join of self and other. + Returns the join of ``self`` and ``other``. INPUT: @@ -4910,8 +4669,141 @@ def join(self, other, verbose_relabel=None, labels="pairs"): G.name('%s join %s'%(self.name(), other.name())) return G + @doc_index("Leftovers") + def seidel_adjacency_matrix(self, vertices=None): + r""" + Returns the Seidel adjacency matrix of ``self``. + + Returns `J-I-2A`, for `A` the (ordinary) + :meth:`adjacency matrix ` of ``self``, + `I` the identity matrix, and `J` the all-1 matrix. + It is closely related to :meth:`twograph`. + + The matrix returned is over the integers. If a different ring is + desired, use either :meth:`sage.matrix.matrix0.Matrix.change_ring` + method or :func:`matrix` function. + + INPUT: + + - ``vertices`` (list) -- the ordering of the vertices defining how they + should appear in the matrix. By default, the ordering given by + :meth:`GenericGraph.vertices` is used. + + EXAMPLES:: + + sage: G = graphs.CycleGraph(5) + sage: G = G.disjoint_union(graphs.CompleteGraph(1)) + sage: G.seidel_adjacency_matrix().minpoly() + x^2 - 5 + """ + + return -self.adjacency_matrix(sparse=False, vertices=vertices)+ \ + self.complement().adjacency_matrix(sparse=False, \ + vertices=vertices) + + @doc_index("Leftovers") + def seidel_switching(self, s, inplace=True): + r""" + Returns the Seidel switching of ``self`` w.r.t. subset of vertices ``s``. + + Returns the graph obtained by Seidel switching of ``self`` + with respect to the subset of vertices ``s``. This is the graph + given by Seidel adjacency matrix `DSD`, for `S` the Seidel + adjacency matrix of ``self``, and `D` the diagonal matrix with -1s + at positions corresponding to ``s``, and 1s elsewhere. + + INPUT: + + - ``s`` -- a list of vertices of ``self`` + + - ``inplace`` (boolean) -- whether to do the modification inplace, or to + return a copy of the graph after switching. + + EXAMPLES:: + + sage: G = graphs.CycleGraph(5) + sage: G = G.disjoint_union(graphs.CompleteGraph(1)) + sage: G.seidel_switching([(0,1),(1,0),(0,0)]) + sage: G.seidel_adjacency_matrix().minpoly() + x^2 - 5 + sage: G.is_connected() + True + + TESTS:: + + sage: H = G.seidel_switching([1,4,5],inplace=False) + sage: G.seidel_switching([1,4,5]) + sage: G == H + True + """ + from itertools import product + G = self if inplace else self.copy() + boundary = self.edge_boundary(s) + G.add_edges(product(s, set(self).difference(s))) + G.delete_edges(boundary) + if not inplace: + return G + + @doc_index("Leftovers") + def twograph(self): + r""" + Returns the two-graph of ``self`` + + Returns the :class:`two-graph ` + with the triples + `T=\{t \in \binom {V}{3} : \left| \binom {t}{2} \cap E \right| \text{odd} \}` + where `V` and `E` are vertices and edges of ``self``, respectively. + + EXAMPLES:: + + sage: p=graphs.PetersenGraph() + sage: p.twograph() + Incidence structure with 10 points and 60 blocks + sage: p=graphs.chang_graphs() + sage: T8 = graphs.CompleteGraph(8).line_graph() + sage: C = T8.seidel_switching([(0,1,None),(2,3,None),(4,5,None),(6,7,None)],inplace=False) + sage: T8.twograph()==C.twograph() + True + sage: T8.is_isomorphic(C) + False + + TESTS:: + + sage: from sage.combinat.designs.twographs import TwoGraph + sage: p=graphs.PetersenGraph().twograph() + sage: TwoGraph(p, check=True) + Incidence structure with 10 points and 60 blocks + + .. SEEALSO:: + + - :meth:`~sage.combinat.designs.twographs.TwoGraph.descendant` + -- computes the descendant graph of the two-graph of self at a vertex + + - :func:`~sage.combinat.designs.twographs.twograph_descendant` + -- ditto, but much faster. + """ + from sage.combinat.designs.twographs import TwoGraph + G = self.relabel(inplace=False) + T = [] + + # Triangles + for x,y,z in G.subgraph_search_iterator(Graph({1:[2,3],2:[3]})): + if x < y and y < z: + T.append([x,y,z]) + + # Triples with just one edge + for x,y,z in G.subgraph_search_iterator(Graph({1:[2],3:[]}),induced=True): + if x < y: + T.append([x,y,z]) + + T = TwoGraph(T) + T.relabel({i:v for i,v in enumerate(self.vertices())}) + + return T + ### Visualization + @doc_index("Basic methods") def write_to_eps(self, filename, **options): r""" Writes a plot of the graph to ``filename`` in ``eps`` format. @@ -4927,9 +4819,9 @@ def write_to_eps(self, filename, **options): sage: P.write_to_eps(tmp_filename(ext='.eps')) It is relatively simple to include this file in a LaTeX - document. ``\usepackagegraphics`` must appear in the - preamble, and ``\includegraphics{filename.eps}`` will include - the file. To compile the document to ``pdf`` with ``pdflatex`` + document. ``\usepackage{graphics}`` must appear in the + preamble, and ``\includegraphics{filename}`` will include + the file. To compile the document to ``pdf`` with ``pdflatex`` or ``xelatex`` the file needs first to be converted to ``pdf``, for example with ``ps2pdf filename.eps filename.pdf``. """ @@ -4944,6 +4836,7 @@ def write_to_eps(self, filename, **options): f.write( print_graph_eps(self.vertices(), self.edge_iterator(), pos) ) f.close() + @doc_index("Algorithmically hard stuff") def topological_minor(self, H, vertices = False, paths = False, solver=None, verbose=0): r""" Returns a topological `H`-minor from ``self`` if one exists. @@ -5175,6 +5068,7 @@ def topological_minor(self, H, vertices = False, paths = False, solver=None, ver ### Cliques + @doc_index("Clique-related methods") def cliques_maximal(self, algorithm = "native"): """ Returns the list of all maximal cliques, with each clique represented @@ -5250,6 +5144,7 @@ def cliques_maximal(self, algorithm = "native"): else: raise ValueError("Algorithm must be equal to 'native' or to 'NetworkX'.") + @doc_index("Clique-related methods") def clique_maximum(self, algorithm="Cliquer"): """ Returns the vertex set of a maximal order complete subgraph. @@ -5320,6 +5215,7 @@ def clique_maximum(self, algorithm="Cliquer"): else: raise NotImplementedError("Only 'MILP', 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def clique_number(self, algorithm="Cliquer", cliques=None): r""" Returns the order of the largest clique of the graph (the clique @@ -5410,6 +5306,7 @@ def clique_number(self, algorithm="Cliquer", cliques=None): else: raise NotImplementedError("Only 'networkx' 'MILP' 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def cliques_number_of(self, vertices=None, cliques=None): """ Returns a dictionary of the number of maximal cliques containing each @@ -5460,6 +5357,7 @@ def cliques_number_of(self, vertices=None, cliques=None): import networkx return networkx.number_of_cliques(self.networkx_graph(copy=False), vertices, cliques) + @doc_index("Clique-related methods") def cliques_get_max_clique_graph(self, name=''): """ Returns a graph constructed with maximal cliques as vertices, and @@ -5491,6 +5389,7 @@ def cliques_get_max_clique_graph(self, name=''): import networkx return Graph(networkx.make_max_clique_graph(self.networkx_graph(copy=False), name=name, create_using=networkx.MultiGraph())) + @doc_index("Clique-related methods") def cliques_get_clique_bipartite(self, **kwds): """ Returns a bipartite graph constructed such that maximal cliques are the @@ -5518,6 +5417,7 @@ def cliques_get_clique_bipartite(self, **kwds): import networkx return BipartiteGraph(networkx.make_clique_bipartite(self.networkx_graph(copy=False), **kwds)) + @doc_index("Algorithmically hard stuff") def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" Returns a maximum independent set. @@ -5607,6 +5507,7 @@ def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_r return [u for u in self.vertices() if not u in my_cover] + @doc_index("Algorithmically hard stuff") def vertex_cover(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" @@ -5891,6 +5792,7 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, cover_g.sort() return cover_g + @doc_index("Clique-related methods") def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, cliques=None): """ @@ -5962,6 +5864,7 @@ def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, else: raise NotImplementedError("Only 'networkx' and 'cliquer' are supported.") + @doc_index("Clique-related methods") def cliques_containing_vertex(self, vertices=None, cliques=None): """ Returns the cliques containing each vertex, represented as a dictionary @@ -6012,6 +5915,7 @@ def cliques_containing_vertex(self, vertices=None, cliques=None): import networkx return networkx.cliques_containing_node(self.networkx_graph(copy=False),vertices, cliques) + @doc_index("Clique-related methods") def clique_complex(self): """ Returns the clique complex of self. This is the largest simplicial complex on @@ -6043,6 +5947,7 @@ def clique_complex(self): C._graph = self return C + @doc_index("Clique-related methods") def clique_polynomial(self, t = None): """ Returns the clique polynomial of self. @@ -6074,6 +5979,7 @@ def clique_polynomial(self, t = None): ### Miscellaneous + @doc_index("Leftovers") def cores(self, k = None, with_labels=False): """ Returns the core number for each vertex in an ordered list. @@ -6231,6 +6137,7 @@ def cores(self, k = None, with_labels=False): else: return core.values() + @doc_index("Leftovers") def modular_decomposition(self): r""" Returns the modular decomposition of the current graph. @@ -6373,6 +6280,7 @@ def modular_decomposition(self): return relabel(D) + @doc_index("Graph properties") def is_prime(self): r""" Tests whether the current graph is prime. @@ -6500,6 +6408,7 @@ def _gomory_hu_tree(self, vertices, method="FF"): return g + @doc_index("Connectivity, orientations, trees") def gomory_hu_tree(self, method="FF"): r""" Returns a Gomory-Hu tree of self. @@ -6576,6 +6485,8 @@ def gomory_hu_tree(self, method="FF"): :trac:`16475`:: sage: G = graphs.PetersenGraph() + sage: for u,v in G.edge_iterator(labels=False): + ....: G.set_edge_label(u, v, 1) sage: for u, v in [(0, 1), (0, 4), (0, 5), (1, 2), (1, 6), (3, 4), (5, 7), (5, 8)]: ....: G.set_edge_label(u, v, 2) sage: T = G.gomory_hu_tree() @@ -6594,6 +6505,7 @@ def gomory_hu_tree(self, method="FF"): g.set_pos(dict(self.get_pos())) return g + @doc_index("Leftovers") def two_factor_petersen(self): r""" Returns a decomposition of the graph into 2-factors. @@ -6663,6 +6575,7 @@ def two_factor_petersen(self): return classes_b + @doc_index("Leftovers") def kirchhoff_symanzik_polynomial(self, name='t'): """ Return the Kirchhoff-Symanzik polynomial of a graph. @@ -6762,21 +6675,25 @@ def kirchhoff_symanzik_polynomial(self, name='t'): D = matrix.diagonal(PolynomialRing(ZZ, name, self.size()).gens()) return (circuit_mtrx.transpose() * D * circuit_mtrx).determinant() + @doc_index("Leftovers") def ihara_zeta_function_inverse(self): """ - Compute the inverse of the Ihara zeta function of the graph + Compute the inverse of the Ihara zeta function of the graph. This is a polynomial in one variable with integer coefficients. The Ihara zeta function itself is the inverse of this polynomial. - See :wikipedia:`Ihara zeta function` + See :wikipedia:`Ihara zeta function`. ALGORITHM: - This is computed here using the determinant of a square matrix - of size twice the number of edges, related to the adjacency - matrix of the line graph, see for example Proposition 9 - in [ScottStorm]_. + This is computed here as the (reversed) characteristic + polynomial of a square matrix of size twice the number of edges, + related to the adjacency matrix of the line graph, see for example + Proposition 9 in [ScottStorm]_ and Def. 4.1 in [Terras]_. + + The graph is first replaced by its 2-core, as this does not change + the Ihara zeta function. EXAMPLES:: @@ -6811,68 +6728,88 @@ def ihara_zeta_function_inverse(self): of the Ihara zeta function, Involve (http://msp.org/involve/2008/1-2/involve-v1-n2-p08-p.pdf) """ from sage.matrix.constructor import matrix - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - - ring = PolynomialRing(ZZ, 't') - t = ring.gen() - - N = self.size() - - labeled_g = DiGraph() - labeled_g.add_edges([(u, v, i) for i, (u, v) in - enumerate(self.edges(labels=False))]) - labeled_g.add_edges([(v, u, i + N) for i, (u, v) in - enumerate(self.edges(labels=False))]) - M = matrix(ring, 2 * N, 2 * N, ring.one()) - for u, v, i in labeled_g.edges(): - for vv, ww, j in labeled_g.outgoing_edges(v): - M[i, j] += -t - M[i, (i + N) % (2 * N)] += t # fixing the 2-cycles + H = self.subgraph(vertices=self.cores(k=2)[1]) + E = H.edges() + m = len(E) + # compute (Hashimoto) edge matrix T + T = matrix(ZZ, 2 * m, 2 * m, 0) + for i in range(m): + for j in range(m): + if i != j: + if E[i][1] == E[j][0]: # same orientation + T[2 * i, 2 * j] = 1 + T[2 * j + 1, 2 * i + 1] = 1 + elif E[i][1] == E[j][1]: # opposite orientation (towards) + T[2 * i, 2 * j + 1] = 1 + T[2 * j, 2 * i + 1] = 1 + elif E[i][0] == E[j][0]: # opposite orientation (away) + T[2 * i + 1, 2 * j] = 1 + T[2 * j + 1, 2 * i] = 1 + return T.charpoly('t').reverse() - return M.determinant() # Aliases to functions defined in Cython modules import types import sage.graphs.weakly_chordal -Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) -Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) -Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) +Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) +Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) +Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) import sage.graphs.asteroidal_triples Graph.is_asteroidal_triple_free = types.MethodType(sage.graphs.asteroidal_triples.is_asteroidal_triple_free, None, Graph) import sage.graphs.chrompoly -Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) +Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) import sage.graphs.graph_decompositions.rankwidth -Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) +Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) import sage.graphs.matchpoly -Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) +Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) import sage.graphs.cliquer -Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) +Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) import sage.graphs.spanning_tree -Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) +Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) import sage.graphs.graph_decompositions.graph_products -Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) +Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) import sage.graphs.distances_all_pairs -Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) +Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) import sage.graphs.base.static_dense_graph -Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) +Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) # From Python modules import sage.graphs.line_graph -Graph.is_line_graph = sage.graphs.line_graph.is_line_graph +Graph.is_line_graph = sage.graphs.line_graph.is_line_graph from sage.graphs.tutte_polynomial import tutte_polynomial -Graph.tutte_polynomial = tutte_polynomial +Graph.tutte_polynomial = tutte_polynomial from sage.graphs.lovasz_theta import lovasz_theta -Graph.lovasz_theta = lovasz_theta +Graph.lovasz_theta = lovasz_theta + +_additional_categories = { + Graph.is_long_hole_free : "Graph properties", + Graph.is_long_antihole_free : "Graph properties", + Graph.is_weakly_chordal : "Graph properties", + Graph.is_asteroidal_triple_free : "Graph properties", + Graph.chromatic_polynomial : "Algorithmically hard stuff", + Graph.rank_decomposition : "Algorithmically hard stuff", + Graph.matching_polynomial : "Algorithmically hard stuff", + Graph.cliques_maximum : "Clique-related methods", + Graph.random_spanning_tree : "Connectivity, orientations, trees", + Graph.is_cartesian_product : "Graph properties", + Graph.is_distance_regular : "Graph properties", + Graph.is_strongly_regular : "Graph properties", + Graph.is_line_graph : "Graph properties", + Graph.tutte_polynomial : "Algorithmically hard stuff", + Graph.lovasz_theta : "Leftovers", + } + +__doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) diff --git a/src/sage/graphs/graph_decompositions/bandwidth.pyx b/src/sage/graphs/graph_decompositions/bandwidth.pyx index c381d894cf4..e9361b4f5e6 100644 --- a/src/sage/graphs/graph_decompositions/bandwidth.pyx +++ b/src/sage/graphs/graph_decompositions/bandwidth.pyx @@ -86,6 +86,17 @@ hope to get better performances. There is some symmetry to break as the reverse of a satisfiable ordering is also a satisfiable ordering. +This module contains the following methods +------------------------------------------ + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`bandwidth` | Compute the bandwidth of an undirected graph + :meth:`~sage.graphs.base.boost_graph.bandwidth_heuristics` | Uses Boost heuristics to approximate the bandwidth of the input graph + Functions --------- """ @@ -99,6 +110,7 @@ include 'sage/ext/interrupt.pxi' from libc.stdint cimport uint16_t from sage.graphs.distances_all_pairs cimport all_pairs_shortest_path_BFS +from sage.graphs.base.boost_graph import bandwidth_heuristics from sage.ext.memory_allocator cimport MemoryAllocator ctypedef uint16_t index_t diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index f251749bbd6..477400f23b3 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -155,13 +155,16 @@ def __append_to_doc(methods): "SzekeresSnarkGraph", "ThomsenGraph", "TietzeGraph", + "TruncatedIcosidodecahedralGraph", + "TruncatedTetrahedralGraph", "Tutte12Cage", "TutteCoxeterGraph", "TutteGraph", "WagnerGraph", "WatkinsSnarkGraph", "WellsGraph", - "WienerArayaGraph"]) + "WienerArayaGraph", + "SuzukiGraph"]) __doc__ += """ **Platonic solids** (ordered ascending by number of vertices) @@ -184,8 +187,7 @@ def __append_to_doc(methods): """ __append_to_doc( - ["AffineOrthogonalPolarGraph", - "BalancedTree", + ["BalancedTree", "BarbellGraph", "BubbleSortGraph", "chang_graphs", @@ -200,6 +202,7 @@ def __append_to_doc(methods): "fusenes", "FuzzyBallGraph", "GeneralizedPetersenGraph", + "GoethalsSeidelGraph", "HanoiTowerGraph", "HararyGraph", "HyperStarGraph", @@ -212,18 +215,39 @@ def __append_to_doc(methods): "NKStarGraph", "NStarGraph", "OddGraph", - "OrthogonalPolarGraph", "PaleyGraph", "petersen_family", "planar_graphs", "quadrangulations", "RingedTree", "SierpinskiGasketGraph", - "SymplecticGraph", + "strongly_regular_graph", "trees", "triangulations", "WheelGraph"]) + +__doc__ += """ +**Graphs from classical geometries over finite fields** + +A number of classes of graphs related to geometries over finite fields and +quadrics and Hermitean varieties there. +""" + +__append_to_doc( + ["AffineOrthogonalPolarGraph", + "AhrensSzekeresGeneralizedQuadrangleGraph", + "NonisotropicOrthogonalPolarGraph", + "NonisotropicUnitaryPolarGraph", + "OrthogonalPolarGraph", + "SymplecticDualPolarGraph", + "SymplecticPolarGraph", + "TaylorTwographDescendantSRG", + "TaylorTwographSRG", + "T2starGeneralizedQuadrangleGraph", + "UnitaryDualPolarGraph", + "UnitaryPolarGraph"]) + __doc__ += """ **Chessboard Graphs** """ @@ -366,6 +390,7 @@ def __append_to_doc(methods): # import from Sage library import graph +import sage.graphs.strongly_regular_db class GraphGenerators(): r""" @@ -1915,11 +1940,14 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None ThomsenGraph = staticmethod(sage.graphs.generators.smallgraphs.ThomsenGraph) TietzeGraph = staticmethod(sage.graphs.generators.smallgraphs.TietzeGraph) Tutte12Cage = staticmethod(sage.graphs.generators.smallgraphs.Tutte12Cage) + TruncatedIcosidodecahedralGraph = staticmethod(sage.graphs.generators.smallgraphs.TruncatedIcosidodecahedralGraph) + TruncatedTetrahedralGraph= staticmethod(sage.graphs.generators.smallgraphs.TruncatedTetrahedralGraph) TutteCoxeterGraph = staticmethod(sage.graphs.generators.smallgraphs.TutteCoxeterGraph) TutteGraph = staticmethod(sage.graphs.generators.smallgraphs.TutteGraph) WagnerGraph = staticmethod(sage.graphs.generators.smallgraphs.WagnerGraph) WatkinsSnarkGraph = staticmethod(sage.graphs.generators.smallgraphs.WatkinsSnarkGraph) WienerArayaGraph = staticmethod(sage.graphs.generators.smallgraphs.WienerArayaGraph) + SuzukiGraph = staticmethod(sage.graphs.generators.smallgraphs.SuzukiGraph) ########################################################################### # Platonic Solids @@ -1935,7 +1963,6 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None # Families ########################################################################### import sage.graphs.generators.families - AffineOrthogonalPolarGraph = staticmethod(sage.graphs.generators.families.AffineOrthogonalPolarGraph) BalancedTree = staticmethod(sage.graphs.generators.families.BalancedTree) BarbellGraph = staticmethod(sage.graphs.generators.families.BarbellGraph) BubbleSortGraph = staticmethod(sage.graphs.generators.families.BubbleSortGraph) @@ -1948,6 +1975,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None FriendshipGraph = staticmethod(sage.graphs.generators.families.FriendshipGraph) FuzzyBallGraph = staticmethod(sage.graphs.generators.families.FuzzyBallGraph) GeneralizedPetersenGraph = staticmethod(sage.graphs.generators.families.GeneralizedPetersenGraph) + GoethalsSeidelGraph = staticmethod(sage.graphs.generators.families.GoethalsSeidelGraph) HanoiTowerGraph = staticmethod(sage.graphs.generators.families.HanoiTowerGraph) HararyGraph = staticmethod(sage.graphs.generators.families.HararyGraph) HyperStarGraph = staticmethod(sage.graphs.generators.families.HyperStarGraph) @@ -1960,15 +1988,33 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None NKStarGraph = staticmethod(sage.graphs.generators.families.NKStarGraph) NStarGraph = staticmethod(sage.graphs.generators.families.NStarGraph) OddGraph = staticmethod(sage.graphs.generators.families.OddGraph) - OrthogonalPolarGraph = staticmethod(sage.graphs.generators.families.OrthogonalPolarGraph) PaleyGraph = staticmethod(sage.graphs.generators.families.PaleyGraph) petersen_family = staticmethod(sage.graphs.generators.families.petersen_family) RingedTree = staticmethod(sage.graphs.generators.families.RingedTree) SierpinskiGasketGraph = staticmethod(sage.graphs.generators.families.SierpinskiGasketGraph) - SymplecticGraph = staticmethod(sage.graphs.generators.families.SymplecticGraph) + strongly_regular_graph = staticmethod(sage.graphs.strongly_regular_db.strongly_regular_graph) trees = staticmethod(sage.graphs.generators.families.trees) WheelGraph = staticmethod(sage.graphs.generators.families.WheelGraph) +########################################################################### +# Graphs from classical geometries over `F_q` +########################################################################### + import sage.graphs.generators.classical_geometries + AffineOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.AffineOrthogonalPolarGraph) + AhrensSzekeresGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.AhrensSzekeresGeneralizedQuadrangleGraph) + NonisotropicOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph) + NonisotropicUnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicUnitaryPolarGraph) + OrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.OrthogonalPolarGraph) + SymplecticDualPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.SymplecticDualPolarGraph) + SymplecticGraph = staticmethod(sage.graphs.generators.classical_geometries.SymplecticGraph) + SymplecticPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.SymplecticPolarGraph) + TaylorTwographDescendantSRG = \ + staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographDescendantSRG) + TaylorTwographSRG = staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographSRG) + T2starGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.T2starGeneralizedQuadrangleGraph) + UnitaryDualPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryDualPolarGraph) + UnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryPolarGraph) + ########################################################################### # Chessboard Graphs ########################################################################### diff --git a/src/sage/graphs/graph_input.py b/src/sage/graphs/graph_input.py new file mode 100644 index 00000000000..3692458a517 --- /dev/null +++ b/src/sage/graphs/graph_input.py @@ -0,0 +1,527 @@ +r""" +Functions for reading/building graphs/digraphs. + +This module gathers functions needed to build a graph from any other data. + +.. NOTE:: + + This is an **internal** module of Sage. All features implemented here are + made available to end-users through the constructors of :class:`Graph` and + :class:`DiGraph`. + +Note that because they are called by the constructors of :class:`Graph` and +:class:`DiGraph`, most of these functions modify a graph inplace. + +{INDEX_OF_FUNCTIONS} + +Functions +--------- + +""" + +def from_graph6(G, g6_string): + r""" + Fill ``G`` with the data of a graph6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a graph6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_graph6 + sage: g = Graph() + sage: from_graph6(g, 'IheA@GUAo') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_graph6 + + if not isinstance(g6_string, str): + raise ValueError('If input format is graph6, then g6_string must be a string.') + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + ss = g6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_graph6(s, n) + expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(i): + if m[k] == '1': + G._backend.add_edge(i, j, None, False) + k += 1 + +def from_sparse6(G, g6_string): + r""" + Fill ``G`` with the data of a sparse6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a sparse6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_sparse6 + sage: g = Graph() + sage: from_sparse6(g, ':I`ES@obGkqegW~') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, int_to_binary_string + from math import ceil, floor + from sage.misc.functional import log + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + s = g6_string[:n] + n, s = length_and_string_from_graph6(s[1:]) + if n == 0: + edges = [] + else: + k = int(ceil(log(n,2))) + ords = [ord(i) for i in s] + if any(o > 126 or o < 63 for o in ords): + raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) + bits = ''.join([int_to_binary_string(o-63).zfill(6) for o in ords]) + b = [] + x = [] + for i in xrange(int(floor(len(bits)/(k+1)))): + b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) + x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) + v = 0 + edges = [] + for i in xrange(len(b)): + if b[i] == 1: + v += 1 + if x[i] > v: + v = x[i] + else: + if v < n: + edges.append((x[i],v)) + G.add_vertices(range(n)) + G.add_edges(edges) + +def from_dig6(G, dig6_string): + r""" + Fill ``G`` with the data of a dig6 string. + + INPUT: + + - ``G`` -- a graph + + - ``dig6_string`` -- a dig6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dig6 + sage: g = DiGraph() + sage: from_dig6(g, digraphs.Circuit(10).dig6_string()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_dig6 + if not isinstance(dig6_string, str): + raise ValueError('If input format is dig6, then dig6_string must be a string.') + n = dig6_string.find('\n') + if n == -1: + n = len(dig6_string) + ss = dig6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_dig6(s, n) + expected = n**2 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(n): + if m[k] == '1': + G._backend.add_edge(i, j, None, True) + k += 1 + +def from_seidel_adjacency_matrix(G, M): + r""" + Fill ``G`` with the data of a Seidel adjacency matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a Seidel adjacency matrix + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_seidel_adjacency_matrix + sage: g = Graph() + sage: from_seidel_adjacency_matrix(g, graphs.PetersenGraph().seidel_adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + + if M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if any(e < -1 or e > 1 for e in entries): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + if any(i==j for i,j in M.nonzero_positions()): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have 0s on the main diagonal") + if not M.is_symmetric(): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " be symmetric") + G.add_vertices(range(M.nrows())) + e = [] + for i,j in M.nonzero_positions(): + if i <= j and M[i,j] < 0: + e.append((i,j)) + G.add_edges(e) + +def from_adjacency_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an adjacency matrix. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``M`` -- an adjacency matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_adjacency_matrix + sage: g = Graph() + sage: from_adjacency_matrix(g, graphs.PetersenGraph().adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + # note: the adjacency matrix might be weighted and hence not + # necessarily consists of integers + if not weighted and M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + if weighted is False: + raise ValueError("Non-weighted graph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if not weighted and any(e < 0 for e in entries): + if weighted is False: + raise ValueError("Non-weighted digraph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + if multiedges is None: multiedges = False + if weighted is None: + weighted = False + + if multiedges is None: + multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + + if not loops and any(M[i,i] for i in xrange(M.nrows())): + if loops is False: + raise ValueError("Non-looped digraph's adjacency"+ + " matrix must have zeroes on the diagonal.") + loops = True + if loops is None: + loops = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + e = [] + if G.is_directed(): + pairs = M.nonzero_positions() + else: + pairs = ((i,j) for i,j in M.nonzero_positions() if i<=j) + if weighted: + for i,j in pairs: + e.append((i,j,M[i][j])) + elif multiedges: + for i,j in pairs: + e += [(i,j)]*int(M[i][j]) + else: + for i,j in pairs: + e.append((i,j)) + G.add_edges(e) + G._weighted = weighted + +def from_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an incidence matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_incidence_matrix + sage: g = Graph() + sage: from_incidence_matrix(g, graphs.PetersenGraph().incidence_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + oriented = any(M[pos] < 0 for pos in M.nonzero_positions(copy=False)) + + positions = [] + for i in range(M.ncols()): + NZ = M.nonzero_positions_in_column(i) + if len(NZ) == 1: + if oriented: + raise ValueError("Column {} of the (oriented) incidence " + "matrix contains only one nonzero value".format(i)) + elif M[NZ[0],i] != 2: + raise ValueError("Each column of a non-oriented incidence " + "matrix must sum to 2, but column {} does not".format(i)) + if loops is None: + loops = True + positions.append((NZ[0],NZ[0])) + elif len(NZ) != 2 or \ + (oriented and not ((M[NZ[0],i] == +1 and M[NZ[1],i] == -1) or \ + (M[NZ[0],i] == -1 and M[NZ[1],i] == +1))) or \ + (not oriented and (M[NZ[0],i] != 1 or M[NZ[1],i] != 1)): + msg = "There must be one or two nonzero entries per column in an incidence matrix. " + msg += "Got entries {} in column {}".format([M[j,i] for j in NZ], i) + raise ValueError(msg) + else: + positions.append(tuple(NZ)) + + if weighted is None: G._weighted = False + if multiedges is None: + total = len(positions) + multiedges = (len(set(positions)) < total ) + G.allow_loops(False if loops is None else loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_oriented_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an *oriented* incidence matrix. + + An oriented incidence matrix is the incidence matrix of a directed graph, in + which each non-loop edge corresponds to a `+1` and a `-1`, indicating its + source and destination. + + INPUT: + + - ``G`` -- a :class:`DiGraph` + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_oriented_incidence_matrix + sage: g = DiGraph() + sage: from_oriented_incidence_matrix(g, digraphs.Circuit(10).incidence_matrix()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + positions = [] + for c in M.columns(): + NZ = c.nonzero_positions() + if len(NZ) != 2: + raise ValueError("There must be two nonzero entries (-1 & 1) per column.") + L = sorted(set(c.list())) + if L != [-1,0,1]: + msg += "Each column represents an edge: -1 goes to 1." + raise ValueError(msg) + if c[NZ[0]] == -1: + positions.append(tuple(NZ)) + else: + positions.append((NZ[1],NZ[0])) + if weighted is None: weighted = False + if multiedges is None: + total = len(positions) + multiedges = ( len(set(positions)) < total ) + G.allow_loops(True if loops else False,check=False) + G.allow_multiple_edges(multiedges,check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_dict_of_dicts(G, M, loops=False, multiedges=False, weighted=False, convert_empty_dict_labels_to_None=False): + r""" + Fill ``G`` with the data of a dictionary of dictionaries. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a dictionary of dictionaries. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + - ``convert_empty_dict_labels_to_None`` (boolean) -- whether to adjust for + empty dicts instead of None in NetworkX default edge labels. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_dicts + sage: g = Graph() + sage: from_dict_of_dicts(g, graphs.PetersenGraph().to_dictionary(edge_labels=True)) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + if not all(isinstance(M[u], dict) for u in M): + raise ValueError("Input dict must be a consistent format.") + + if not loops and any(u in neighb for u,neighb in M.iteritems()): + if loops is False: + u = next(u for u,neighb in M.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input M has a loop at {}.".format(u)) + loops = True + if loops is None: + loops = False + + if weighted is None: G._weighted = False + for u in M: + for v in M[u]: + if multiedges is not False and not isinstance(M[u][v], list): + if multiedges is None: multiedges = False + if multiedges: + raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") + if multiedges is None and len(M) > 0: + multiedges = True + + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + verts = set().union(M.keys(), *M.values()) + G.add_vertices(verts) + if convert_empty_dict_labels_to_None: + relabel = lambda x : x if x!={} else None + else: + relabel = lambda x : x + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in M: + for v in M[u]: + if v_to_id[u] <= v_to_id[v] or v not in M or u not in M[v] or u == v: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),False) + elif multiedges: + for u in M: + for v in M[u]: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),is_directed) + else: + for u in M: + for v in M[u]: + G._backend.add_edge(u,v,relabel(M[u][v]),is_directed) + +def from_dict_of_lists(G, D, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of a dictionary of lists. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``D`` -- a dictionary of lists. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_lists + sage: g = Graph() + sage: from_dict_of_lists(g, graphs.PetersenGraph().to_dictionary()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + verts = set().union(D.keys(),*D.values()) + if loops is None or loops is False: + for u in D: + if u in D[u]: + if loops is None: + loops = True + elif loops is False: + u = next(u for u,neighb in D.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input D has a loop at {}.".format(u)) + break + if loops is None: + loops = False + if weighted is None: G._weighted = False + for u in D: + if len(set(D[u])) != len(D[u]): + if multiedges is False: + v = next((v for v in D[u] if D[u].count(v) > 1)) + raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) + if multiedges is None: + multiedges = True + if multiedges is None: multiedges = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(verts) + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in D: + for v in D[u]: + if (v_to_id[u] <= v_to_id[v] or + v not in D or u not in D[v] or u == v): + G._backend.add_edge(u,v,None,False) + else: + for u in D: + for v in D[u]: + G._backend.add_edge(u,v,None,is_directed) + +from sage.misc.rest_index_of_methods import gen_rest_table_index +import sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__])) diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index 2008c216ab0..3a68ca639d2 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -356,17 +356,6 @@ def set_vertices(self, **vertex_options): vertex_colors = {} for i in range(l): vertex_colors[R[i]] = partition[i] - elif len(self._graph._boundary) != 0: - vertex_colors = {} - bdy_verts = [] - int_verts = [] - for v in self._graph.vertex_iterator(): - if v in self._graph._boundary: - bdy_verts.append(v) - else: - int_verts.append(v) - vertex_colors['#fec7b8'] = int_verts - vertex_colors['#b3e8ff'] = bdy_verts elif not vertex_colors: vertex_colors='#fec7b8' else: diff --git a/src/sage/graphs/graph_plot_js.py b/src/sage/graphs/graph_plot_js.py index be2fc1ad22e..40cb0596e31 100644 --- a/src/sage/graphs/graph_plot_js.py +++ b/src/sage/graphs/graph_plot_js.py @@ -50,9 +50,8 @@ Since the d3js package is not standard yet, the javascript is fetched from d3js.org website by the browser. If you want to avoid that (e.g. to protect your privacy or by lack of internet connection), you can install - the d3js package for offline use with the Sage command - ``install_package('d3js')`` or by running ``sage -i d3js`` from the command - line. + the d3js package for offline use by running ``sage -i d3js`` from + the command line. .. TODO:: @@ -122,9 +121,8 @@ def gen_html_code(G, - ``edge_partition`` -- same as ``vertex_partition``, with edges instead. Set to ``[]`` by default. - - ``force_spring_layout`` -- whether to take sage's position into account if - there is one (see :meth:`~sage.graphs.generic_graph.GenericGraph.` and - :meth:`~sage.graphs.generic_graph.GenericGraph.`), or to compute a spring + - ``force_spring_layout`` -- whether to take previously computed position + of nodes into account if there is one, or to compute a spring layout. Set to ``False`` by default. - ``vertex_size`` -- The size of a vertex' circle. Set to `7` by default. @@ -152,9 +150,8 @@ def gen_html_code(G, Since the d3js package is not standard yet, the javascript is fetched from d3js.org website by the browser. If you want to avoid that (e.g. to protect your privacy or by lack of internet connection), you can - install the d3js package for offline use with the Sage command - ``install_package('d3js')`` or by running ``sage -i d3js`` from the - command line. + install the d3js package for offline use by running + ``sage -i d3js`` from the command line. EXAMPLES:: diff --git a/src/sage/graphs/hyperbolicity.pyx b/src/sage/graphs/hyperbolicity.pyx index b55a1d890d1..07d3b4703f4 100644 --- a/src/sage/graphs/hyperbolicity.pyx +++ b/src/sage/graphs/hyperbolicity.pyx @@ -51,8 +51,8 @@ Hyperbolicity Several improvements over the naive algorithm have been proposed and are implemented in the current module. - - Another upper bound on `hyp(a, b, c, d)` has been proved in [CCL12]_. It - is used to design an algorithm with worse case time complexity in + - Another upper bound on `hyp(a, b, c, d)` has been proved in [CCL15]_. It + is used to design an algorithm with worse case time complexity in `O(n^4)` but that behaves much better in practice. Assume that `S_1 = dist(a, b) + dist(c, d)` is the largest sum among @@ -100,9 +100,13 @@ Hyperbolicity `(a,b)` and `(c,d)` satisfying `\delta(G) = hyp(a, b, c, d)/2`. For instance, the `n\times m`-grid has only two far-apart pairs, and so computing its hyperbolicity is immediate once the far-apart pairs are - found. The 'CCL+FA' or 'CCL+' algorithm improves the 'CCL' algorithm + found. The 'CCL+FA' or 'CCL+' algorithm improves the 'CCL' algorithm since it uses far-apart pairs. + - This algorithm was further improved in [BCCM15]_: instead of iterating + twice over all pairs of vertices, in the "inner" loop, we cut several + pairs by exploiting properties of the underlying graph. + TODO: - Add exact methods for the hyperbolicity of chordal graphs @@ -123,9 +127,14 @@ At Python level : REFERENCES: -.. [CCL12] N. Cohen, D. Coudert, and A. Lancin. Exact and approximate - algorithms for computing the hyperbolicity of large-scale graphs. - Research Report RR-8074, Sep. 2012. [``_]. +.. [BCCM15] M. Borassi, D. Coudert, P. Crescenzi, and A. Marino. + On Computing the Hyperbolicity of Real-World Graphs. + Proceedings of the 23rd European Symposium on Algorithms (ESA 2015) + +.. [CCL15] N. Cohen, D. Coudert, and A. Lancin. On computing the Gromov + hyperbolicity. ACM Journal of Experimental Algorithmics, 20(1.6):1-18, 2015. + [``_] or + [``_]. .. [FIV12] H. Fournier, A. Ismail, and A. Vigneron. Computing the Gromov hyperbolicity of a discrete metric space. ArXiv, Tech. Rep. arXiv:1210.3323, @@ -134,7 +143,7 @@ REFERENCES: .. [Gromov87] M. Gromov. Hyperbolic groups. Essays in Group Theory, 8:75--263, 1987. -.. [Soto11] M. A. Soto Gomez. 2011. Quelques proprietes topologiques des +.. [Soto11] M. A. Soto Gomez. 2011. Quelques proprietes topologiques des graphes et applications a internet et aux reseaux. Ph.D. Dissertation. Univ. Paris Diderot (Paris 7). @@ -143,7 +152,7 @@ AUTHORS: - David Coudert (2012): initial version, exact and approximate algorithm, distribution, sampling - David Coudert (2014): improved exact algorithm using far-apart pairs -- Michele Borassi (2015): cleaned the code +- Michele Borassi (2015): cleaned the code and implemented the new algorithm Methods @@ -198,8 +207,8 @@ def _my_subgraph(G, vertices, relabel=False, return_map=False): ignored as well as any other decoration of the graph (vertex position, etc.). - If ``relabel`` is ``True``, the vertices of the new graph are relabeled - with integers in the range '0\cdots |vertices|-1'. The relabeling map is + If ``relabel`` is ``True``, the vertices of the new graph are relabeled + with integers in the range '0\cdots \mid vertices \mid -1'. The relabeling map is returned if ``return_map`` is also ``True``. TESTS: @@ -289,7 +298,7 @@ cdef tuple hyperbolicity_basic_algorithm(int N, - ``N`` -- number of vertices of the graph. - - ``distances`` -- path distance matrix (see the distance_all_pairs + - ``distances`` -- path distance matrix (see the distance_all_pairs module). - ``verbose`` -- (default: ``False``) is boolean. Set to True to display @@ -299,8 +308,8 @@ cdef tuple hyperbolicity_basic_algorithm(int N, This function returns a tuple ( h, certificate ), where: - - ``h`` -- the maximum computed value over all 4-tuples, and so is twice - the hyperbolicity of the graph. If no such 4-tuple is found, -1 is + - ``h`` -- the maximum computed value over all 4-tuples, and so is twice + the hyperbolicity of the graph. If no such 4-tuple is found, -1 is returned. - ``certificate`` -- 4-tuple of vertices maximizing the value `h`. If no @@ -311,7 +320,7 @@ cdef tuple hyperbolicity_basic_algorithm(int N, cdef list certificate h_LB = -1 - + for 0 <= a < N-3: for a < b < N-2: @@ -381,7 +390,7 @@ cdef inline distances_and_far_apart_pairs(gg, This method assumes that: - - The input graph gg is connected. If not, the result will be + - The input graph gg is connected. If not, the result will be incorrect. - The arrays distances and far_apart_pairs have already been allocated @@ -492,11 +501,11 @@ cdef inline pair** sort_pairs(uint32_t N, ): """ Returns an array of unordered pairs {i,j} in increasing order of values. - + Uses counting sort to list pairs {i,j} in increasing order of values(i,j). - If to_include[i][j] = 0, the pair is ignored. We assume N and D to be - correct with respect to the arrays values and to_include, that values and - to_include are symmetric (that is, values[i][j] = values[j][i] and + If to_include[i][j] = 0, the pair is ignored. We assume N and D to be + correct with respect to the arrays values and to_include, that values and + to_include are symmetric (that is, values[i][j] = values[j][i] and to_include[i][j] = to_include[j][i], and that nb_p, nb_pairs_of_length are already allocated. @@ -507,22 +516,22 @@ cdef inline pair** sort_pairs(uint32_t N, - ``D`` -- the maximum value of an element; - - ``values`` -- an array containing in position (i,j) the value of the + - ``values`` -- an array containing in position (i,j) the value of the pair (i,j); - - - ``to_include`` -- an array such that to_include[i][j] contains "1" if - pair (i,j) should be included, "0" otherwise. If NULL, all elements are + + - ``to_include`` -- an array such that to_include[i][j] contains "1" if + pair (i,j) should be included, "0" otherwise. If NULL, all elements are included; OUTPUT: - + - ``nb_p`` -- the number of pairs to be included; - - - ``nb_pairs_of_length`` -- an array containing in position k the number + + - ``nb_pairs_of_length`` -- an array containing in position k the number of pairs (i,j) that are included and such that values[i][j] = k. - - - ``pairs_of_length`` -- this function returns this array, containing in - position k a pointer to the first included pair (i,j) such that + + - ``pairs_of_length`` -- this function returns this array, containing in + position k a pointer to the first included pair (i,j) such that values[i][j] = k. """ # pairs_of_length[d] is the list of pairs of vertices at distance d @@ -530,10 +539,10 @@ cdef inline pair** sort_pairs(uint32_t N, cdef unsigned short *p_to_include cdef uint32_t i,j,k nb_p[0] = 0; - + # fills nb_pairs_of_length and nb_p memset(nb_pairs_of_length, 0, (D+1) * sizeof(uint32_t)) - + if to_include == NULL: nb_p[0] = (N*(N-1))/2 for i from 0 <= i < N: @@ -588,10 +597,266 @@ cdef inline pair** sort_pairs(uint32_t N, sage_free(cpt_pairs) return pairs_of_length - + + +###################################################################### +# Compute the hyperbolicity using the algorithm of [BCCM15]_ +###################################################################### + +cdef tuple hyperbolicity_BCCM(int N, + unsigned short **distances, + unsigned short **far_apart_pairs, + int D, + int h_LB, + float approximation_factor, + float additive_gap, + verbose = False): + """ + Return the hyperbolicity of a graph. + + This method implements the exact and the approximate algorithms proposed in + [BCCM15]_. See the module's documentation for more details. + + This method assumes that the graph under consideration is connected. + + INPUTS: + + - ``N`` -- number of vertices of the graph + + - ``distances`` -- path distance matrix + + - ``far_apart_pairs`` -- 0/1 matrix of far-apart pairs. Pair ``(i,j)`` is + far-apart if ``far_apart_pairs[i][j]\neq 0``. + + - ``D`` -- diameter of the graph + + - ``h_LB`` -- lower bound on the hyperbolicity + + - ``approximation_factor`` -- When the approximation factor is set to some + value larger than 1.0, the function stop computations as soon as the + ratio between the upper bound and the best found solution is less than + the approximation factor. When the approximation factor is 1.0, the + problem is solved optimaly. + + - ``additive_gap`` -- When sets to a positive number, the function stop + computations as soon as the difference between the upper bound and the + best found solution is less than additive gap. When the gap is 0.0, the + problem is solved optimaly. + + - ``verbose`` -- (default: ``False``) is boolean set to ``True`` to display + some information during execution + + OUTPUTS: + + This function returns a tuple ( h, certificate, h_UB ), where: + + - ``h`` -- is an integer. When 4-tuples with hyperbolicity larger or equal + to `h_LB are found, h is the maximum computed value and so twice the + hyperbolicity of the graph. If no such 4-tuple is found, it returns -1. + + - ``certificate`` -- is a list of vertices. When 4-tuples with + hyperbolicity larger that h_LB are found, certificate is the list of the + 4 vertices for which the maximum value (and so the hyperbolicity of the + graph) has been computed. If no such 4-tuple is found, it returns the + empty list []. + + - ``h_UB`` -- is an integer equal to the proven upper bound for `h`. When + ``h == h_UB``, the returned solution is optimal. + """ + cdef MemoryAllocator mem = MemoryAllocator() + cdef int h = 0, hh # can get negative value + cdef int a, b, c, d, h_UB, n_val, n_acc, i, j + cdef int hplusone + cdef int condacc + cdef int x, y, S1, S2, S3 + cdef list certificate = [] + cdef uint32_t nb_p # The total number of pairs. + cdef unsigned short *dist_a + cdef unsigned short *dist_b + cdef bint GOTO_RETURN = 0 + + # Variable used to store "mates". + cdef int **mate = mem.malloc(N * sizeof(int*)) + for i in range(N): + mate[i] = mem.malloc(N * sizeof(int)) + cdef int *cont_mate = mem.calloc(N, sizeof(int)) + + # The farness of all vertices (the farness of v is the sum of the distances + # between v and all other vertices). + cdef uint64_t *farness = mem.calloc(N, sizeof(uint64_t)) + cdef short *ecc = mem.calloc(N, sizeof(short)) + cdef int central = 0 + cdef int **mates_decr_order_value = mem.malloc(N * sizeof(int*)) + cdef int *value = mem.malloc(N * sizeof(int)) + cdef int *nvalues = mem.malloc((D + 1) * sizeof(int)) + cdef short *acc_bool = mem.calloc(N, sizeof(short)) + cdef int *acc = mem.malloc(N * sizeof(int)) + cdef int *val = mem.malloc(N * sizeof(int)) + cdef int *nvalues_cum = mem.malloc((D + 1) * sizeof(int)) + cdef uint64_t nq = 0 + + # We compute the farness and the eccentricity of all vertices. + # We set central as the vertex with minimum farness + for a in range(N): + dist_a = distances[a] + for b in range(N): + farness[a] += dist_a[b] + ecc[a] = max(ecc[a], dist_a[b]) + if dist_a[b] >= N: + raise ValueError("The input graph must be connected.") + if farness[a] < farness[central]: + central = a + cdef unsigned short *dist_central = distances[central] + + # We put in variable mates_decr_order_value[a] all vertices b, in + # decreasing order of ecc[b]-distances[a][b] + for a in range(N): + mates_decr_order_value[a] = mem.malloc(N * sizeof(int)) + dist_a = distances[a] + memset(nvalues, 0, (D+1) * sizeof(int)) + + for b in range(N): + value[b] = ecc[b] - dist_a[b] + nvalues[value[b]] += 1 + nvalues_cum[D] = 0 + + for b in range(D-1, -1, -1): + nvalues_cum[b] = nvalues_cum[b+1] + nvalues[b+1] + + for b in range(N): + mates_decr_order_value[a][nvalues_cum[value[b]]] = b + nvalues_cum[value[b]] += 1 + + # We sort pairs, in increasing order of distance + cdef uint32_t * nb_pairs_of_length = mem.calloc(D+1, sizeof(uint32_t)) + + cdef pair ** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs, + &nb_p, nb_pairs_of_length) + + if verbose: + print "Current 2 connected component has %d vertices and diameter %d" %(N,D) + if far_apart_pairs == NULL: + print "Number of pairs: %d" %(nb_p) + print "Repartition of pairs:", [(i, nb_pairs_of_length[i]) for i in range(1, D+1) if nb_pairs_of_length[i]>0] + else: + print "Number of far-apart pairs: %d\t(%d pairs in total)" %(nb_p, binomial(N, 2)) + print "Repartition of far-apart pairs:", [(i, nb_pairs_of_length[i]) for i in range(1, D+1) if nb_pairs_of_length[i]>0] + + cdef pair * sorted_pairs = pairs_of_length[0] + + approximation_factor = min(approximation_factor, D) + additive_gap = min(additive_gap, D) + + # We start iterating from pairs with maximum distance. + for x in range(nb_p-1, -1, -1): + a = sorted_pairs[x].s + b = sorted_pairs[x].t + + # Without loss of generality, a has smaller farness than b. + if farness[a] < farness[b]: + a,b = b,a + + dist_a = distances[a] + dist_b = distances[b] + h_UB = distances[a][b] + + # If we cannot improve further, we stop + if h_UB <= h: + h_UB = h + GOTO_RETURN = 1 + break + + # Termination if required approximation is found + if (h_UB <= h*approximation_factor) or (h_UB-h <= additive_gap): + GOTO_RETURN = 1 + break + + # We update variable mate, adding pair (a,b) + mate[a][cont_mate[a]] = b + cont_mate[a] += 1 + mate[b][cont_mate[b]] = a + cont_mate[b] += 1 + + # We compute acceptable and valuable vertices + n_acc = 0 + n_val = 0 + + hplusone = h+1 + condacc = 3 * hplusone - 2 * h_UB + + for i in range(N): + c = mates_decr_order_value[a][i] + if cont_mate[c] > 0: + if 2 * (ecc[c] - dist_a[c]) >= condacc: + if 2 * (ecc[c] - dist_b[c]) >= condacc: + if 2 * dist_a[c] >= hplusone and 2 * dist_b[c] >= hplusone: + if (2 * ecc[c] >= 2*hplusone - h_UB + dist_a[c] + dist_b[c]): + # Vertex c is acceptable + acc_bool[c] = 1 + acc[n_acc] = c + n_acc += 1 + if 2 * dist_central[c] + h_UB - h > dist_a[c] + dist_b[c]: + # Vertex c is valuable + val[n_val] = c; + n_val += 1 + else: + break + + # For each pair (c,d) where c is valuable and d is acceptable, we + # compute the hyperbolicity of (a,b,c,d), and we update h if necessary + for i in range(n_val): + c = val[i] + for j in range(cont_mate[c]): + d = mate[c][j]; + if (acc_bool[d]): + nq += 1 + S1 = h_UB + distances[c][d] + S2 = dist_a[c] + dist_b[d]; + S3 = dist_a[d] + dist_b[c]; + if S2 > S3: + hh = S1 - S2 + else: + hh = S1 - S3 + + if h < hh or not certificate: + # We update current bound on the hyperbolicity and the + # search space. + # + # Note that if hh==0, we first make sure that a,b,c,d are + # all distinct and are a valid certificate. + if hh>0 or not (a==c or a==d or b==c or b==d): + h = hh + certificate = [a, b, c, d] + + if verbose: + print "New lower bound:",ZZ(hh)/2 + + # We reset acc_bool + for v in range(n_acc): + acc_bool[acc[v]] = 0 + + # Needed because sometimes h_UB is not updated, if the analysis is no cut. + if not GOTO_RETURN: + h_UB = h + + # We now free the memory + sage_free(pairs_of_length[0]) + sage_free(pairs_of_length) + + if verbose: + print "Visited 4-tuples:", nq + + # Last, we return the computed value and the certificate + if len(certificate) == 0: + return ( -1, [], h_UB ) + else: + # When using far-apart pairs, the loops may end before improving the + # upper-bound + return (h, certificate, h_UB) + ###################################################################### -# Compute the hyperbolicity using the algorithm of [CCL12]_ +# Compute the hyperbolicity using the algorithm of [CCL15]_ ###################################################################### cdef tuple hyperbolicity_CCL(int N, @@ -606,7 +871,7 @@ cdef tuple hyperbolicity_CCL(int N, Return the hyperbolicity of a graph. This method implements the exact and the approximate algorithms proposed in - [CCL12]_. See the module's documentation for more details. + [CCL15]_. See the module's documentation for more details. This method assumes that the graph under consideration is connected. @@ -624,9 +889,9 @@ cdef tuple hyperbolicity_CCL(int N, - ``h_LB`` -- lower bound on the hyperbolicity - ``approximation_factor`` -- When the approximation factor is set to some - value larger than 1.0, the function stop computations as soon as the - ratio between the upper bound and the best found solution is less than - the approximation factor. When the approximation factor is 1.0, the + value larger than 1.0, the function stop computations as soon as the + ratio between the upper bound and the best found solution is less than + the approximation factor. When the approximation factor is 1.0, the problem is solved optimaly. - ``additive_gap`` -- When sets to a positive number, the function stop @@ -645,10 +910,10 @@ cdef tuple hyperbolicity_CCL(int N, to `h_LB are found, h is the maximum computed value and so twice the hyperbolicity of the graph. If no such 4-tuple is found, it returns -1. - - ``certificate`` -- is a list of vertices. When 4-tuples with + - ``certificate`` -- is a list of vertices. When 4-tuples with hyperbolicity larger that h_LB are found, certificate is the list of the - 4 vertices for which the maximum value (and so the hyperbolicity of the - graph) has been computed. If no such 4-tuple is found, it returns the + 4 vertices for which the maximum value (and so the hyperbolicity of the + graph) has been computed. If no such 4-tuple is found, it returns the empty list []. - ``h_UB`` -- is an integer equal to the proven upper bound for `h`. When @@ -673,9 +938,9 @@ cdef tuple hyperbolicity_CCL(int N, if (nb_pairs_of_length == NULL): raise MemoryError - cdef pair ** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs, + cdef pair ** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs, &nb_p, nb_pairs_of_length) - + if verbose: print "Current 2 connected component has %d vertices and diameter %d" %(N,D) if far_apart_pairs == NULL: @@ -807,10 +1072,10 @@ cdef tuple hyperbolicity_CCL(int N, return (h, certificate, h_UB if GOTO_RETURN else h) -def hyperbolicity(G, - algorithm='CCL+FA', - approximation_factor=None, - additive_gap=None, +def hyperbolicity(G, + algorithm='BCCM', + approximation_factor=None, + additive_gap=None, verbose = False): r""" Returns the hyperbolicity of the graph or an approximation of this value. @@ -830,21 +1095,25 @@ def hyperbolicity(G, - ``G`` -- a connected Graph - - ``algorithm`` -- (default: ``'CCL+FA'``) specifies the algorithm to use + - ``algorithm`` -- (default: ``'BCCM'``) specifies the algorithm to use among: - ``'basic'`` is an exhaustive algorithm considering all possible 4-tuples and so have time complexity in `O(n^4)`. - - ``'CCL'`` is an exact algorithm proposed in [CCL12_]. It considers + - ``'CCL'`` is an exact algorithm proposed in [CCL15_]. It considers the 4-tuples in an ordering allowing to cut the search space as soon as a new lower bound is found (see the module's documentation). This algorithm can be turned into a approximation algorithm. - - ``'CCL+FA'`` or ``'CCL+'`` uses the notion of far-apart pairs as - proposed in [Soto11]_ to significantly reduce the overall + - ``'CCL+FA'`` or ``'CCL+'`` uses the notion of far-apart pairs as + proposed in [Soto11]_ to significantly reduce the overall computation time of the ``'CCL'`` algorithm. + - ``'BCCM'`` is an exact algorithm proposed in [BCCM15_]. It improves + ``'CCL+FA'`` by cutting several 4-tuples (for more information, + see the module's documentation). + - ``'dom'`` is an approximation with additive constant four. It computes the hyperbolicity of the vertices of a dominating set of the graph. This is sometimes slower than ``'CCL'`` and sometimes @@ -857,13 +1126,13 @@ def hyperbolicity(G, soon as the ratio between the upper bound and the best found solution is less than the approximation factor. When the approximation factor is 1.0, the problem is solved optimaly. This parameter is used only when the - chosen algorithm is ``'CCL'`` or ``'CCL+FA'``. + chosen algorithm is ``'CCL'``, ``'CCL+FA'``, or ``'BCCM'``. - ``additive_gap`` -- (default: None) When sets to a positive number, the function stop computations as soon as the difference between the upper bound and the best found solution is less than additive gap. When the gap is 0.0, the problem is solved optimaly. This parameter is used only when - the chosen algorithm is ``'CCL'`` or ``'CCL+FA'``. + the chosen algorithm is ``'CCL'`` or ``'CCL+FA'``, or ``'BCCM'``. - ``verbose`` -- (default: ``False``) is a boolean set to True to display some information during execution: new upper and lower bounds, etc. @@ -887,6 +1156,8 @@ def hyperbolicity(G, sage: from sage.graphs.hyperbolicity import hyperbolicity sage: G = graphs.GridGraph([3,3]) + sage: hyperbolicity(G,algorithm='BCCM') + (2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2) sage: hyperbolicity(G,algorithm='CCL') (2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2) sage: hyperbolicity(G,algorithm='basic') @@ -896,6 +1167,8 @@ def hyperbolicity(G, sage: from sage.graphs.hyperbolicity import hyperbolicity sage: G = graphs.PetersenGraph() + sage: hyperbolicity(G,algorithm='BCCM') + (1/2, [6, 7, 8, 9], 1/2) sage: hyperbolicity(G,algorithm='CCL') (1/2, [0, 1, 2, 3], 1/2) sage: hyperbolicity(G,algorithm='CCL+') @@ -921,9 +1194,9 @@ def hyperbolicity(G, (1, [(0, 0), (0, 9), (1, 0), (1, 9)], 3) sage: hyperbolicity(G,algorithm='dom') (1, [(0, 1), (0, 9), (1, 0), (1, 8)], 5) - + Asking for an approximation in a cycle graph:: - + sage: from sage.graphs.hyperbolicity import hyperbolicity sage: G = graphs.CycleGraph(10) sage: hyperbolicity(G,algorithm='CCL', approximation_factor=1.5) @@ -937,15 +1210,32 @@ def hyperbolicity(G, sage: from sage.graphs.hyperbolicity import hyperbolicity sage: for i in xrange(10): # long time - ... G = graphs.RandomBarabasiAlbert(100,2) - ... d1,_,_ = hyperbolicity(G,algorithm='basic') - ... d2,_,_ = hyperbolicity(G,algorithm='CCL') - ... d3,_,_ = hyperbolicity(G,algorithm='CCL+') - ... d4,_,_ = hyperbolicity(G,algorithm='CCL+FA') - ... l3,_,u3 = hyperbolicity(G,approximation_factor=2) - ... if (not d1==d2==d3==d4) or l3>d1 or u3d1 or u3d1 or u3= 1.0.") else: @@ -1025,12 +1315,12 @@ def hyperbolicity(G, additive_gap = 0.0 elif additive_gap==0.0: pass - elif algorithm in ['CCL', 'CCL+FA']: + elif algorithm in ['CCL', 'CCL+FA', 'BCCM']: if not additive_gap in RR or additive_gap < 0.0: raise ValueError("The additive gap must be a real positive number.") else: raise ValueError("The additive_gap is ignored when using the '%s' algorithm." %(algorithm)) - + # The hyperbolicity is defined on connected graphs if not G.is_connected(): raise ValueError("The input Graph must be connected.") @@ -1111,7 +1401,7 @@ def hyperbolicity(G, if distances == NULL: raise MemoryError("Unable to allocate array 'distances'.") - if algorithm == 'CCL+FA': + if algorithm == 'CCL+FA' or algorithm == 'BCCM': _distances_ = check_allocarray(N * N, sizeof(unsigned short)) _far_apart_pairs_ = check_allocarray(N * N, sizeof(unsigned short)) far_apart_pairs = check_allocarray(N, sizeof(unsigned short *)) @@ -1148,6 +1438,13 @@ def hyperbolicity(G, approximation_factor, 2*additive_gap, verbose) sig_off() + elif algorithm == 'BCCM': + sig_on() + hyp, certif, hyp_UB = hyperbolicity_BCCM(N, distances, far_apart_pairs, + D, hyp, approximation_factor, + 2*additive_gap, verbose) + sig_off() + elif algorithm == 'dom': # Computes a dominating set DOM of G, and computes the hyperbolicity # considering only vertices in DOM diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index 0deb4cbafdd..7dfb9473d99 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -159,4 +159,23 @@ def nauty(self, number_of_sets, number_of_vertices, yield tuple( tuple( x for x in G.neighbors(v)) for v in range(number_of_vertices, total)) + def CompleteUniform(self, n, k): + r""" + Return the complete `k`-uniform hypergraph on `n` points. + + INPUT: + + - ``k,n`` -- nonnegative integers with `k\leq n` + + EXAMPLE:: + + sage: h = hypergraphs.CompleteUniform(5,2); h + Incidence structure with 5 points and 10 blocks + sage: len(h.packing()) + 2 + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from itertools import combinations + return IncidenceStructure(list(combinations(range(n),k))) + hypergraphs = HypergraphGenerators() diff --git a/src/sage/graphs/independent_sets.pyx b/src/sage/graphs/independent_sets.pyx index cbb99409f55..e91e0609dba 100644 --- a/src/sage/graphs/independent_sets.pyx +++ b/src/sage/graphs/independent_sets.pyx @@ -338,7 +338,7 @@ cdef class IndependentSets: - ``S`` -- a set of vertices to be tested. - TESTS:: + TESTS: All independent sets of PetersenGraph are... independent sets:: diff --git a/src/sage/graphs/isgci.py b/src/sage/graphs/isgci.py index 177c1bacbaa..72ae258cfda 100644 --- a/src/sage/graphs/isgci.py +++ b/src/sage/graphs/isgci.py @@ -184,7 +184,7 @@ * - Interval - - :meth:`~sage.graphs.graph_generators.GraphGenerators.RandomInterval`, + - :meth:`~sage.graphs.graph_generators.GraphGenerators.RandomIntervalGraph`, :meth:`~sage.graphs.graph_generators.GraphGenerators.IntervalGraph`, :meth:`~sage.graphs.generic_graph.GenericGraph.is_interval` diff --git a/src/sage/graphs/lovasz_theta.py b/src/sage/graphs/lovasz_theta.py index b33459ebf99..0d345c92176 100644 --- a/src/sage/graphs/lovasz_theta.py +++ b/src/sage/graphs/lovasz_theta.py @@ -65,10 +65,10 @@ def lovasz_theta(graph): from sage.misc.temporary_file import tmp_filename import os, subprocess from sage.env import SAGE_LOCAL - from sage.misc.package import is_package_installed + from sage.misc.package import is_package_installed, PackageNotFoundError if not is_package_installed('csdp'): - raise NotImplementedError("Package csdp is required. Please install it with 'sage -i csdp'.") + raise PackageNotFoundError("csdp") g = graph.relabel(inplace=False, perm=range(1,n+1)).networkx_graph() tf_name = tmp_filename() diff --git a/src/sage/graphs/schnyder.py b/src/sage/graphs/schnyder.py index c4b4b8398fc..6af206e9581 100644 --- a/src/sage/graphs/schnyder.py +++ b/src/sage/graphs/schnyder.py @@ -6,12 +6,14 @@ Walter Schnyder's Algorithm. AUTHORS: - -- Jonathan Bober, Emily Kirkman (2008-02-09): initial version + +- Jonathan Bober, Emily Kirkman (2008-02-09) -- initial version REFERENCE: - [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. - Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, - San Francisco (1994), pp. 138-147. + +.. [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. + Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, + San Francisco (1994), pp. 138-147. """ #***************************************************************************** # Copyright (C) 2008 Jonathan Bober and Emily Kirkman @@ -42,11 +44,13 @@ def _triangulate(g, comb_emb): method will work on one of these attempts.) INPUT: - g -- the graph to triangulate - comb_emb -- a planar combinatorial embedding of g - RETURNS: - A list of edges that are added to the graph (in place) + - g -- the graph to triangulate + - ``comb_emb`` -- a planar combinatorial embedding of g + + OUTPUT: + + A list of edges that are added to the graph (in place) EXAMPLES:: @@ -125,27 +129,33 @@ def _triangulate(g, comb_emb): return edges_added def _normal_label(g, comb_emb, external_face): - """ - Helper function to schnyder method for computing coordinates in the plane to - plot a planar graph with no edge crossings. + r""" + Helper function to schnyder method for computing coordinates in + the plane to plot a planar graph with no edge crossings. - Constructs a normal labelling of a triangular graph g, given the planar - combinatorial embedding of g and a designated external face. Returns labels - dictionary. The normal label is constructed by first contracting the graph - down to its external face, then expanding the graph back to the original while - simultaneously adding angle labels. + Constructs a normal labelling of a triangular graph g, given the + planar combinatorial embedding of g and a designated external + face. Returns labels dictionary. The normal label is constructed + by first contracting the graph down to its external face, then + expanding the graph back to the original while simultaneously + adding angle labels. INPUT: - g -- the graph to find the normal labeling of (g must be triangulated) - comb_emb -- a planar combinatorial embedding of g - external_face -- the list of three edges in the external face of g - RETURNS: - x -- tuple with entries - x[0] = dict of dicts of normal labeling for each vertex of g and each - adjacent neighbors u,v (u < v) of vertex: - { vertex : { (u,v): angel_label } } - x[1] = (v1,v2,v3) tuple of the three vertices of the external face. + - g -- the graph to find the normal labeling of (g must be triangulated) + - ``comb_emb`` -- a planar combinatorial embedding of g + - ``external_face`` -- the list of three edges in the external face of g + + OUTPUT: + + x -- tuple with entries + + x[0] = dict of dicts of normal labeling for each vertex of g and each + adjacent neighbors u,v (u < v) of vertex: + + { vertex : { (u,v): angel_label } } + + x[1] = (v1,v2,v3) tuple of the three vertices of the external face. EXAMPLES:: @@ -160,7 +170,6 @@ def _normal_label(g, comb_emb, external_face): sage: _realizer(g, tn) ({0: []}, (0, 1, 2)) - """ contracted = [] contractible = [] @@ -329,20 +338,27 @@ def _realizer(g, x, example=False): give a path to each of the three external vertices. INPUT: - g -- the graph to compute the realizer of - x -- tuple with entries - x[0] = dict of dicts representing a normal labeling of g. For - each vertex of g and each adjacent neighbors u,v (u < v) of - vertex: { vertex : { (u,v): angle_label } } - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) - - RETURNS: - x -- tuple with entries - x[0] = dict of lists of TreeNodes: - { root_vertex : [ list of all TreeNodes under root_vertex ] } - x[0] = (v1,v2,v3) tuple of the three external vertices (also the - roots of each tree) + + - g -- the graph to compute the realizer of + - x -- tuple with entries + + x[0] = dict of dicts representing a normal labeling of g. For + each vertex of g and each adjacent neighbors u,v (u < v) of + vertex: { vertex : { (u,v): angle_label } } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + + OUTPUT: + + - x -- tuple with entries + + x[0] = dict of lists of TreeNodes: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1,v2,v3) tuple of the three external vertices (also the + roots of each tree) EXAMPLES:: @@ -409,22 +425,26 @@ def _realizer(g, x, example=False): return tree_nodes, (v1,v2,v3) def _compute_coordinates(g, x): - """ + r""" Given a triangulated graph g with a dict of trees given by the realizer and tuple of the external vertices, we compute the coordinates of a planar geometric embedding in the grid. - The coordinates will be set to the _pos attribute of g. + The coordinates will be set to the ``_pos`` attribute of g. INPUT: - g -- the graph to compute the coordinates of - x -- tuple with entries - x[0] = dict of tree nodes for the three trees with each external - vertex as root - { root_vertex : [ list of all TreeNodes under root_vertex ] } - - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) + + - g -- the graph to compute the coordinates of + - x -- tuple with entries + + x[0] = dict of tree nodes for the three trees with each external + vertex as root: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + EXAMPLES:: sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates @@ -505,16 +525,17 @@ def _compute_coordinates(g, x): class TreeNode(): """ - A class to represent each node in the trees used by _realizer() and - _compute_coordinates() when finding a planar geometric embedding in + A class to represent each node in the trees used by :func:`_realizer` and + :func:`_compute_coordinates` when finding a planar geometric embedding in the grid. Each tree node is doubly linked to its parent and children. INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLES:: @@ -532,14 +553,14 @@ class TreeNode(): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ def __init__(self, parent = None, children = None, label = None): """ INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLE:: @@ -557,7 +578,6 @@ def __init__(self, parent = None, children = None, label = None): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if children is None: children = [] @@ -566,10 +586,10 @@ def __init__(self, parent = None, children = None, label = None): self.label = label self.number_of_descendants = 1 - def compute_number_of_descendants(self): """ Computes the number of descendants of self and all descendants. + For each TreeNode, sets result as attribute self.number_of_descendants EXAMPLES:: @@ -599,6 +619,7 @@ def compute_number_of_descendants(self): def compute_depth_of_self_and_children(self): """ Computes the depth of self and all descendants. + For each TreeNode, sets result as attribute self.depth EXAMPLES:: @@ -617,7 +638,6 @@ def compute_depth_of_self_and_children(self): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if self.parent is None: self.depth = 1 @@ -646,7 +666,6 @@ def append_child(self, child): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if child in self.children: return diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index ae83909a70f..7467ee0fbac 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -87,10 +87,22 @@ cpdef kruskal(G, wfunction=None, bint check=False): - ``G`` -- an undirected graph. - - ``wfunction`` -- A weight function: a function that takes an edge and - returns a numeric weight. If ``wfunction=None`` (default), the algorithm - uses the edge weight, if available, otherwise it assigns weight 1 to each - edge (in the latter case, the output can be any spanning tree). + - ``weight_function`` (function) - a function that inputs an edge ``e`` + and outputs its weight. An edge has the form ``(u,v,l)``, where ``u`` + and ``v`` are vertices, ``l`` is a label (that can be of any kind). + The ``weight_function`` can be used to transform the label into a + weight. In particular: + + - if ``weight_function`` is not ``None``, the weight of an edge ``e`` + is ``weight_function(e)``; + + - if ``weight_function`` is ``None`` (default) and ``g`` is weighted + (that is, ``g.weighted()==True``), the weight of an edge + ``e=(u,v,l)`` is ``l``, independently on which kind of object ``l`` + is: the ordering of labels relies on Python's operator ``<``; + + - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set + all weights to 1 (hence, the output can be any spanning tree). - ``check`` -- Whether to first perform sanity checks on the input graph ``G``. Default: ``check=False``. If we toggle ``check=True``, the @@ -282,7 +294,6 @@ cpdef kruskal(G, wfunction=None, bint check=False): else: g = G - # G is assumed to be connected, undirected, and with at least a vertex # We sort edges, as specified. if wfunction is None: diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx new file mode 100644 index 00000000000..7ae6e8a21e2 --- /dev/null +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -0,0 +1,3186 @@ +# -*- coding: utf-8 -*- +r""" +Database of strongly regular graphs + +This module manages a database associating to a set of four integers +`(v,k,\lambda,\mu)` a strongly regular graphs with these parameters, when one +exists. + +Using Andries Brouwer's `database of strongly regular graphs +`__, it can also return +non-existence results. Note that some constructions are missing, and that some +strongly regular graphs that exist in the database cannot be automatically built +by Sage. Help us if you know any. + +.. NOTE:: + + Any missing/incorrect information in the database must be reported to + `Andries E. Brouwer `__ directly, in order to + have a unique and updated source of information. + +REFERENCES: + +.. [BvL84] A. Brouwer, J van Lint, + Strongly regular graphs and partial geometries, + Enumeration and design, + (Waterloo, Ont., 1982) (1984): 85-122. + http://oai.cwi.nl/oai/asset/1817/1817A.pdf + +Functions +--------- +""" +from sage.categories.sets_cat import EmptySetError +from sage.misc.unknown import Unknown +from sage.rings.arith import is_square +from sage.rings.arith import is_prime_power +from sage.misc.cachefunc import cached_function +from sage.combinat.designs.orthogonal_arrays import orthogonal_array +from sage.combinat.designs.bibd import balanced_incomplete_block_design +from sage.graphs.generators.smallgraphs import McLaughlinGraph +from sage.graphs.generators.smallgraphs import CameronGraph +from sage.graphs.generators.smallgraphs import M22Graph +from sage.graphs.generators.smallgraphs import SimsGewirtzGraph +from sage.graphs.generators.smallgraphs import HoffmanSingletonGraph +from sage.graphs.generators.smallgraphs import SchlaefliGraph +from sage.graphs.generators.smallgraphs import HigmanSimsGraph +from sage.graphs.generators.smallgraphs import LocalMcLaughlinGraph +from sage.graphs.generators.smallgraphs import SuzukiGraph +from sage.graphs.graph import Graph +from libc.math cimport sqrt, floor +from sage.matrix.constructor import Matrix +from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.coding.linear_code import LinearCode +from sage.rings.sum_of_squares cimport two_squares_c +from libc.stdint cimport uint_fast32_t + +cdef dict _brouwer_database = None + +@cached_function +def is_paley(int v,int k,int l,int mu): + r""" + Test whether some Paley graph is `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_paley + sage: t = is_paley(13,6,2,3); t + (..., 13) + sage: g = t[0](*t[1:]); g + Paley graph with parameter 13: Graph on 13 vertices + sage: g.is_strongly_regular(parameters=True) + (13, 6, 2, 3) + sage: t = is_paley(5,5,5,5); t + """ + if (v%4 == 1 and is_prime_power(v) and + k == (v-1)/2 and + l == (v-5)/4 and + mu == (v-1)/4): + from sage.graphs.generators.families import PaleyGraph + return (lambda q : PaleyGraph(q),v) + +@cached_function +def is_orthogonal_array_block_graph(int v,int k,int l,int mu): + r""" + Test whether some Orthogonal Array graph is `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_orthogonal_array_block_graph + sage: t = is_orthogonal_array_block_graph(64, 35, 18, 20); t + (..., 5, 8) + sage: g = t[0](*t[1:]); g + OA(5,8): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 35, 18, 20) + + sage: t = is_orthogonal_array_block_graph(5,5,5,5); t + """ + # notations from + # http://www.win.tue.nl/~aeb/graphs/OA.html + if not is_square(v): + return + n = int(sqrt(v)) + if k % (n-1): + return + m = k//(n-1) + if (l != (m-1)*(m-2)+n-2 or + mu != m*(m-1)): + return + if orthogonal_array(m,n,existence=True): + from sage.graphs.generators.intersection import OrthogonalArrayBlockGraph + return (lambda m,n : OrthogonalArrayBlockGraph(m, n), m,n) + +@cached_function +def is_johnson(int v,int k,int l,int mu): + r""" + Test whether some Johnson graph is `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_johnson + sage: t = is_johnson(10,6,3,4); t + (..., 5) + sage: g = t[0](*t[1:]); g + Johnson graph with parameters 5,2: Graph on 10 vertices + sage: g.is_strongly_regular(parameters=True) + (10, 6, 3, 4) + + sage: t = is_johnson(5,5,5,5); t + """ + # Using notations of http://www.win.tue.nl/~aeb/graphs/Johnson.html + # + # J(n,m) has parameters v = m(m – 1)/2, k = 2(m – 2), λ = m – 2, μ = 4. + m = l + 2 + if (mu == 4 and + k == 2*(m-2) and + v == m*(m-1)/2): + from sage.graphs.generators.families import JohnsonGraph + return (lambda m: JohnsonGraph(m,2), m) + +@cached_function +def is_steiner(int v,int k,int l,int mu): + r""" + Test whether some Steiner graph is `(v,k,\lambda,\mu)`-strongly regular. + + A Steiner graph is the intersection graph of a Steiner set system. For more + information, see http://www.win.tue.nl/~aeb/graphs/S.html. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_steiner + sage: t = is_steiner(26,15,8,9); t + (..., 13, 3) + sage: g = t[0](*t[1:]); g + Intersection Graph: Graph on 26 vertices + sage: g.is_strongly_regular(parameters=True) + (26, 15, 8, 9) + + sage: t = is_steiner(5,5,5,5); t + """ + # Using notations from http://www.win.tue.nl/~aeb/graphs/S.html + # + # The block graph of a Steiner 2-design S(2,m,n) has parameters: + # v = n(n-1)/m(m-1), k = m(n-m)/(m-1), λ = (m-1)^2 + (n-1)/(m–1)–2, μ = m^2. + if mu <= 1 or not is_square(mu): + return + m = int(sqrt(mu)) + n = (k*(m-1))//m+m + if (v == (n*(n-1))/(m*(m-1)) and + k == m*(n-m)/(m-1) and + l == (m-1)**2 + (n-1)/(m-1)-2 and + balanced_incomplete_block_design(n,m,existence=True)): + from sage.graphs.generators.intersection import IntersectionGraph + return (lambda n,m: IntersectionGraph(map(frozenset,balanced_incomplete_block_design(n,m))),n,m) + +@cached_function +def is_affine_polar(int v,int k,int l,int mu): + r""" + Test whether some Affine Polar graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see http://www.win.tue.nl/~aeb/graphs/VO.html. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_affine_polar + sage: t = is_affine_polar(81,32,13,12); t + (..., 4, 3) + sage: g = t[0](*t[1:]); g + Affine Polar Graph VO^+(4,3): Graph on 81 vertices + sage: g.is_strongly_regular(parameters=True) + (81, 32, 13, 12) + + sage: t = is_affine_polar(5,5,5,5); t + """ + from sage.rings.arith import divisors + # Using notations from http://www.win.tue.nl/~aeb/graphs/VO.html + # + # VO+(2e,q) has parameters: v = q^(2e), k = (q^(e−1) + 1)(q^e − 1), λ = + # q(q^(e−2) + 1)(q^(e−1) − 1) + q − 2, μ = q^(e−1)(q^(e−1) + 1) + # + # VO−(2e,q) has parameters v = q^(2e), k = (q^(e−1) - 1)(q^e + 1), λ = + # q(q^(e−2) - 1)(q^(e−1) + 1) + q − 2, μ = q^(e−1)(q^(e−1) - 1) + if (not is_square(v) or + not is_prime_power(v)): + return + prime,power = is_prime_power(v,get_data=True) + if power%2: + return + for e in divisors(power/2): + q = prime**(power//(2*e)) + assert v == q**(2*e) + if (k == (q**(e-1) + 1)*(q**e-1) and + l == q*(q**(e-2) + 1)*(q**(e-1)-1)+q-2 and + mu== q**(e-1)*(q**(e-1) + 1)): + from sage.graphs.generators.classical_geometries import AffineOrthogonalPolarGraph + return (lambda d,q : AffineOrthogonalPolarGraph(d,q,sign='+'),2*e,q) + if (k == (q**(e-1) - 1)*(q**e+1) and + l == q*(q**(e-2)- 1)*(q**(e-1)+1)+q-2 and + mu== q**(e-1)*(q**(e-1) - 1)): + from sage.graphs.generators.classical_geometries import AffineOrthogonalPolarGraph + return (lambda d,q : AffineOrthogonalPolarGraph(d,q,sign='-'),2*e,q) + +@cached_function +def is_orthogonal_polar(int v,int k,int l,int mu): + r""" + Test whether some Orthogonal Polar graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see http://www.win.tue.nl/~aeb/graphs/srghub.html. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_orthogonal_polar + sage: t = is_orthogonal_polar(85, 20, 3, 5); t + (, 5, 4, '') + sage: g = t[0](*t[1:]); g + Orthogonal Polar Graph O(5, 4): Graph on 85 vertices + sage: g.is_strongly_regular(parameters=True) + (85, 20, 3, 5) + + sage: t = is_orthogonal_polar(5,5,5,5); t + + TESTS: + + All of ``O(2m+1,q)``, ``O^+(2m,q)`` and ``O^-(2m,q)`` appear:: + + sage: is_orthogonal_polar(85, 20, 3, 5) + (, 5, 4, '') + sage: is_orthogonal_polar(119,54,21,27) + (, 8, 2, '-') + sage: is_orthogonal_polar(130,48,20,16) + (, 6, 3, '+') + + """ + from sage.rings.arith import divisors + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + q_pow_m_minus_one = -s-1 if abs(s) > r else r+1 + + if is_prime_power(q_pow_m_minus_one): + prime,power = is_prime_power(q_pow_m_minus_one,get_data=True) + for d in divisors(power): + q = prime**d + m = (power//d)+1 + + # O(2m+1,q) + if (v == (q**(2*m)-1)/(q-1) and + k == q*(q**(2*m-2)-1)/(q-1) and + l == q**2*(q**(2*m-4)-1)/(q-1) + q-1 and + mu== (q**(2*m-2)-1)/(q-1)): + from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph + return (OrthogonalPolarGraph, 2*m+1, q, "") + + # O^+(2m,q) + if (v == (q**(2*m-1)-1)/(q-1) + q**(m-1) and + k == q*(q**(2*m-3)-1)/(q-1) + q**(m-1) and + k == q**(2*m-3) + l + 1 and + mu== k/q): + from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph + return (OrthogonalPolarGraph, 2*m, q, "+") + + # O^+(2m+1,q) + if (v == (q**(2*m-1)-1)/(q-1) - q**(m-1) and + k == q*(q**(2*m-3)-1)/(q-1) - q**(m-1) and + k == q**(2*m-3) + l + 1 and + mu== k/q): + from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph + return (OrthogonalPolarGraph, 2*m, q, "-") + +@cached_function +def is_goethals_seidel(int v,int k,int l,int mu): + r""" + Test whether some + :func:`~sage.graphs.graph_generators.GraphGenerators.GoethalsSeidelGraph` graph is + `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_goethals_seidel + sage: t = is_goethals_seidel(28, 15, 6, 10); t + [, 3, 3] + sage: g = t[0](*t[1:]); g + Graph on 28 vertices + sage: g.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + sage: t = is_goethals_seidel(256, 135, 70, 72); t + [, 2, 15] + sage: g = t[0](*t[1:]); g + Graph on 256 vertices + sage: g.is_strongly_regular(parameters=True) + (256, 135, 70, 72) + + sage: t = is_goethals_seidel(5,5,5,5); t + + TESTS:: + + sage: for p in [(16, 9, 4, 6), (28, 15, 6, 10), (64, 35, 18, 20), (120, 63, 30, 36), + ....: (144, 77, 40, 42), (256, 135, 70, 72), (400, 209, 108, 110), + ....: (496, 255, 126, 136), (540, 275, 130, 150), (576, 299, 154, 156), + ....: (780, 399, 198, 210), (784, 405, 208, 210), (976, 495, 238, 264)]: + ....: print is_goethals_seidel(*p) + [, 2, 3] + [, 3, 3] + [, 2, 7] + [, 3, 7] + [, 2, 11] + [, 2, 15] + [, 2, 19] + [, 3, 15] + [, 5, 11] + [, 2, 23] + [, 3, 19] + [, 2, 27] + [, 5, 15] + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + + # here we guess the parameters v_bibd,k_bibd and r_bibd of the block design + # + # - the number of vertices v is equal to v_bibd*(r_bibd+1) + # - the degree k of the graph is equal to k=(v+r_bibd-1)/2 + + r_bibd = k - (v-1-k) + v_bibd = v//(r_bibd+1) + k_bibd = (v_bibd-1)/r_bibd + 1 if r_bibd>0 else -1 + + if (v == v_bibd*(r_bibd+1) and + 2*k == v+r_bibd-1 and + 4*l == -2*v + 6*k -v_bibd -k_bibd and + hadamard_matrix(r_bibd+1, existence=True) and + balanced_incomplete_block_design(v_bibd, k_bibd, existence = True)): + from sage.graphs.generators.families import GoethalsSeidelGraph + return [GoethalsSeidelGraph, k_bibd, r_bibd] + +@cached_function +def is_NOodd(int v,int k,int l,int mu): + r""" + Test whether some NO^e(2n+1,q) graph is `(v,k,\lambda,\mu)`-strongly regular. + + Here `q>2`, for in the case `q=2` this graph is complete. For more + information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` + and Sect. 7.C of [BvL84]_. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_NOodd + sage: t = is_NOodd(120, 51, 18, 24); t + (, 5, 4, '-') + sage: g = t[0](*t[1:]); g + NO^-(5, 4): Graph on 120 vertices + sage: g.is_strongly_regular(parameters=True) + (120, 51, 18, 24) + + TESTS: + + All of ``NO^+(2m+1,q)`` and ``NO^-(2m+1,q)`` appear:: + + sage: t = is_NOodd(120, 51, 18, 24); t + (, 5, 4, '-') + sage: t = is_NOodd(136, 75, 42, 40); t + (, 5, 4, '+') + sage: t=is_NOodd(378, 260, 178, 180); t + (, 7, 3, '+') + sage: t=is_NOodd(45, 32, 22, 24); t + (, 5, 3, '+') + sage: t=is_NOodd(351, 224, 142, 144); t + (, 7, 3, '-') + sage: t = is_NOodd(325, 144, 68, 60); t + (, 5, 5, '+') + sage: t = is_NOodd(300, 104, 28, 40); t + (, 5, 5, '-') + sage: t = is_NOodd(5,5,5,5); t + """ + cdef int n, q + r,s = eigenvalues(v,k,l,mu) # -eq^(n-1)-1 and eq^(n-1)(q-2)-1; q=3 is special case + if r is None: + return + r += 1 + s += 1 + if abs(r)>abs(s): + (r,s) = (s,r) # r=-eq^(n-1) s= eq^(n-1)(q-2) + q = 2 - s/r + p, t = is_prime_power(q, get_data=True) + pp, kk = is_prime_power(abs(r), get_data=True) + if p == pp and t != 0: + n = kk/t + 1 + e = 1 if v == (q**n)*(q**n+1)/2 else -1 + if (v == (q**n)*(q**n+e)/2 and + k == (q**n-e)*(q**(n-1)+e) and + l == 2*(q**(2*n-2)-1)+e*q**(n-1)*(q-1) and + mu == 2*q**(n-1)*(q**(n-1)+e)): + from sage.graphs.generators.classical_geometries import NonisotropicOrthogonalPolarGraph + return (NonisotropicOrthogonalPolarGraph, 2*n+1, q, '+' if e==1 else '-') + +@cached_function +def is_NOperp_F5(int v,int k,int l,int mu): + r""" + Test whether some NO^e,perp(2n+1,5) graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` + and Sect. 7.D of [BvL84]_. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_NOperp_F5 + sage: t = is_NOperp_F5(10, 3, 0, 1); t + (, 3, 5, '-', 1) + sage: g = t[0](*t[1:]); g + NO^-,perp(3, 5): Graph on 10 vertices + sage: g.is_strongly_regular(parameters=True) + (10, 3, 0, 1) + + TESTS: + + All of ``NO^+,perp(2m+1,5)`` and ``NO^-,perp(2m+1,5)`` appear:: + + sage: t = is_NOperp_F5(325, 60, 15, 10); t + (, 5, 5, '+', 1) + sage: t = is_NOperp_F5(300, 65, 10, 15); t + (, 5, 5, '-', 1) + sage: t = is_NOperp_F5(5,5,5,5); t + """ + cdef int n + r,s = eigenvalues(v,k,l,mu) # 2*e*5**(n-1), -e*5**(n-1); note exceptional case n=1 + if r is None: + return + if abs(r), 4, 2, '-') + sage: g = t[0](*t[1:]); g + NO^-(4, 2): Graph on 10 vertices + sage: g.is_strongly_regular(parameters=True) + (10, 3, 0, 1) + + TESTS: + + All of ``NO^+(2m,2)`` and ``NO^-(2m,2)`` appear:: + + sage: t = is_NO_F2(36, 15, 6, 6); t + (, 6, 2, '-') + sage: t = is_NO_F2(28, 15, 6, 10); t + (, 6, 2, '+') + sage: t = is_NO_F2(5,5,5,5); t + """ + cdef int n, e, p + p, n = is_prime_power(k+1, get_data=True) # k+1==2**(2*n-2) + if 2 == p and n != 0 and n % 2 == 0: + n = (n+2)/2 + e = (2**(2*n-1)-v)/2**(n-1) + if (abs(e) == 1 and + v == 2**(2*n-1)-e*2**(n-1) and + k == 2**(2*n-2)-1 and + l == 2**(2*n-3)-2 and + mu == 2**(2*n-3)+e*2**(n-2)): + from sage.graphs.generators.classical_geometries import NonisotropicOrthogonalPolarGraph + return (NonisotropicOrthogonalPolarGraph, 2*n, 2, '+' if e==1 else '-') + +@cached_function +def is_NO_F3(int v,int k,int l,int mu): + r""" + Test whether some NO^e,perp(2n,3) graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph`. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_NO_F3 + sage: t = is_NO_F3(15, 6, 1, 3); t + (, 4, 3, '-') + sage: g = t[0](*t[1:]); g + NO^-(4, 3): Graph on 15 vertices + sage: g.is_strongly_regular(parameters=True) + (15, 6, 1, 3) + + TESTS: + + All of ``NO^+(2m,3)`` and ``NO^-(2m,3)`` appear:: + + sage: t = is_NO_F3(126, 45, 12, 18); t + (, 6, 3, '-') + sage: t = is_NO_F3(117, 36, 15, 9); t + (, 6, 3, '+') + sage: t = is_NO_F3(5,5,5,5); t + """ + cdef int n, e, p + r,s = eigenvalues(v,k,l,mu) # e*3**(n-1), -e*3**(n-2) + if r is None: + return + if abs(r)0 else -1 + p, n = is_prime_power(abs(r), get_data=True) + if (3 == p and n != 0): + n += 1 + if (v == 3**(n-1)*(3**n-e)/2 and + k == 3**(n-1)*(3**(n-1)-e)/2 and + l == 3**(n-2)*(3**(n-1)+e)/2 and + mu == 3**(n-1)*(3**(n-2)-e)/2): + from sage.graphs.generators.classical_geometries import NonisotropicOrthogonalPolarGraph + return (NonisotropicOrthogonalPolarGraph, 2*n, 3, '+' if e==1 else '-') + +@cached_function +def is_NU(int v,int k,int l,int mu): + r""" + Test whether some NU(n,q)-graph, is `(v,k,\lambda,\mu)`-strongly regular. + + Note that n>2; for n=2 there is no s.r.g. For more information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicUnitaryPolarGraph` + and series C14 in [Hu75]_. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_NU + sage: t = is_NU(40, 27, 18, 18); t + (, 4, 2) + sage: g = t[0](*t[1:]); g + NU(4, 2): Graph on 40 vertices + sage: g.is_strongly_regular(parameters=True) + (40, 27, 18, 18) + + TESTS:: + + sage: t = is_NU(176, 135, 102, 108); t + (, 5, 2) + sage: t = is_NU(540, 224, 88, 96); t + (, 4, 3) + sage: t = is_NU(208, 75, 30, 25); t + (, 3, 4) + sage: t = is_NU(5,5,5,5); t + """ + cdef int n, q, e # special cases: n=3 or q=2 + r,s = eigenvalues(v,k,l,mu) #r,s = eq^{n-2} - 1, -e(q^2-q-1)q^{n-3} - 1, e=(-1)^n + if r is None: + return + r += 1 + s += 1 + if abs(r)>abs(s): + (r,s) = (s,r) + p, t = is_prime_power(abs(r), get_data=True) + if p==2: # it can be that q=2, then we'd have r>s now + pp, kk = is_prime_power(abs(s), get_data=True) + if pp==2 and kk>0: + (r,s) = (s,r) + p, t = is_prime_power(abs(r), get_data=True) + if r==1: + return + kr = k/(r-1) # eq^{n-1}+1 + e = 1 if kr>0 else -1 + q = (kr-1)/r + pp, kk = is_prime_power(q, get_data=True) + if p == pp and kk != 0: + n = t/kk + 2 + if (v == q**(n-1)*(q**n - e)/(q + 1) and + k == (q**(n-1) + e)*(q**(n-2) - e) and + l == q**(2*n-5)*(q+1) - e*q**(n-2)*(q-1) - 2 and + mu == q**(n-3)*(q + 1)*(q**(n-2) - e)): + from sage.graphs.generators.classical_geometries import NonisotropicUnitaryPolarGraph + return (NonisotropicUnitaryPolarGraph, n, q) + +@cached_function +def is_polhill(int v,int k,int l,int mu): + r""" + Test whether some graph from [Polhill09]_ is `(1024,k,\lambda,\mu)`-strongly regular. + + .. NOTE:: + + This function does not actually explore *all* strongly regular graphs + produced in [Polhill09]_, but only those on 1024 vertices. + + John Polhill offered his help if we attempt to write a code to guess, + given `(v,k,\lambda,\mu)`, which of his construction must be applied to + find the graph. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_polhill + sage: t = is_polhill(1024, 231, 38, 56); t + [. at ...>] + sage: g = t[0](*t[1:]); g # not tested (too long) + Graph on 1024 vertices + sage: g.is_strongly_regular(parameters=True) # not tested (too long) + (1024, 231, 38, 56) + sage: t = is_polhill(1024, 264, 56, 72); t + [. at ...>] + sage: t = is_polhill(1024, 297, 76, 90); t + [. at ...>] + sage: t = is_polhill(1024, 330, 98, 110); t + [. at ...>] + sage: t = is_polhill(1024, 462, 206, 210); t + [. at ...>] + + REFERENCE: + + .. [Polhill09] J. Polhill, + Negative Latin square type partial difference sets and + amorphic association schemes with Galois rings, + Journal of Combinatorial Designs 17, no. 3 (2009): 266-282. + http://onlinelibrary.wiley.com/doi/10.1002/jcd.20206/abstract + """ + if (v,k,l,mu) not in [(1024, 231, 38, 56), + (1024, 264, 56, 72), + (1024, 297, 76, 90), + (1024, 330, 98, 110), + (1024, 462, 206, 210)]: + return + + from itertools import product + from sage.categories.cartesian_product import cartesian_product + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + from copy import copy + + def additive_cayley(vertices): + g = Graph() + g.add_vertices(vertices[0].parent()) + edges = [(x,x+vv) + for vv in set(vertices) + for x in g] + g.add_edges(edges) + g.relabel() + return g + + # D is a Partial Difference Set of (Z4)^2, see section 2. + G = cartesian_product([IntegerModRing(4),IntegerModRing(4)]) + D = [ + [(2,0),(0,1),(0,3),(1,1),(3,3)], + [(1,0),(3,0),(0,2),(1,3),(3,1)], + [(1,2),(3,2),(2,1),(2,3),(2,2)] + ] + D = [map(G,x) for x in D] + + # The K_i are hyperplanes partitionning the nonzero elements of + # GF(2^s)^2. See section 6. + s = 3 + G1 = GF(2**s,'x') + Gp = cartesian_product([G1,G1]) + K = [Gp((x,1)) for x in G1]+[Gp((1,0))] + K = [[x for x in Gp if x[0]*uu+x[1]*vv == 0] for (uu,vv) in K] + + # We now define the P_{i,j}. see section 6. + + P = {} + P[0,1] = range((-1) + 1 , 2**(s-2)+1) + P[1,1] = range((-1) + 2**(s-2)+2 , 2**(s-1)+1) + P[2,1] = range((-1) + 2**(s-1)+2 , 2**(s-1)+2**(s-2)+1) + P[3,1] = range((-1) + 2**(s-1)+2**(s-2)+2, 2**(s)+1) + + P[0,2] = range((-1) + 2**(s-2)+2 , 2**(s-1)+2) + P[1,2] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + P[2,2] = range((-1) + 2**(s-1)+2**(s-2)+3, 2**(s)+1) + [0] + P[3,2] = range((-1) + 2 , 2**(s-2)+1) + + P[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+3) + P[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1] + P[2,3] = range((-1) + 3 , 2**(s-2)+2) + P[3,3] = range((-1) + 2**(s-2)+3 , 2**(s-1)+2) + + P[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + P[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1,2**(s-1)+2**(s-2)+2] + P[2,4] = range((-1) + 2**(s-2)+3 , 2**(s-1)+1) + [2**(s-1)+2**(s-2)+1,1] + P[3,4] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+1) + [2**(s-2)+1,0] + + R = {x:copy(P[x]) for x in P} + + for x in P: + P[x] = [K[i] for i in P[x]] + P[x] = set(sum(P[x],[])).difference([Gp((0,0))]) + + P[1,4].add(Gp((0,0))) + P[2,4].add(Gp((0,0))) + P[3,4].add(Gp((0,0))) + + # We now define the R_{i,j}. see *end* of section 6. + + R[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + R[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1,2**(s-1)+2**(s-2)+2] + R[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [2**(s-1)+2**(s-2)+2] + R[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1] + + for x in R: + R[x] = [K[i] for i in R[x]] + R[x] = set(sum(R[x],[])).difference([Gp((0,0))]) + + R[1,3].add(Gp((0,0))) + R[2,4].add(Gp((0,0))) + R[3,4].add(Gp((0,0))) + + # Dabcd = Da, Db, Dc, Dd (cf. p273) + # D1234 = D1, D2, D3, D4 (cf. p276) + Dabcd = [] + D1234 = [] + + Gprod = cartesian_product([G,Gp]) + for DD,PQ in [(Dabcd,P), (D1234,R)]: + for i in range(1,5): + Dtmp = [product([G.zero()],PQ[0,i]), + product(D[0],PQ[1,i]), + product(D[1],PQ[2,i]), + product(D[2],PQ[3,i])] + Dtmp = map(set,Dtmp) + Dtmp = map(Gprod,sum(map(list,Dtmp),[])) + DD.append(Dtmp) + + # Now that we have the data, we can return the graphs. + if k == 231: + return [lambda :additive_cayley(Dabcd[0])] + if k == 264: + return [lambda :additive_cayley(D1234[2])] + if k == 297: + return [lambda :additive_cayley(D1234[0]+D1234[1]+D1234[2]).complement()] + if k == 330: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1]+Dabcd[2]).complement()] + if k == 462: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1])] + +def is_RSHCD(int v,int k,int l,int mu): + r""" + Test whether some RSHCD graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see :func:`SRG_from_RSHCD`. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_RSHCD + sage: t = is_RSHCD(64,27,10,12); t + [, 64, 27, 10, 12] + sage: g = t[0](*t[1:]); g + Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 27, 10, 12) + + """ + if SRG_from_RSHCD(v,k,l,mu,existence=True): + return [SRG_from_RSHCD,v,k,l,mu] + +def SRG_from_RSHCD(v,k,l,mu, existence=False,check=True): + r""" + Return a `(v,k,l,mu)`-strongly regular graph from a RSHCD + + This construction appears in 8.D of [BvL84]_. For more information, see + :func:`~sage.combinat.matrices.hadamard_matrix.regular_symmetric_hadamard_matrix_with_constant_diagonal`. + + INPUT: + + - ``v,k,l,mu`` (integers) + + - ``existence`` (boolean) -- whether to return a graph or to test if Sage + can build such a graph. + + - ``check`` (boolean) -- whether to check that output is correct before + returning it. As this is expected to be useless (but we are cautious + guys), you may want to disable it whenever you want speed. Set to ``True`` + by default. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_from_RSHCD + sage: SRG_from_RSHCD(784, 0, 14, 38, existence=True) + False + sage: SRG_from_RSHCD(784, 377, 180, 182, existence=True) + True + sage: SRG_from_RSHCD(144, 65, 28, 30) + Graph on 144 vertices + + TESTS:: + + sage: SRG_from_RSHCD(784, 0, 14, 38) + Traceback (most recent call last): + ... + ValueError: I do not know how to build a (784, 0, 14, 38)-SRG from a RSHCD + + """ + from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal + sgn = lambda x: 1 if x>=0 else -1 + n = v + a = (n-4*mu)//2 + e = 2*k - n + 1 + a + t = abs(a//2) + + if (e**2 == 1 and + k == (n-1-a+e)/2 and + l == (n-2*a)/4 - (1-e) and + mu== (n-2*a)/4 and + regular_symmetric_hadamard_matrix_with_constant_diagonal(n,sgn(a)*e,existence=True)): + if existence: + return True + from sage.matrix.constructor import identity_matrix as I + from sage.matrix.constructor import ones_matrix as J + + H = regular_symmetric_hadamard_matrix_with_constant_diagonal(n,sgn(a)*e) + if list(H.column(0)[1:]).count(1) == k: + H = -H + G = Graph((J(n)-I(n)-H+H[0,0]*I(n))/2,loops=False,multiedges=False,format="adjacency_matrix") + if check: + assert G.is_strongly_regular(parameters=True) == (v,k,l,mu) + return G + + if existence: + return False + raise ValueError("I do not know how to build a {}-SRG from a RSHCD".format((v,k,l,mu))) + +@cached_function +def is_unitary_polar(int v,int k,int l,int mu): + r""" + Test whether some Unitary Polar graph is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see http://www.win.tue.nl/~aeb/graphs/srghub.html. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_unitary_polar + sage: t = is_unitary_polar(45, 12, 3, 3); t + (, 4, 2) + sage: g = t[0](*t[1:]); g + Unitary Polar Graph U(4, 2); GQ(4, 2): Graph on 45 vertices + sage: g.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + + sage: t = is_unitary_polar(5,5,5,5); t + + TESTS: + + All the ``U(n,q)`` appear:: + + sage: t = is_unitary_polar(45, 12, 3, 3); t + (, 4, 2) + sage: t = is_unitary_polar(165, 36, 3, 9); t + (, 5, 2) + sage: t = is_unitary_polar(693, 180, 51, 45); t + (, 6, 2) + sage: t = is_unitary_polar(1105, 80, 15, 5); t + (, 4, 4) + """ + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + q = k/mu + if q*mu != k or q < 2: + return + p,t = is_prime_power(q, get_data=True) + if p**t != q or t % 2 != 0: + return + # at this point we know that we should have U(n,q) for some n and q=p^t, t even + if r > 0: + q_pow_d_minus_one = r+1 + else: + q_pow_d_minus_one = -s-1 + ppp,ttt = is_prime_power(q_pow_d_minus_one, get_data=True) + d = ttt/t + 1 + if ppp != p or (d-1)*t != ttt: + return + t /= 2 + # U(2d+1,q); write q^(1/2) as p^t + if (v == (q**d - 1)*((q**d)*p**t + 1)/(q - 1) and + k == q*(q**(d-1) - 1)*((q**d)/(p**t) + 1)/(q - 1) and + l == q*q*(q**(d-2)-1)*((q**(d-1))/(p**t) + 1)/(q - 1) + q - 1): + from sage.graphs.generators.classical_geometries import UnitaryPolarGraph + return (UnitaryPolarGraph, 2*d+1, p**t) + + # U(2d,q); + if (v == (q**d - 1)*((q**d)/(p**t) + 1)/(q - 1) and + k == q*(q**(d-1) - 1)*((q**(d-1))/(p**t) + 1)/(q - 1) and + l == q*q*(q**(d-2)-1)*((q**(d-2))/(p**t) + 1)/(q - 1) + q - 1): + from sage.graphs.generators.classical_geometries import UnitaryPolarGraph + return (UnitaryPolarGraph, 2*d, p**t) + +@cached_function +def is_unitary_dual_polar(int v,int k,int l,int mu): + r""" + Test whether some Unitary Dual Polar graph is `(v,k,\lambda,\mu)`-strongly regular. + + This must be the U_5(q) on totally isotropic lines. + For more information, see http://www.win.tue.nl/~aeb/graphs/srghub.html. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_unitary_dual_polar + sage: t = is_unitary_dual_polar(297, 40, 7, 5); t + (, 5, 2) + sage: g = t[0](*t[1:]); g + Unitary Dual Polar Graph DU(5, 2); GQ(8, 4): Graph on 297 vertices + sage: g.is_strongly_regular(parameters=True) + (297, 40, 7, 5) + sage: t = is_unitary_dual_polar(5,5,5,5); t + + TESTS:: + + sage: is_unitary_dual_polar(6832, 270, 26, 10) + (, 5, 3) + """ + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + q = mu - 1 + if q < 2: + return + p,t = is_prime_power(q, get_data=True) + if p**t != q or t % 2 != 0: + return + if (r < 0 and q != -r - 1) or (s < 0 and q != -s - 1): + return + t /= 2 + # we have correct mu, negative eigenvalue, and q=p^(2t) + if (v == (q**2*p**t + 1)*(q*p**t + 1) and + k == q*p**t*(q + 1) and + l == k - 1 - q**2*p**t): + from sage.graphs.generators.classical_geometries import UnitaryDualPolarGraph + return (UnitaryDualPolarGraph, 5, p**t) + +@cached_function +def is_GQqmqp(int v,int k,int l,int mu): + r""" + Test whether some `GQ(q-1,q+1)` or `GQ(q+1,q-1)`-graph is `(v,k,\lambda,\mu)`-srg. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_GQqmqp + sage: t = is_GQqmqp(27,10,1,5); t + (, 3, False) + sage: g = t[0](*t[1:]); g + AS(3); GQ(2, 4): Graph on 27 vertices + sage: t = is_GQqmqp(45,12,3,3); t + (, 3, True) + sage: g = t[0](*t[1:]); g + AS(3)*; GQ(4, 2): Graph on 45 vertices + sage: g.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + sage: t = is_GQqmqp(16,6,2,2); t + (, 2, True) + sage: g = t[0](*t[1:]); g + T2*(O,2)*; GQ(3, 1): Graph on 16 vertices + sage: g.is_strongly_regular(parameters=True) + (16, 6, 2, 2) + sage: t = is_GQqmqp(64,18,2,6); t + (, 4, False) + sage: g = t[0](*t[1:]); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: (S,T)=(127,129) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, False) + sage: (S,T)=(129,127) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, True) + sage: (S,T)=(124,126) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, False) + sage: (S,T)=(126,124) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, True) + sage: t = is_GQqmqp(5,5,5,5); t + """ + # do we have GQ(s,t)? we must have mu=t+1, s=l+1, + # v=(s+1)(st+1), k=s(t+1) + S=l+1 + T=mu-1 + q = (S+T)//2 + p, w = is_prime_power(q, get_data=True) + if (v == (S+1)*(S*T+1) and + k == S*(T+1) and + q == p**w and + (S+T)/2 == q): + if p % 2 == 0: + from sage.graphs.generators.classical_geometries\ + import T2starGeneralizedQuadrangleGraph as F + else: + from sage.graphs.generators.classical_geometries\ + import AhrensSzekeresGeneralizedQuadrangleGraph as F + if (S,T) == (q-1, q+1): + return (F, q, False) + elif (S,T) == (q+1, q-1): + return (F, q, True) + +@cached_function +def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): + r""" + Test whether some descendant graph of an s.r.g. is `(v,k_0,\lambda,\mu)`-s.r.g. + + We check whether there can exist `(v+1,k,\lambda^*,\mu^*)`-s.r.g. `G` so + that ``self`` is a descendant graph of the regular two-graph specified + by `G`. + Specifically, we must have that `v+1=2(2k-\lambda^*-\mu^*)`, and + `k_0=2(k-\mu^*)`, `\lambda=k+\lambda^*-2\mu^*`, `\mu=k-\mu^*`, which give 2 + independent linear conditions, say `k-\mu^*=\mu` and + `\lambda^*-\mu^*=\lambda-\mu`. Further, there is a quadratic relation + `2 k^2-(v+1+4 \mu) k+ 2 v \mu=0`. + + If we can contruct such `G` then we return a function to build a + `(v,k_0,\lambda,\mu)`-s.r.g. For more information, + see 10.3 in http://www.win.tue.nl/~aeb/2WF02/spectra.pdf + + INPUT: + + - ``v,k0,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists and is known, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_twograph_descendant_of_srg + sage: t = is_twograph_descendant_of_srg(27, 10, 1, 5); t + (.la at... + sage: g = t[0](*t[1:]); g + descendant of complement(Johnson graph with parameters 8,2) at {5, 7}: Graph on 27 vertices + sage: g.is_strongly_regular(parameters=True) + (27, 10, 1, 5) + sage: t = is_twograph_descendant_of_srg(5,5,5,5); t + + TESTS:: + + sage: graphs.strongly_regular_graph(279, 150, 85, 75, existence=True) + True + sage: graphs.strongly_regular_graph(279, 150, 85, 75).is_strongly_regular(parameters=True) # optional - gap_packages internet + (279, 150, 85, 75) + """ + cdef int b, k, s + if k0 != 2*mu or v % 2 == 0: + return + b = v+1+4*mu + D = sqrt(b**2-16*v*mu) + if int(D)==D: + for kf in [(-D+b)/4, (D+b)/4]: + k = int(kf) + if k == kf and \ + strongly_regular_graph(v+1, k, l - 2*mu + k , k - mu, existence=True): + def la(vv): + from sage.combinat.designs.twographs import twograph_descendant + g = strongly_regular_graph(vv, k, l - 2*mu + k) + return twograph_descendant(g, g.vertex_iterator().next(), name=True) + return(la, v+1) + return + +@cached_function +def is_taylor_twograph_srg(int v,int k,int l,int mu): + r""" + Test whether some Taylor two-graph SRG is `(v,k,\lambda,\mu)`-strongly regular. + + For more information, see §7E of [BvL84]_. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph + :func:`TaylorTwographSRG + ` if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_taylor_twograph_srg + sage: t = is_taylor_twograph_srg(28, 15, 6, 10); t + (, 3) + sage: g = t[0](*t[1:]); g + Taylor two-graph SRG: Graph on 28 vertices + sage: g.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + sage: t = is_taylor_twograph_srg(5,5,5,5); t + + TESTS:: + + sage: is_taylor_twograph_srg(730, 369, 168, 205) + (, 9) + + """ + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + p,t = is_prime_power(v-1, get_data=True) + if p**t+1 != v or t % 3 != 0 or p % 2 == 0: + return + q = p**(t//3) + if (k, l, mu) == (q*(q**2+1)/2, (q**2+3)*(q-1)/4, (q**2+1)*(q+1)/4): + from sage.graphs.generators.classical_geometries import TaylorTwographSRG + return (TaylorTwographSRG, q) + return + +def is_switch_OA_srg(int v, int k, int l, int mu): + r""" + Test whether some *switch* `OA(k,n)+*` is `(v,k,\lambda,\mu)`-strongly regular. + + The "switch* `OA(k,n)+*` graphs appear on `Andries Brouwer's database + `__ and are built by + adding an isolated vertex to a + :meth:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`, + and a :meth:`Seidel switching ` a set of disjoint + `n`-cocliques. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: graphs.strongly_regular_graph(170, 78, 35, 36) # indirect doctest + Graph on 170 vertices + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import is_switch_OA_srg + sage: t = is_switch_OA_srg(5,5,5,5); t + sage: t = is_switch_OA_srg(170, 78, 35, 36); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (170, 78, 35, 36) + sage: t = is_switch_OA_srg(290, 136, 63, 64); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (290, 136, 63, 64) + sage: is_switch_OA_srg(626, 300, 143, 144) + (.switch_OA_srg at ..., 12, 25) + sage: is_switch_OA_srg(842, 406, 195, 196) + (.switch_OA_srg at ..., 14, 29) + + """ + from sage.combinat.designs.orthogonal_arrays import orthogonal_array + + cdef int n_2_p_1 = v + cdef int n = floor(sqrt(n_2_p_1-1)) + + if n*n != n_2_p_1-1: # is it a square? + return None + + cdef int c = k/n + if (k % n or + l != c*c-1 or + k != 1+(c-1)*(c+1)+(n-c)*(n-c-1) or + not orthogonal_array(c+1,n,existence=True,resolvable=True)): + return None + + def switch_OA_srg(c,n): + from itertools import izip + OA = map(tuple,orthogonal_array(c+1,n,resolvable=True)) + g = Graph([OA,lambda x,y: any(xx==yy for xx,yy in izip(x,y))],loops=False) + g.add_vertex(0) + g.seidel_switching(OA[:c*n]) + return g + + return (switch_OA_srg,c,n) + +cdef eigenvalues(int v,int k,int l,int mu): + r""" + Return the eigenvalues of a (v,k,l,mu)-strongly regular graph. + + If the set of parameters is not feasible, or if they correspond to a + conference graph, the function returns ``(None,None)``. + + INPUT: + + - ``v,k,l,mu`` (integers) + + """ + # See 1.3.1 of [Distance-regular graphs] + b = (mu-l) + c = (mu-k) + D = b**2-4*c + if not is_square(D): + return [None,None] + return [(-b+sqrt(D))/2.0, + (-b-sqrt(D))/2.0] + +def _H_3_cayley_graph(L): + r""" + return the `L`-Cayley graph of the group `H_3` from Prop. 12 in [JK03]_. + + INPUT: + + - the list of words for the generating set in the format ["abc",...,"xyz"] for + a,b,...,z being integers between 0 and 4. + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import _H_3_cayley_graph + sage: _H_3_cayley_graph(["100","110","130","140","200","230","240","300"]) + Graph on 100 vertices + """ + from sage.groups.free_group import FreeGroup + from sage.groups.finitely_presented import FinitelyPresentedGroup + G = FreeGroup('x,y,z') + x,y,z = G.gens() + rels = (x**5,y**5,z**4,x*y*x**(-1)*y**(-1),z*x*z**(-1)*x**(-2),z*y*z**(-1)*y**(-2)) + G = FinitelyPresentedGroup(G,rels) + x,y,z = G.gens() + H = G.as_permutation_group() + L = map(lambda x:map(int,x),L) + x,y,z=(H.gen(0),H.gen(1),H.gen(2)) + L = [H(x**xx*y**yy*z**zz) for xx,yy,zz in L] + return Graph(H.cayley_graph(generators=L, simple=True)) + +def SRG_100_44_18_20(): + r""" + Return a `(100, 44, 18, 20)`-strongly regular graph. + + This graph is built as a Cayley graph, using the construction for `\Delta_1` + with group `H_3` presented in Table 8.1 of [JK03]_ + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_100_44_18_20 + sage: G = SRG_100_44_18_20() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (100, 44, 18, 20) + + REFERENCES: + + .. [JK03] L. K. Jørgensen, M. Klin, M., + Switching of edges in strongly regular graphs. + I. A family of partial difference sets on 100 vertices, + Electronic Journal of Combinatorics 10(1), 2003. + """ + return _H_3_cayley_graph(["100","110","130","140","200","230","240","300", + "310","320","400","410","420","440","041","111","221","231","241", + "321","331","401","421","441","002","042","112","122","142","212", + "232","242","322","342","033","113","143","223","303","333","343", + "413","433","443"]) + +def SRG_100_45_20_20(): + r""" + Return a `(100, 45, 20, 20)`-strongly regular graph. + + This graph is built as a Cayley graph, using the construction for `\Gamma_3` + with group `H_3` presented in Table 8.1 of [JK03]_. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_100_45_20_20 + sage: G = SRG_100_45_20_20() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (100, 45, 20, 20) + """ + return _H_3_cayley_graph(["120","140","200","210","201","401","411","321", + "002","012","022","042","303","403","013","413","240","031","102", + "323","300","231","132","133","310","141","142","233","340","241", + "202","333","410","341","222","433","430","441","242","302","312", + "322","332","442","143"]) + + +def SRG_105_32_4_12(): + r""" + Return a `(105, 32, 4, 12)`-strongly regular graph. + + The vertices are the flags of the projective plane of order 4. Two flags + `(a,A)` and `(b,B)` are adjacent if the point `a` is on the line `B` or + the point `b` is on the line `A`, and `a \neq b`, `A \neq B`. See + Theorem 2.7 in [GS70]_, and [Co06]_. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_105_32_4_12 + sage: G = SRG_105_32_4_12(); G + Graph on 105 vertices + sage: G.is_strongly_regular(parameters=True) + (105, 32, 4, 12) + + REFERENCES: + + .. [GS70] J.-M. Goethals and J. J. Seidel, + Strongly regular graphs derived from combinatorial designs, + Can. J. Math. 22 (1970) 597-614. + http://dx.doi.org/10.4153/CJM-1970-067-9 + + .. [Co06] K. Coolsaet, + The uniqueness of the strongly regular graph srg(105,32,4,12), + Bull. Belg. Math. Soc. 12(2006), 707-718. + http://projecteuclid.org/euclid.bbms/1136902608 + """ + from sage.combinat.designs.block_design import ProjectiveGeometryDesign + P = ProjectiveGeometryDesign(2,1,GF(4,'a')) + IG = P.incidence_graph().line_graph() + a = IG.automorphism_group() + h = a.stabilizer(a.domain()[0]) + o = filter(lambda x: len(x)==32, h.orbits())[0][0] + e = a.orbit((a.domain()[0],o),action="OnSets") + G = Graph() + G.add_edges(e) + return G + +def SRG_120_77_52_44(): + r""" + Return a `(120,77,52,44)`-strongly regular graph. + + To build this graph, we first build a `2-(21,7,12)` design, by removing two + points from the :func:`~sage.combinat.designs.block_design.WittDesign` on 23 + points. We then build the intersection graph of blocks with intersection + size 3. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_120_77_52_44 + sage: G = SRG_120_77_52_44() # optional - gap_packages + sage: G.is_strongly_regular(parameters=True) # optional - gap_packages + (120, 77, 52, 44) + """ + from sage.combinat.designs.block_design import WittDesign + from sage.combinat.designs.incidence_structures import IncidenceStructure + W = WittDesign(23) + H = IncidenceStructure([x for x in W if 22 not in x and 21 not in x]) + return H.intersection_graph(3) + +def SRG_144_39_6_12(): + r""" + Return a `(144,39,6,12)`-strongly regular graph. + + This graph is obtained as an orbit of length 2808 on sets of cardinality 2 + (among 2 such orbits) of the group `PGL_3(3)` acting on the (right) cosets of + a subgroup of order 39. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_144_39_6_12 + sage: G = SRG_144_39_6_12() + sage: G.is_strongly_regular(parameters=True) + (144, 39, 6, 12) + """ + + from sage.libs.gap.libgap import libgap + g=libgap.ProjectiveGeneralLinearGroup(3,3) + ns=g.Normalizer(g.SylowSubgroup(13)) + G=g.Action(g.RightCosets(ns),libgap.OnRight) + H=G.Stabilizer(1) + for o in filter(lambda x: len(x)==39, H.Orbits()): + h = Graph() + h.add_edges(G.Orbit([1,o[0]],libgap.OnSets)) + if h.is_strongly_regular(): + h.relabel() + return h + +def SRG_176_49_12_14(): + r""" + Return a `(176,49,12,14)`-strongly regular graph. + + This graph is built from the symmetric Higman-Sims design. In + [BrouwerPolarities82]_, it is explained that there exists an involution + `\sigma` exchanging the points and blocks of the Higman-Sims design, such + that each point is mapped on a block that contains it (i.e. `\sigma` is a + 'polarity with all universal points'). The graph is then built by making two + vertices `u,v` adjacent whenever `v\in \sigma(u)`. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_176_49_12_14 + sage: G = SRG_176_49_12_14() # optional - gap_packages # long time + sage: G.is_strongly_regular(parameters=True) # optional - gap_packages # long time + (176, 49, 12, 14) + + REFERENCE: + + .. [BrouwerPolarities82] A. Brouwer, + Polarities of G. Higman's symmetric design and a strongly regular graph on 176 vertices, + Aequationes mathematicae 25, no. 1 (1982): 77-82. + """ + from sage.combinat.designs.database import HigmanSimsDesign + d = HigmanSimsDesign() + g = d.incidence_graph(labels=True) + ag=g.automorphism_group().conjugacy_classes_representatives() + + # Looking for an involution that maps a point of the design to one of the + # blocks that contains it. It is called a polarity with only absolute + # points in + for aut in ag: + try: + 0 in aut(0) + except TypeError: + continue + if (aut.order() == 2 and + all(i in aut(i) for i in d.ground_set())): + g = Graph() + g.add_edges((u,v) for u in d.ground_set() for v in aut(u)) + return g + +def SRG_176_105_68_54(): + r""" + Return a `(176, 105, 68, 54)`-strongly regular graph. + + To build this graph, we first build a `2-(22,7,16)` design, by removing one + point from the :func:`~sage.combinat.designs.block_design.WittDesign` on 23 + points. We then build the intersection graph of blocks with intersection + size 3. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_176_105_68_54 + sage: G = SRG_176_105_68_54() # optional - gap_packages + sage: G.is_strongly_regular(parameters=True) # optional - gap_packages + (176, 105, 68, 54) + """ + from sage.combinat.designs.block_design import WittDesign + from sage.combinat.designs.incidence_structures import IncidenceStructure + W = WittDesign(23) + H = IncidenceStructure([x for x in W if 22 not in x]) + return H.intersection_graph(3) + +def SRG_210_99_48_45(): + r""" + Return a strongly regular graph with parameters `(210, 99, 48, 45)` + + This graph is from Example 4.2 in [KPRWZ10]_. One considers the action of + the symmetric group `S_7` on the 210 digraphs isomorphic to the + disjoint union of `K_1` and the circulant 6-vertex digraph + ``digraphs.Circulant(6,[1,4])``. It has 16 orbitals; the package [COCO]_ + found a megring of them, explicitly described in [KPRWZ10]_, resulting in + this graph. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_210_99_48_45 + sage: g=SRG_210_99_48_45() + sage: g.is_strongly_regular(parameters=True) + (210, 99, 48, 45) + + REFERENCES: + + .. [KPRWZ10] M. H. Klin, C. Pech, S. Reichard, A. Woldar, M. Zvi-Av, + Examples of computer experimentation in algebraic combinatorics, + ARS MATHEMATICA CONTEMPORANEA 3 (2010) 237–258 + http://amc-journal.eu/index.php/amc/article/viewFile/119/118 + + .. [COCO] I. A. Faradjev and M. H. Klin, + Computer package for computations with coherent configurations, + Proc. ISSAC-91, ACM Press, Bonn, 1991, pages 219–223; + code, by I.A.Faradjev (with contributions by A.E.Brouwer, D.V.Pasechnik) + https://github.com/dimpase/coco + + """ + from sage.libs.gap.libgap import libgap + from sage.combinat.permutation import Permutation + def ekg(g0): # return arcs of the Cayley digraph of on {g,g^4} + g = Permutation(g0) + return libgap.Set(map(lambda x: (x,g(x)), range(1,8))\ + + map(lambda x: (x,g(g(g(g(x))))), range(1,8))) + + kd=map(ekg, + [(7, 1, 2, 3, 4, 5), (7, 1, 3, 4, 5, 6), + (7, 3, 4, 5, 6, 2), (7, 1, 4, 3, 5, 6), + (7, 3, 1, 4, 5, 6), (7, 2, 4, 3, 5, 6), + (7, 3, 2, 4, 5, 1), (7, 2, 4, 3, 5, 1)]) + s=libgap.SymmetricGroup(7) + O=s.Orbit(kd[0],libgap.OnSetsTuples) + sa=s.Action(O,libgap.OnSetsTuples) + G=Graph() + for g in kd[1:]: + G.add_edges(libgap.Orbit(sa,[libgap.Position(O,kd[0]),\ + libgap.Position(O,g)],libgap.OnSets)) + return G + +def SRG_243_110_37_60(): + r""" + Return a `(243, 110, 37, 60)`-strongly regular graph. + + To build this graph, we consider the orthogonal complement of the + :func:`~sage.coding.code_constructions.TernaryGolayCode`, which has 243 + points. On those points we define a graph, in which two points are adjacent + when their hamming distance is equal to 9. This construction appears in + [GS75]_. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_243_110_37_60 + sage: G = SRG_243_110_37_60() + sage: G.is_strongly_regular(parameters=True) + (243, 110, 37, 60) + + REFERENCE: + + .. [GS75] J.M. Goethals, and J. J. Seidel, + The regular two-graph on 276 vertices, + Discrete Mathematics 12, no. 2 (1975): 143-158. + http://dx.doi.org/10.1016/0012-365X(75)90029-1 + """ + from sage.coding.code_constructions import TernaryGolayCode + M = TernaryGolayCode().generator_matrix() + V = list(M.right_kernel()) + return Graph([range(len(V)), lambda x,y:(V[x]-V[y]).hamming_weight() == 9 ]) + +def SRG_253_140_87_65(): + r""" + Return a `(253, 140, 87, 65)`-strongly regular graph. + + To build this graph, we first build the + :func:`~sage.combinat.designs.block_design.WittDesign` on 23 points which is + a `2-(23,7,21)` design. We then build the intersection graph of blocks with + intersection size 3. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_253_140_87_65 + sage: G = SRG_253_140_87_65() # optional - gap_packages + sage: G.is_strongly_regular(parameters=True) # optional - gap_packages + (253, 140, 87, 65) + """ + from sage.combinat.designs.block_design import WittDesign + from sage.combinat.designs.incidence_structures import IncidenceStructure + W = WittDesign(23) + return W.intersection_graph(3) + +def SRG_196_91_42_42(): + r""" + Return a `(196,91,42,42)`-strongly regular graph. + + This strongly regular graph is built following the construction provided in + Corollary 8.2.27 of [IS06]_. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_196_91_42_42 + sage: G = SRG_196_91_42_42() + sage: G.is_strongly_regular(parameters=True) + (196, 91, 42, 42) + + REFERENCE: + + .. [IS06] Y.J. Ionin, S. Shrikhande, + Combinatorics of symmetric designs. + Cambridge University Press, 2006. + """ + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + from sage.graphs.generators.intersection import IntersectionGraph + k = 7 + G = IntegerModRing(91) + A = map(G,{0, 10, 27, 28, 31, 43, 50}) + B = map(G,{0, 11, 20, 25, 49, 55, 57}) + H = map(G,[13*i for i in range(k)]) + U = map(frozenset,[[x+z for x in A] for z in G]) + V = map(frozenset,[[x+z for x in B] for z in G]) + W = map(frozenset,[[x+z for x in H] for z in G]) + G = IntersectionGraph(U+V+W) + + G.seidel_switching(U) + + G.add_edges((-1,x) for x in U) + G.relabel() + return G + +def SRG_220_84_38_28(): + r""" + Return a `(280, 135, 70, 60)`-strongly regular graph. + + This graph is obtained from the + :meth:`~IncidenceStructure.intersection_graph` of a + :func:`~sage.combinat.designs.database.BIBD_45_9_8`. This construction + appears in VII.11.2 from [DesignHandbook]_ + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_220_84_38_28 + sage: g=SRG_220_84_38_28() + sage: g.is_strongly_regular(parameters=True) + (220, 84, 38, 28) + """ + from sage.combinat.designs.database import BIBD_45_9_8 + from sage.combinat.designs.incidence_structures import IncidenceStructure + G = IncidenceStructure(BIBD_45_9_8()).intersection_graph(3) + G.relabel() + return G + +def SRG_276_140_58_84(): + r""" + Return a `(276, 140, 58, 84)`-strongly regular graph. + + The graph is built from from + :meth:`~sage.graphs.graph_generators.GraphGenerators.McLaughlinGraph`, with + an added isolated vertex. We then perform a + :meth:`~Graph.seidel_switching` on a set of 28 disjoint 5-cliques, which + exist by cf. [HT96]_. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_276_140_58_84 + sage: g=SRG_276_140_58_84() # long time # optional - gap_packages + sage: g.is_strongly_regular(parameters=True) # long time # optional - gap_packages + (276, 140, 58, 84) + + REFERENCE: + + .. [HT96] W. H. Haemers and V. D. Tonchev, + Spreads in strongly regular graphs, + Designs, Codes and Cryptography 8 (1996) 145-157. + """ + g = McLaughlinGraph() + C = [[ 0, 72, 87, 131, 136], [ 1, 35, 61, 102, 168], [ 2, 32, 97, 125, 197], [ 3, 22, 96, 103, 202], + [ 4, 46, 74, 158, 229], [ 5, 83, 93, 242, 261], [ 6, 26, 81, 147, 176], [ 7, 42, 63, 119, 263], + [ 8, 49, 64, 165, 227], [ 9, 70, 85, 208, 273], [10, 73, 92, 230, 268], [11, 54, 95, 184, 269], + [12, 55, 62, 185, 205], [13, 51, 65, 162, 254], [14, 78, 88, 231, 274], [15, 40, 59, 117, 252], + [16, 24, 71, 137, 171], [17, 39, 43, 132, 163], [18, 57, 79, 175, 271], [19, 68, 80, 217, 244], + [20, 75, 98, 239, 267], [21, 33, 56, 113, 240], [23, 127, 152, 164, 172], [25, 101, 128, 183, 264], + [27, 129, 154, 160, 201], [28, 126, 144, 161, 228], [29, 100, 133, 204, 266], [30, 108, 146, 200, 219]] + g.add_vertex(-1) + g.seidel_switching(sum(C,[])) + g.relabel() + g.name('') + return g + +def SRG_280_135_70_60(): + r""" + Return a strongly regular graph with parameters `(280, 135, 70, 60)`. + + This graph is built from the action of `J_2` on a `3.PGL(2,9)` subgroup it + contains. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_280_135_70_60 + sage: g=SRG_280_135_70_60() # long time # optional - gap_packages + sage: g.is_strongly_regular(parameters=True) # long time # optional - gap_packages + (280, 135, 70, 60) + """ + from sage.interfaces.gap import gap + from sage.groups.perm_gps.permgroup import PermutationGroup + from sage.graphs.graph import Graph + + gap.load_package("AtlasRep") + + # A representation of J2 acting on a 3.PGL(2,9) it contains. + J2 = PermutationGroup(gap('AtlasGenerators("J2",2).generators')) + edges = J2.orbit((1,2),"OnSets") + g = Graph() + g.add_edges(edges) + g.relabel() + return g + +def SRG_280_117_44_52(): + r""" + Return a strongly regular graph with parameters `(280, 117, 44, 52)`. + + This graph is built according to a very pretty construction of Mathon and + Rosa [MR85]_: + + The vertices of the graph `G` are all partitions of a set of 9 elements + into `\{\{a,b,c\},\{d,e,f\},\{g,h,i\}\}`. The cross-intersection of two + such partitions `P=\{P_1,P_2,P_3\}` and `P'=\{P'_1,P'_2,P'_3\}` being + defined as `\{P_i \cap P'_j: 1\leq i,j\leq 3\}`, two vertices of `G` are + set to be adjacent if the cross-intersection of their respective + partitions does not contain exactly 7 nonempty sets. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_280_117_44_52 + sage: g=SRG_280_117_44_52() + sage: g.is_strongly_regular(parameters=True) + (280, 117, 44, 52) + + REFERENCE: + + .. [MR85] R. Mathon and A. Rosa, + A new strongly regular graph, + Journal of Combinatorial Theory, Series A 38, no. 1 (1985): 84-86. + http://dx.doi.org/10.1016/0097-3165(85)90025-1 + """ + from sage.graphs.hypergraph_generators import hypergraphs + + # V is the set of partions {{a,b,c},{d,e,f},{g,h,i}} of {0,...,8} + H = hypergraphs.CompleteUniform(9,3) + g = H.intersection_graph() + V = g.complement().cliques_maximal() + V = map(frozenset,V) + + # G is the graph defined on V in which two vertices are adjacent when they + # corresponding partitions cross-intersect on 7 nonempty sets + G = Graph([V, lambda x,y: + sum(any(xxx in yy for xxx in xx) for xx in x for yy in y) != 7], + loops=False) + return G + +def strongly_regular_from_two_weight_code(L): + r""" + Return a strongly regular graph from a two-weight code. + + A code is said to be a *two-weight* code the weight of its nonzero codewords + (i.e. their number of nonzero coordinates) can only be one of two integer + values `w_1,w_2`. It is said to be *projective* if the minimum weight of the + dual code is `\geq 3`. A strongly regular graph can be built from a + two-weight projective code with weights `w_1,w_2` (assuming `w_1`__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_256_187_138_132 + sage: G = SRG_256_187_138_132() + sage: G.is_strongly_regular(parameters=True) + (256, 187, 138, 132) + """ + x=("10000000100111100110000001101000100111000011100101011010111111010110", + "01000000010011110011000000110100010011100001110010101101011111101011", + "00100000001001111101100000011010001001110000111001010110101111110101", + "00010000100011011100110001100101100011111011111001100001101000101100", + "00001000110110001100011001011010011110111110011001111010001011000000", + "00000100111100100000001101000101101000011100101001110111111010110110", + "00000010011110010000000110100010111100001110010100101011111101011011", + "00000001001111001100000011010001011110000111001010010101111110101101") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_416_100_36_20(): + r""" + Return a `(416,100,36,20)`-strongly regular graph. + + This graph is obtained as an orbit on sets of cardinality 2 + (among 2 that exists) of the group `G_2(4)`. + This graph is isomorphic to the subgraph of the from :meth:`Suzuki Graph + ` induced on + the neighbors of a vertex. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_416_100_36_20 + sage: g = SRG_416_100_36_20() # optional - gap_packages # long time + sage: g.is_strongly_regular(parameters=True) # optional - gap_packages # long time + (416, 100, 36, 20) + """ + from sage.libs.gap.libgap import libgap + libgap.LoadPackage("AtlasRep") + g=libgap.AtlasGroup("G2(4)",libgap.NrMovedPoints,416) + h = Graph() + h.add_edges(g.Orbit([1,5],libgap.OnSets)) + h.relabel() + return h + +def SRG_560_208_72_80(): + r""" + Return a `(560,208,72,80)`-strongly regular graph + + This graph is obtained as the union of 4 orbits of sets of cardinality 2 + (among the 13 that exists) of the group `Sz(8)`. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_560_208_72_80 + sage: g = SRG_560_208_72_80() # optional - database_gap # not tested (~2s) + sage: g.is_strongly_regular(parameters=True) # optional - database_gap # not tested (~2s) + (560, 208, 72, 80) + """ + from sage.libs.gap.libgap import libgap + libgap.LoadPackage("AtlasRep") + g=libgap.AtlasGroup("Sz8",libgap.NrMovedPoints,560) + + h = Graph() + h.add_edges(g.Orbit([1,2],libgap.OnSets)) + h.add_edges(g.Orbit([1,4],libgap.OnSets)) + h.add_edges(g.Orbit([1,8],libgap.OnSets)) + h.add_edges(g.Orbit([1,27],libgap.OnSets)) + h.relabel() + return h + +def strongly_regular_from_two_intersection_set(M): + r""" + Return a strongly regular graph from a 2-intersection set. + + A set of points in the projective geometry `PG(k,q)` is said to be a + 2-intersection set if it intersects every hyperplane in either `h_1` or + `h_2` points, where `h_1,h_2\in \\NN`. + + From a 2-intersection set `S` can be defined a strongly-regular graph in the + following way: + + - Place the points of `S` on a hyperplane `H` in `PG(k+1,q)` + + - Define the graph `G` on all points of `PG(k+1,q)\backslash H` + + - Make two points of `V(G)=PG(k+1,q)\backslash H` adjacent if the line going + through them intersects `S` + + For more information, see e.g. [CDB13]_ where this explanation has been + taken from. + + INPUT: + + - `M` -- a `|S| \times k` matrix with entries in `F_q` representing the points of + the 2-intersection set. We assume that the first non-zero entry of each row is + equal to `1`, that is, they give points in homogeneous coordinates. + + The implementation does not check that `S` is actually a 2-intersection set. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import strongly_regular_from_two_intersection_set + sage: S=Matrix([(0,0,1),(0,1,0)] + map(lambda x: (1,x^2,x), GF(4,'b'))) + sage: g=strongly_regular_from_two_intersection_set(S) + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + REFERENCES: + + .. [CDB13] I. Cardinali and B. De Bruyn, + Spin-embeddings, two-intersection sets and two-weight codes, + Ars Comb. 109 (2013): 309-319. + https://biblio.ugent.be/publication/4241842/file/4241845.pdf + """ + from itertools import product, izip + K = M.base_ring() + k = M.ncols() + g = Graph() + + M = [list(p) for p in M] + + # For every point in F_q^{k+1} not on the hyperplane of M + for u in [tuple(x) for x in product(K,repeat=k)]: + # For every v point of M + for v in M: + # u is adjacent with all vertices on a uv line. + g.add_edges([[u,tuple([u[i]+qq*v[i] for i in range(k)])] \ + for qq in K if not qq==K.zero()]) + g.relabel() + return g + + +def SRG_729_336_153_156(): + r""" + Return a `(729, 336, 153, 156)`-strongly regular graph. + + This graph is built from a 2-intersection code shared by L. Disset in his + thesis [Disset00]_ and available at + http://www.mat.uc.cl/~ldissett/newgraphs/. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_336_153_156 + sage: G = SRG_729_336_153_156() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 336, 153, 156) + + REFERENCES: + + .. [Disset00] L. Dissett, + Combinatorial and computational aspects of finite geometries, + 2000, + https://tspace.library.utoronto.ca/bitstream/1807/14575/1/NQ49844.pdf + """ + L = [ + "101212212122202012010102120101112012121001201012120220122112001121201201201201010020012201001201201201202120121122012021201221021110200212121011211002012220000122201201", + "011100122001200111220011220020011222001200022000220012220122011220011101122012012001222010122200012011120112220112000120120012002012201122001220012122000201212001211211", + "000011111000011111112000001112000000111122222000001111112222000001111122222000111222222001111122222000001111112222000001112222000111122222000001111222000011122000011122", + "000000000111111111111000000000111111111111111222222222222222000000000000000111111111111222222222222000000000000000111111111111222222222222000000000000111111111222222222", + "000000000000000000000111111111111111111111111111111111111111000000000000000000000000000000000000000111111111111111111111111111111111111111222222222222222222222222222222", + "000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + ] + + L = Matrix(GF(3),map(list,L)).transpose() + return strongly_regular_from_two_intersection_set(L) + +def SRG_729_448_277_272(): + r""" + Return a `(729, 448, 277, 272)`-strongly regular graph. + + This graph is built from a ternary `[140, 6]` code with weights `90, 99`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_448_277_272 + sage: G = SRG_729_448_277_272() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 448, 277, 272) + """ + x = ("10111011111111101110101110111100111011111011101111101001001111011011011111100100111101000111101111101100011011001111101110111110101111111001", + "01220121111211011101011101112101220022120121011222010110011010120110112112001101021010101111012211011000020110012221212101011101211122020011", + "22102021112110111120211021122012100012202220112110101200110101202102122120011110020201211110021210110000101200121222122010211022211210110101", + "11010221121101111102210221220221000111011101121102012101101012012022222000211200202012211100111201200001122001211011120102110212212102121001", + "20201121211111111012202022201210001220122121211010121011010020110121220201212002010222011001111012100011010212110021202021102112221012110011", + "02022222111111110112020112011200022102212222110102210110100101102211201211220020002120110011110221100110002121100222120211021112010112220101") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_729_532_391_380(): + r""" + Return a `(729, 532, 391, 380)`-strongly regular graph. + + This graph is built from a projective ternary `[98,6]` code with weights + `63, 72`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_532_391_380 + sage: G = SRG_729_532_391_380() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 532, 391, 380) + """ + x=("10000021022112121121110122000110112002010011100120022110120200120111220220122120012012100201110210", + "01000020121020200200211101202121120002211002210100021021202220112122012212101102010210010221221201", + "00100021001211011111111202120022221002201111021101021212210122101020121111002000210000101222202000", + "00010022122200222202201212211112001102200112202202121201211212010210202001222120000002110021000110", + "00001021201002010011020210221221012112200012020011201200111021021102212120211102012002011201210221", + "00000120112212122122202110022202210010200022002120112200101002202221111102110100210212001022201202") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_729_560_433_420(): + r""" + Return a `(729, 560, 433, 420)`-strongly regular graph. + + This graph is built from a projective ternary `[84,6]` code with weights + `54, 63`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_560_433_420 + sage: G = SRG_729_560_433_420() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 560, 433, 420) + """ + x=("100000210221121211211212100002020022102220010202100220112211111022012202220001210020", + "010000201210202002002200010022222022012112111222010212120102222221210102112001001022", + "001000210012110111111202101021212221000101021021021211021221000111100202101200010122", + "000100221222002222022202010121111210202200012001222011212000211200122202100120211002", + "000010212010020100110002001011101112122110211102212121200111102212021122100010201120", + "000001201122121221222212000110100102011101201012001102201222221110211011100001200102") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_729_616_523_506(): + r""" + Return a `(729, 616, 523, 506)`-strongly regular graph. + + This graph is built from a projective ternary `[56,6]` code with weights + `36, 45`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_616_523_506 + sage: G = SRG_729_616_523_506() # not tested (3s) + sage: G.is_strongly_regular(parameters=True) # not tested (3s) + (729, 616, 523, 506) + """ + x=("10000021022112022210202200202122221120200112100200111102", + "01000020121020221101202120220001110202220110010222122212", + "00100021001211211020022112222122002210122100101222020020", + "00010022122200010012221111121001121211212002110020010101", + "00001021201002220211121011010222000111021002011201112112", + "00000120112212111201011001002111121101002212001022222010") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_625_364_213_210(): + r""" + Return a `(625, 364, 213, 210)`-strongly regular graph. + + This graph is built from a projective 5-ary `[88,5]` code with weights `64, + 72`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_625_364_213_210 + sage: G = SRG_625_364_213_210() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (625, 364, 213, 210) + """ + x=("10004323434444234221223441130101034431234004441141003110400203240", + "01003023101220331314013121123212111200011403221341101031340421204", + "00104120244011212302124203142422240001230144213220111213034240310", + "00012321211123213343321143204040211243210011144140014401003023101") + M = Matrix(GF(5),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_625_416_279_272(): + r""" + Return a `(625, 416, 279, 272)`-strongly regular graph. + + This graph is built from a projective 5-ary `[52,4]` code with weights `40, + 45`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_625_416_279_272 + sage: G = SRG_625_416_279_272() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (625, 416, 279, 272) + """ + x=("1000432343444423422122344123113041011022221414310431", + "0100302310122033131401312133032331123141114414001300", + "0010412024401121230212420301411224123332332300210011", + "0001232121112321334332114324420140440343341412401244") + M = Matrix(GF(5),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_625_468_353_342(): + r""" + Return a `(625, 468, 353, 342)`-strongly regular graph. + + This graph is built from a two-weight code presented in [BJ03]_ (cf. Theorem + 4.1). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_625_468_353_342 + sage: G = SRG_625_468_353_342() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (625, 468, 353, 342) + + REFERENCE: + + .. [BJ03] I. Bouyukliev and S. Juriaan, + Some new results on optimal codes over `F_5`, + Designs, Codes and Cryptography 30, no. 1 (2003): 97-111, + http://www.moi.math.bas.bg/moiuser/~iliya/pdf_site/gf5srev.pdf + """ + x = ("111111111111111111111111111111000000000", + "111111222222333333444444000000111111000", + "223300133440112240112240133440123400110", + "402340414201142301132013234230044330401") + M = Matrix(GF(5),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_243_220_199_200(): + r""" + Return a `(243, 220, 199, 200)`-strongly regular graph. + + This graph is built from a projective ternary `[55,5]` code with weights + `36, 45`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_243_220_199_200 + sage: G = SRG_243_220_199_200() + sage: G.is_strongly_regular(parameters=True) + (243, 220, 199, 200) + """ + x=("1000010122200120121002211022111101011212112022022020002", + "0100011101120102100102202121022211112000020211221222002", + "0010021021222220122011212220021121100021220002100102201", + "0001012221012012100200102211110211121211201002202000222", + "0000101222101201210020110221111020112121120120220200022") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_729_476_313_306(): + r""" + Return a `(729, 476, 313, 306)`-strongly regular graph. + + This graph is built from a projective ternary `[126,6]` code with weights + `81, 90`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_476_313_306 + sage: G = SRG_729_476_313_306() # not tested (5s) + sage: G.is_strongly_regular(parameters=True) # not tested (5s) + (729, 476, 313, 306) + """ + x=("100000210221121211211101220021210000100011020200201101121021122102020111100122122221120200110001010222000021110110011211110210", + "010000201210202002002111012020001001110012222220221211200120201212222102210100001110202220121001110211200120221121012001221201", + "001000210012110111111112021220210102211012212122200222212000112220212011021102122002210122122101120210120100102212112112202000", + "000100221222002222022012122120201012021112211112111120010221100121011012202201001121211212002211120210012201120021222121000110", + "000010212010020100110202102200200102002122111011112210121010202111121212020010222000111021000222122210001011222102100121210221", + "000001201122121221222021100221200012000220101001022022100122112010102222002122111121101002200020221110000122202000221222201202") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_729_420_243_240(): + r""" + Return a `(729, 420, 243, 240)`-strongly regular graph. + + This graph is built from a projective ternary `[154,6]` code with weights + `99, 108`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_420_243_240 + sage: G = SRG_729_420_243_240() # not tested (5s) + sage: G.is_strongly_regular(parameters=True) # not tested (5s) + (729, 420, 243, 240) + """ + x=("10000021022112121121110122002121000010001102020020110112102112202221021020201"+ + "20202212102220222022222110122210022201211222111110211101121002011102101111002", + "01000020121020200200211101202000100111001222222022121120012020122110122122221"+ + "02222102012112111221111021101101021121002001022221202211100102212212010222102", + "00100021001211011111111202122021010221101221212220022221200011221102002202120"+ + "20121121000101000111020212200020121210011112210001001022001012222020000100212", + "00010022122200222202201212212020101202111221111211112001022110001001221210110"+ + "12211020202200222000021101010212001022212020002112011200021100210001100121020", + "00001021201002010011020210220020010200212211101111221012101020222021111111212"+ + "11120012122110211222201220220201222200102101111020112221020112012102211120101", + "00000120112212122122202110022120001200022010100102202210012211211120100101022"+ + "01011212011101110111112202111200111021221112222211222020120010222012022220012") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_1024_825_668_650(): + r""" + Return a `(1024, 825, 668, 650)`-strongly regular graph. + + This graph is built from a projective binary `[198,10]` code with weights + `96, 112`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_1024_825_668_650 + sage: G = SRG_1024_825_668_650() # not tested (13s) + sage: G.is_strongly_regular(parameters=True) # not tested (13s) + (1024, 825, 668, 650) + """ + x=("1000000000111111101010000100011001111101101010010010011110001101111001101100111000111101010101110011"+ + "11110010100111101001001100111101011110111100101101110100111100011111011011100111110010100110110000", + "0100000000010110011100100010101010101111011010001001010110011010101011011101000110000001101101010110"+ + "10110111110101000000011011001100010111110001001011011100111100100000110001011001110110011101011000", + "0010000000011100111110111011000011010100100011110000001100011011101111001010001100110110000001111000"+ + "11000000101011010111110101000111110010011011101110000010110100000011100010011111100100111101010010", + "0001000000001111100010000000100101010001110111100010010010010111000100101100010001001110111101110100"+ + "10010101101100110011010011101100110100100011011101100000110011110011111000000010110101011111101111", + "0000100000110010010000010110000111010011010101000010110100101010011011000011001100001110011011110001"+ + "11101000010000111101101100111100001011010010111011100101101001111000100011000010110111111111011100", + "0000010000110100111001111011010000101110001011100010010010010111100101011001011011100110101110100001"+ + "01101010110010100011000101111100100001110111001001001001001100001101110110000110101010011010101101", + "0000001000011011110010110100010010001100000011001000011101000110001101001000110110010101011011001111"+ + "01111111010011111010100110011001110001001000001110000110111011010000011101001110111001011011001011", + "0000000100111001101011110010111100100001010100100110001100100110010101111001100101101001000101011000"+ + "10001001111101011101001001010111010011011101010011010000101010011001010110011110010000011011111001", + "0000000010101011010101010101011100111101111110100011011001001010111101100111010110100101100110101100"+ + "00000001100011110110010101100001000000010100001101111011111000110001100101101010000001110101011100", + "0000000001101100111101011000010000000011010100000110101010011010100111100001000011010011011101110111"+ + "01110111011110101100100100110110011100001001000001010011010010010111110011101011101001101101011010") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_512_73_12_10(): + r""" + Return a `(512, 73, 12, 10)`-strongly regular graph. + + This graph is built from a projective binary `[219,9]` code with weights + `96, 112`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_512_73_12_10 + sage: G = SRG_512_73_12_10() # not tested (3s) + sage: G.is_strongly_regular(parameters=True) # not tested (3s) + (512, 73, 12, 10) + """ + x=("10100011001110100010100010010000100100001011110010001010011000000001101011110011001001000010011110111011111"+ + "0001001010110110110111001100111100011011101000000110101110001010100011110011111111110111010100101011000101111111", + "01100010110101110100001000010110001010010010011000111101111001011101000011101011100111111110001100000111010"+ + "1101001000110001111011001100101011110101011110010001101011110000100000101101100010110100001111001100110011001111", + "00010010001001011011001110011101111110000000101110101000110110011001110101011011101011011011000010010011111"+ + "1110110100111111000000110011101101000000001010000000011000111111100101100001110011110001110011110110100111100001", + "00001000100010101110101110011100010101110011010110000001111111100111010000101110001010100100000001011010111"+ + "1001001000000011000011001100100100111010000000001010111001001100100101011110001100110001000000111001100100100111", + "00000101010100010101101110011101001000101110000000000111101100011000000001110100000001011010101001111110110"+ + "0010110111100111000000110011110110101101110000001111100001010001100101100001110011110001101101000000000000100001", + "00000000000000000000010000011101011100100010000110110100101011001011001100000001011000101010100111000111101"+ + "0011100011011011011111100010011100010111101001011001001101100010011010001011010110001110100001001111110010100100", + "00000000000000000000000001011010110110101111010110101001001001000101010000000000001011000011000010100100110"+ + "0000110000111101100010000111111111101101001010110000111111101110101011010010010001011101110011111001100100101110", + "00000000000000000000000000110111101011110010101110000110010010100010001010000000010100011000101000010011000"+ + "0110000111100110100001001011111111111010110000001010111111110011110110001100100010101011101101110110011000110110", + "00000000000000000000000000000000000000000000000001111111111111111111111110000001111111111111111111111111111"+ + "1111111100000000000011111111111111000000111111111111111111000000000000111111111111000000000000000000111111000110") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_512_219_106_84(): + r""" + Return a `(512, 219, 106, 84)`-strongly regular graph. + + This graph is built from a projective binary `[73,9]` code with weights `32, + 40`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_512_219_106_84 + sage: G = SRG_512_219_106_84() + sage: G.is_strongly_regular(parameters=True) + (512, 219, 106, 84) + """ + x=("1010010100000010100000101010001100110101101101000010110010100100111011101", + "0110000110000101101111001101000100111111101011011101110010110001100111100", + "0001010000000001111111011010100101001111011010101100001010000001110100001", + "0000100100000001111111100111000011110011110101000001010110000001011010001", + "0000001010000001111110111100011000111100101110010010101100000001101001001", + "0000000001000111001010110010011001101001011010110110011001010111100010010", + "0000000000100100011000100100111100001100101111010001011011111000110011110", + "0000000000010111001100101011111110101010000000000100111110000001111111100", + "0000000000001011100001000011011010110001110101101100001100101110101110110") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_512_315_202_180(): + r""" + Return a `(512, 315, 202, 180)`-strongly regular graph. + + This graph is built from a projective binary code with weights `32, 40`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly + regular graph from a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_512_315_202_180 + sage: G = SRG_512_315_202_180() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (512, 315, 202, 180) + + REFERENCE: + + .. [Kohnert07] A. Kohnert, + Constructing two-weight codes with prescribed groups of automorphisms, + Discrete applied mathematics 155, no. 11 (2007): 1451-1457. + http://linearcodes.uni-bayreuth.de/twoweight/ + """ + x=("0100011110111000001011010110110111100010001000001000001001010110101101", + "1000111101110000000110101111101111000100010000010000000010101111001011", + "0001111011100000011101011101011110011000100000100000000101011100010111", + "0011110101000000111010111010111100110001000011000000001010111000101101", + "0111101000000001110101110111111001100010000100000000010101110011001011", + "1111010000000011101011101101110011010100001000000000101011100100010111", + "1110100010000111000111011011100110111000010000000001000111001010101101", + "1101000110001110001110110101001101110000100010000010001110010101001011", + "1010001110011100001101101010011011110001000100000100001100101010010111") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_256_153_92_90(): + r""" + Return a `(256, 153, 92, 90)`-strongly regular graph. + + This graph is built from a projective 4-ary `[34,4]` code with weights `24, + 28`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_256_153_92_90 + sage: G = SRG_256_153_92_90() + sage: G.is_strongly_regular(parameters=True) + (256, 153, 92, 90) + """ + K = GF(4,conway=True, prefix='x') + F = K.gens()[0] + J = F*F + x = [[1,0,0,0,1,F,F,J,1,0,F,F,0,1,J,F,F,J,J,J,F,F,J,J,J,1,J,F,1,0,1,F,J,1], + [0,1,0,0,F,F,1,J,1,1,J,1,F,F,0,0,1,0,F,F,0,1,J,F,F,1,0,0,0,1,F,F,J,1], + [0,0,1,0,1,0,0,F,F,1,J,1,1,J,1,F,F,F,J,1,0,F,F,0,1,J,F,F,1,0,0,0,1,F], + [0,0,0,1,F,F,J,1,0,F,F,0,1,J,F,F,1,J,J,F,F,J,J,J,1,J,F,1,0,1,F,J,1,J]] + M = Matrix(K,[map(K,l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_256_170_114_110(): + r""" + Return a `(256, 170, 114, 110)`-strongly regular graph. + + This graph is built from a projective binary `[85,8]` code with weights `40, + 48`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_256_170_114_110 + sage: G = SRG_256_170_114_110() + sage: G.is_strongly_regular(parameters=True) + (256, 170, 114, 110) + """ + x=("1000000010011101010001000011100111000111111010110001101101000110010011001101011100001", + "0100000011010011111001100010010100100100000111101001011011100101011010101011110010001", + "0010000011110100101101110010101101010101111001000101000000110100111110011000100101001", + "0001000011100111000111111010110001101101000110010011001101011100001100000001001110101", + "0000100011101110110010111110111111110001011001111000001011101000010101001101111011011", + "0000010011101010001000011100111000111111010110001101101000110010011001101011100001100", + "0000001001110101000100001110011100011111101011000110110100011001001100110101110000110", + "0000000100111010100010000111001110001111110101100011011010001100100110011010111000011") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_120_63_30_36(): + r""" + Return a `(120,63,30,36)`-strongly regular graph + + It is the distance-2 graph of :meth:`JohnsonGraph(10,3) + `. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_120_63_30_36 + sage: G = SRG_120_63_30_36() + sage: G.is_strongly_regular(parameters=True) + (120, 63, 30, 36) + """ + from sage.graphs.generators.families import JohnsonGraph + return JohnsonGraph(10,3).distance_graph([2]) + +def SRG_126_25_8_4(): + r""" + Return a `(126,25,8,4)`-strongly regular graph + + It is the distance-(1 or 4) graph of :meth:`JohnsonGraph(9,4) + `. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_126_25_8_4 + sage: G = SRG_126_25_8_4() + sage: G.is_strongly_regular(parameters=True) + (126, 25, 8, 4) + """ + from sage.graphs.generators.families import JohnsonGraph + return JohnsonGraph(9,4).distance_graph([1,4]) + +def SRG_175_72_20_36(): + r""" + Return a `(175,72,20,36)`-strongly regular graph + + This graph is obtained from the line graph of + :meth:`~sage.graphs.graph_generators.GraphGenerators.HoffmanSingletonGraph`. Setting + two vertices to be adjacent if their distance in the line graph is exactly + two yields the strongly regular graph. For more information, see + http://www.win.tue.nl/~aeb/graphs/McL.html. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_175_72_20_36 + sage: G = SRG_175_72_20_36() + sage: G.is_strongly_regular(parameters=True) + (175, 72, 20, 36) + """ + return HoffmanSingletonGraph().line_graph().distance_graph([2]) + +def SRG_126_50_13_24(): + r""" + Return a `(126,50,13,24)`-strongly regular graph + + This graph is a subgraph of + :meth:`~sage.graphs.strongly_regular_db.SRG_175_72_20_36`. + This construction, due to Goethals, is given in §10B.(vii) of [BvL84]_. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import SRG_126_50_13_24 + sage: G = SRG_126_50_13_24() + sage: G.is_strongly_regular(parameters=True) + (126, 50, 13, 24) + """ + from sage.graphs.generators.smallgraphs import HoffmanSingletonGraph + from sage.graphs.strongly_regular_db import SRG_175_72_20_36 + hs = HoffmanSingletonGraph() + s = set(hs.vertices()).difference(hs.neighbors(0)+[0]) + return SRG_175_72_20_36().subgraph(hs.edge_boundary(s,s)) + +def SRG_81_50_31_30(): + r""" + Return a `(81, 50, 31, 30)`-strongly regular graph. + + This graph is built from a projective ternary `[15,4]` code with weights `9, + 12`, obtained from Eric Chen's `database of two-weight codes + `__. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_81_50_31_30 + sage: G = SRG_81_50_31_30() + sage: G.is_strongly_regular(parameters=True) + (81, 50, 31, 30) + """ + x=("100022021001111", + "010011211122000", + "001021112100011", + "000110120222220") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + +def SRG_1288_792_476_504(): + r""" + Return a `(1288, 792, 476, 504)`-strongly regular graph. + + This graph is built on the words of weight 12 in the + :func:`~sage.coding.code_constructions.BinaryGolayCode`. Two of them are + then made adjacent if their symmetric difference has weight 12 (cf + [BvE92]_). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_1288_792_476_504 + sage: G = SRG_1288_792_476_504() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (1288, 792, 476, 504) + + REFERENCE: + + .. [BvE92] A. Brouwer and C. Van Eijl, + On the p-Rank of the Adjacency Matrices of Strongly Regular Graphs + Journal of Algebraic Combinatorics (1992), vol.1, n.4, pp329-346, + http://dx.doi.org/10.1023/A%3A1022438616684 + """ + from sage.coding.code_constructions import BinaryGolayCode + C = BinaryGolayCode() + C = [[i for i,v in enumerate(c) if v] + for c in C] + C = [s for s in C if len(s) == 12] + G = Graph([map(frozenset,C), + lambda x,y:len(x.symmetric_difference(y))==12]) + G.relabel() + return G + + +cdef bint seems_feasible(int v, int k, int l, int mu): + r""" + Tests is the set of parameters seems feasible + + INPUT: + + - ``v,k,l,mu`` (integers) + """ + cdef int r,s,f,g + cdef uint_fast32_t tmp[2] + + if (v<0 or k<=0 or l<0 or mu<0 or + k>=v-1 or l>=k or mu>=k or + v-2*k+mu-2 < 0 or # lambda of complement graph >=0 + v-2*k+l < 0 or # μ of complement graph >=0 + mu*(v-k-1) != k*(k-l-1)): + return False + + # Conference graphs. Only possible if 'v' is a sum of two squares (3.A of + # [BvL84] + if (v-1)*(mu-l)-2*k == 0: + return two_squares_c(v,tmp) + + rr,ss = eigenvalues(v,k,l,mu) + if rr is None: + return False + r,s = rr,ss + + # 1.3.1 of [Distance-regular graphs] + # "Integrality condition" + if ((s+1)*(k-s)*k) % (mu*(s-r)): + return False + + # Theorem 21.3 of [WilsonACourse] or + # 3.B of [BvL84] + # (Krein conditions) + if ((r+1)*(k+r+2*r*s) > (k+r)*(s+1)**2 or + (s+1)*(k+s+2*r*s) > (k+s)*(r+1)**2): + return False + + # multiplicity of eigenvalues 'r,s' (f=lambda_r, g=lambda_s) + # + # They are integers (checked by the 'integrality condition'). + f = -k*(s+1)*(k-s)/((k+r*s)*(r-s)) + g = k*(r+1)*(k-r)/((k+r*s)*(r-s)) + + # 3.C of [BvL84] + # (Absolute bound) + if (2*v > f*(f+3) or + 2*v > g*(g+3)): + return False + + # 3.D of [BvL84] + # (Claw bound) + if (mu != s**2 and + mu != s*(s+1) and + 2*(r+1) > s*(s+1)*(mu+1)): + return False + + # 3.E of [BvL84] + # (the Case μ=1) + if mu == 1: + if ( k % (l+1) or + (v*k) % ((l+1)*(l+2))): + return False + + # 3.F of [BvL84] + # (the Case μ=2) + if mu == 2 and 2*k < l*(l+3) and k%(l+1): + return False + + return True + +def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint check=True): + r""" + Return a `(v,k,\lambda,\mu)`-strongly regular graph. + + This function relies partly on Andries Brouwer's `database of strongly + regular graphs `__. See + the documentation of :mod:`sage.graphs.strongly_regular_db` for more + information. + + INPUT: + + - ``v,k,l,mu`` (integers) -- note that ``mu``, if unspecified, is + automatically determined from ``v,k,l``. + + - ``existence`` (boolean;``False``) -- instead of building the graph, + return: + + - ``True`` -- meaning that a `(v,k,\lambda,\mu)`-strongly regular graph + exists. + + - ``Unknown`` -- meaning that Sage does not know if such a strongly + regular graph exists (see :mod:`sage.misc.unknown`). + + - ``False`` -- meaning that no such strongly regular graph exists. + + - ``check`` -- (boolean) Whether to check that output is correct before + returning it. As this is expected to be useless (but we are cautious + guys), you may want to disable it whenever you want speed. Set to + ``True`` by default. + + EXAMPLES: + + Petersen's graph from its set of parameters:: + + sage: graphs.strongly_regular_graph(10,3,0,1,existence=True) + True + sage: graphs.strongly_regular_graph(10,3,0,1) + complement(Johnson graph with parameters 5,2): Graph on 10 vertices + + Now without specifying `\mu`:: + + sage: graphs.strongly_regular_graph(10,3,0) + complement(Johnson graph with parameters 5,2): Graph on 10 vertices + + An obviously infeasible set of parameters:: + + sage: graphs.strongly_regular_graph(5,5,5,5,existence=True) + False + sage: graphs.strongly_regular_graph(5,5,5,5) + Traceback (most recent call last): + ... + ValueError: There exists no (5, 5, 5, 5)-strongly regular graph + + An set of parameters proved in a paper to be infeasible:: + + sage: graphs.strongly_regular_graph(324,57,0,12,existence=True) + False + sage: graphs.strongly_regular_graph(324,57,0,12) + Traceback (most recent call last): + ... + EmptySetError: Andries Brouwer's database reports that no (324, 57, 0, + 12)-strongly regular graph exists. Comments: Gavrilyuk & Makhnev and Kaski & stergrd + + A set of parameters unknown to be realizable in Andries Brouwer's database:: + + sage: graphs.strongly_regular_graph(324,95,22,30,existence=True) + Unknown + sage: graphs.strongly_regular_graph(324,95,22,30) + Traceback (most recent call last): + ... + RuntimeError: Andries Brouwer's database reports that no + (324,95,22,30)-strongly regular graph is known to exist. + Comments: + + A large unknown set of parameters (not in Andries Brouwer's database):: + + sage: graphs.strongly_regular_graph(1394,175,0,25,existence=True) + Unknown + sage: graphs.strongly_regular_graph(1394,175,0,25) + Traceback (most recent call last): + ... + RuntimeError: Sage cannot figure out if a (1394,175,0,25)-strongly regular graph exists. + + Test the Claw bound (see 3.D of [BvL84]_):: + + sage: graphs.strongly_regular_graph(2058,242,91,20,existence=True) + False + """ + load_brouwer_database() + if mu == -1: + mu = k*(k-l-1)//(v-k-1) + + params = (v,k,l,mu) + params_complement = (v,v-k-1,v-2*k+mu-2,v-2*k+l) + + if not seems_feasible(v,k,l,mu): + if existence: + return False + raise ValueError("There exists no "+str(params)+"-strongly regular graph") + + def check_srg(G): + if check and (v,k,l,mu) != G.is_strongly_regular(parameters=True): + raise RuntimeError("Sage built an incorrect {}-SRG.".format((v,k,l,mu))) + return G + + constructions = { + ( 27, 16, 10, 8): [SchlaefliGraph], + ( 36, 14, 4, 6): [Graph,('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ'+ + 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygSGkZ`OyHETdK[?lWStCapVgKK')], + ( 50, 7, 0, 1): [HoffmanSingletonGraph], + ( 56, 10, 0, 2): [SimsGewirtzGraph], + ( 77, 16, 0, 4): [M22Graph], + ( 81, 50, 31, 30): [SRG_81_50_31_30], + (100, 22, 0, 6): [HigmanSimsGraph], + (100, 44, 18, 20): [SRG_100_44_18_20], + (100, 45, 20, 20): [SRG_100_45_20_20], + (105, 32, 4, 12): [SRG_105_32_4_12], + (120, 63, 30, 36): [SRG_120_63_30_36], + (120, 77, 52, 44): [SRG_120_77_52_44], + (126, 25, 8, 4): [SRG_126_25_8_4], + (126, 50, 13, 24): [SRG_126_50_13_24], + (144, 39, 6, 12): [SRG_144_39_6_12], + (162, 56, 10, 24): [LocalMcLaughlinGraph], + (175, 72, 20, 36): [SRG_175_72_20_36], + (176, 49, 12, 14): [SRG_176_49_12_14], + (176, 105, 68, 54): [SRG_176_105_68_54], + (196, 91, 42, 42): [SRG_196_91_42_42], + (210, 99, 48, 45): [SRG_210_99_48_45], + (220, 84, 38, 28): [SRG_220_84_38_28], + (231, 30, 9, 3): [CameronGraph], + (243, 110, 37, 60): [SRG_243_110_37_60], + (243, 220, 199,200): [SRG_243_220_199_200], + (253, 140, 87, 65): [SRG_253_140_87_65], + (256, 170, 114,110): [SRG_256_170_114_110], + (256, 187, 138,132): [SRG_256_187_138_132], + (256, 153, 92, 90): [SRG_256_153_92_90], + (275, 112, 30, 56): [McLaughlinGraph], + (276, 140, 58, 84): [SRG_276_140_58_84], + (280, 117, 44, 52): [SRG_280_117_44_52], + (280, 135, 70, 60): [SRG_280_135_70_60], + (416, 100, 36, 20): [SRG_416_100_36_20], + (512, 219, 106, 84): [SRG_512_219_106_84], + (512, 73, 12, 10): [SRG_512_73_12_10], + (512, 315, 202,180): [SRG_512_315_202_180], + (560, 208, 72, 80): [SRG_560_208_72_80], + (625, 364, 213,210): [SRG_625_364_213_210], + (625, 416, 279,272): [SRG_625_416_279_272], + (625, 468, 353,342): [SRG_625_468_353_342], + (729, 336, 153,156): [SRG_729_336_153_156], + (729, 616, 523,506): [SRG_729_616_523_506], + (729, 420, 243,240): [SRG_729_420_243_240], + (729, 448, 277,272): [SRG_729_448_277_272], + (729, 560, 433,420): [SRG_729_560_433_420], + (729, 476, 313,306): [SRG_729_476_313_306], + (729, 532, 391,380): [SRG_729_532_391_380], + (1024,825, 668,650): [SRG_1024_825_668_650], + (1288,792, 476,504): [SRG_1288_792_476_504], + (1782,416, 100, 96): [SuzukiGraph], + } + + if params in constructions: + val = constructions[params] + return True if existence else check_srg(val[0](*val[1:])) + if params_complement in constructions: + val = constructions[params_complement] + return True if existence else check_srg(val[0](*val[1:]).complement()) + + test_functions = [is_paley, is_johnson, + is_orthogonal_array_block_graph, + is_steiner, is_affine_polar, + is_goethals_seidel, + is_orthogonal_polar, + is_NOodd, is_NOperp_F5, is_NO_F2, is_NO_F3, is_NU, + is_unitary_polar, is_unitary_dual_polar, is_GQqmqp, + is_RSHCD, + is_twograph_descendant_of_srg, + is_taylor_twograph_srg, + is_switch_OA_srg, + is_polhill] + + # Going through all test functions, for the set of parameters and its + # complement. + for f in test_functions: + if f(*params): + if existence: + return True + ans = f(*params) + return check_srg(ans[0](*ans[1:])) + if f(*params_complement): + if existence: + return True + ans = f(*params_complement) + return check_srg(ans[0](*ans[1:]).complement()) + + # From now on, we have no idea how to build the graph. + # + # We try to return the most appropriate error message. + + global _brouwer_database + brouwer_data = _brouwer_database.get(params,None) + + if brouwer_data is not None: + if brouwer_data['status'] == 'impossible': + if existence: + return False + raise EmptySetError("Andries Brouwer's database reports that no "+ + str((v,k,l,mu))+"-strongly regular graph exists. "+ + "Comments: "+brouwer_data['comments'].encode('ascii','ignore')) + + if brouwer_data['status'] == 'open': + if existence: + return Unknown + raise RuntimeError(("Andries Brouwer's database reports that no "+ + "({},{},{},{})-strongly regular graph is known "+ + "to exist.\nComments: ").format(v,k,l,mu) + +brouwer_data['comments'].encode('ascii','ignore')) + + if brouwer_data['status'] == 'exists': + if existence: + return True + raise RuntimeError(("Andries Brouwer's database claims that such a "+ + "({},{},{},{})-strongly regular graph exists, but "+ + "Sage does not know how to build it. If *you* do, "+ + "please get in touch with us on sage-devel!\n"+ + "Comments: ").format(v,k,l,mu) + +brouwer_data['comments'].encode('ascii','ignore')) + if existence: + return Unknown + raise RuntimeError(("Sage cannot figure out if a ({},{},{},{})-strongly "+ + "regular graph exists.").format(v,k,l,mu)) + +def apparently_feasible_parameters(int n): + r""" + Return a list of parameters `(v,k,\lambda,\mu)` which are a-priori feasible. + + Note that some of those that it returns may also be infeasible for more + involved reasons. + + INPUT: + + - ``n`` (integer) -- return all a-priori feasible tuples `(v,k,\lambda,\mu)` + for `vMathon; 2-graph* + ... + In Andries Brouwer's database: + - 448 impossible entries + - 2950 undecided entries + - 1140 realizable entries (Sage misses ... of them) + + """ + global _brouwer_database + load_brouwer_database() + + # Check that all parameters detected as infeasible are actually infeasible + # in Brouwer's database, for a test that was implemented. + for params in set(_brouwer_database).difference(apparently_feasible_parameters(1301)): + if _brouwer_database[params]['status'] != "impossible": + raise RuntimeError("Brouwer's db does not seem to know that {} in unfeasible".format(params)) + comment = _brouwer_database[params]['comments'] + if ('Krein' in comment or + 'Absolute' in comment or + 'Conf' in comment or + 'mu=1' in comment or + 'μ=2' in comment): + continue + raise RuntimeError("We detected that {} was unfeasible, but maybe we should not have".format(params)) + + # We empty the global database, to be sure that strongly_regular_graph does + # not use its data to answer. + _brouwer_database, saved_database = {}, _brouwer_database + + cdef int missed = 0 + for params,dic in sorted(saved_database.items()): + sage_answer = strongly_regular_graph(*params,existence=True) + if dic['status'] == 'open': + if sage_answer: + print "Sage can build a {}, Brouwer's database cannot".format(params) + assert sage_answer is not False + elif dic['status'] == 'exists': + if sage_answer is not True: + print (("Sage cannot build a ({:<4} {:<4} {:<4} {:<4}) that exists. "+ + "Comment from Brouwer's database: ").format(*params) + +dic['comments'].encode('ascii','ignore')) + missed += 1 + assert sage_answer is not False + elif dic['status'] == 'impossible': + assert sage_answer is not True + else: + assert False # must not happen + + status = [x['status'] for x in saved_database.values()] + print "\nIn Andries Brouwer's database:" + print "- {} impossible entries".format(status.count('impossible')) + print "- {} undecided entries".format(status.count('open')) + print "- {} realizable entries (Sage misses {} of them)".format(status.count('exists'),missed) + + # Reassign its value to the global database + _brouwer_database = saved_database diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index c49325d699b..d1d61c93cc0 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -93,7 +93,6 @@ class AbelianGroupElement(AbelianGroupElementBase): sage: a*b in F True """ - def as_permutation(self): r""" Return the element of the permutation group G (isomorphic to the diff --git a/src/sage/groups/abelian_gps/element_base.py b/src/sage/groups/abelian_gps/element_base.py index ab2cff59587..1cf9c4a5a62 100644 --- a/src/sage/groups/abelian_gps/element_base.py +++ b/src/sage/groups/abelian_gps/element_base.py @@ -73,6 +73,16 @@ def __init__(self, parent, exponents): if len(self._exponents) != n: raise IndexError('argument length (= %s) must be %s.'%(len(exponents), n)) + def __hash__(self): + r""" + TESTS:: + + sage: F = AbelianGroup(3,[7,8,9]) + sage: hash(F.an_element()) # random + 1024 + """ + return hash(self.parent()) ^ hash(self._exponents) + def exponents(self): """ The exponents of the generators defining the group element. diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 51fa899443a..3bf8aef61e6 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -401,7 +401,7 @@ def __init__(self, cover, rels, gens): Additive abelian group isomorphic to Z/3 """ AdditiveAbelianGroup_class.__init__(self, cover, rels) - self._orig_gens = [self(x) for x in gens] + self._orig_gens = tuple(self(x) for x in gens) def gens(self): r""" @@ -416,7 +416,7 @@ def gens(self): sage: G.smith_form_gens() ((1, 2),) """ - return tuple(self._orig_gens) + return self._orig_gens def identity(self): r""" diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index e273b763e8e..61bf372f931 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -216,7 +216,7 @@ def _element_constructor_check(self, A, b): This is called from the group element constructor and can be overridden for subgroups of the affine group. It is guaranteed - that ``A``, ``b`` are in the correct matrix/vetor space. + that ``A``, ``b`` are in the correct matrix/vector space. INPUT: diff --git a/src/sage/groups/affine_gps/euclidean_group.py b/src/sage/groups/affine_gps/euclidean_group.py index 0b3387a1c1b..15f1ad8f442 100644 --- a/src/sage/groups/affine_gps/euclidean_group.py +++ b/src/sage/groups/affine_gps/euclidean_group.py @@ -167,7 +167,7 @@ def _element_constructor_check(self, A, b): This is called from the group element constructor and can be overridden for subgroups of the affine group. It is guaranteed - that ``A``, ``b`` are in the correct matrix/vetor space. + that ``A``, ``b`` are in the correct matrix/vector space. INPUT: diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index 0a965ca67bc..96143459ac0 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Braid groups @@ -46,6 +47,7 @@ - Miguel Angel Marco Buzunariz - Volker Braun +- Søren Fuglede Jørgensen - Robert Lipshitz - Thierry Monteil: add a ``__hash__`` method consistent with the word problem to ensure correct Cayley graph computations. @@ -64,6 +66,7 @@ import six from sage.rings.integer import Integer from sage.rings.integer_ring import IntegerRing +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.groups.free_group import FreeGroup, is_FreeGroup from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing @@ -145,10 +148,10 @@ def _latex_(self): """ latexrepr = '' for i in self.Tietze(): - if i>0: - latexrepr = latexrepr+"\sigma_{%s}"%i - if i<0: - latexrepr = latexrepr+"\sigma_{%s}^{-1}"%(-i) + if i > 0: + latexrepr = latexrepr+"\sigma_{%s}" % i + if i < 0: + latexrepr = latexrepr+"\sigma_{%s}^{-1}" % (-i) return latexrepr def strands(self): @@ -164,6 +167,51 @@ def strands(self): """ return self.parent().strands() + def exponent_sum(self): + """ + Return the exponent sum of the braid. + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: B = BraidGroup(5) + sage: b = B([1, 4, -3, 2]) + sage: b.exponent_sum() + 2 + sage: b = B([]) + sage: b.exponent_sum() + 0 + """ + return sum(s.sign() for s in self.Tietze()) + + def components_in_closure(self): + """ + Return the number of components of the trace closure of the braid. + + OUTPUT: + + Positive integer. + + EXAMPLES:: + + sage: B = BraidGroup(5) + sage: b = B([1, -3]) # Three disjoint unknots + sage: b.components_in_closure() + 3 + sage: b = B([1, 2, 3, 4]) # The unknot + sage: b.components_in_closure() + 1 + sage: B = BraidGroup(4) + sage: K11n42 = B([1, -2, 3, -2, 3, -2, -2, -1, 2, -3, -3, 2, 2]) + sage: K11n42.components_in_closure() + 1 + """ + cycles = self.permutation().to_cycles(singletons=False) + return self.strands() - sum(len(c)-1 for c in cycles) + def burau_matrix(self, var='t', reduced=False): """ Return the Burau matrix of the braid. @@ -526,7 +574,7 @@ def plot3d(self, color='rainbow'): def LKB_matrix(self, variables='x,y'): """ - Return the Lawence-Krammer-Bigelow representation matrix. + Return the Lawrence-Krammer-Bigelow representation matrix. The matrix is expressed in the basis $\{e_{i, j} \mid 1\\leq i < j \leq n\}$, where the indices are ordered @@ -541,7 +589,7 @@ def LKB_matrix(self, variables='x,y'): OUTPUT: - The matrix corresponding to the Lawence-Krammer-Bigelow representation of the braid. + The matrix corresponding to the Lawrence-Krammer-Bigelow representation of the braid. EXAMPLES:: @@ -559,10 +607,111 @@ def LKB_matrix(self, variables='x,y'): REFERENCES: - .. [Bigelow] Bigelow, Stephen J. The Lawrence-Krammer representation. arXiv:math/0204057v1 + .. [Bigelow] Bigelow, Stephen J. The Lawrence-Krammer representation. + :arxiv:`math/0204057v1` """ return self.parent()._LKB_matrix_(self.Tietze(), variab=variables) + def TL_matrix(self, drain_size, variab=None, sparse=True): + r""" + Return the matrix representation of the Temperley--Lieb--Jones + representation of the braid in a certain basis. + + The basis is given by non-intersecting pairings of `(n+d)` points, + where `n` is the number of strands, `d` is given by ``drain_size``, + and the pairings satisfy certain rules. See + :meth:`~sage.groups.braid.BraidGroup_class.TL_basis_with_drain()` + for details. + + We use the convention that the eigenvalues of the standard generators + are `1` and `-A^4`, where `A` is a variable of a Laurent + polynomial ring. + + When `d = n - 2` and the variables are picked appropriately, the + resulting representation is equivalent to the reduced Burau + representation. + + INPUT: + + - ``drain_size`` -- integer between 0 and the number of strands + (both inclusive) + + - ``variab`` -- variable (default: ``None``); the variable in the + entries of the matrices; if ``None``, then use a default variable + in `\ZZ[A,A^{-1}]` + + - ``sparse`` -- boolean (default: ``True``); whether or not the + result should be given as a sparse matrix + + OUTPUT: + + The matrix of the TL representation of the braid. + + The parameter ``sparse`` can be set to ``False`` if it is + expected that the resulting matrix will not be sparse. We + currently make no attempt at guessing this. + + EXAMPLES: + + Let us calculate a few examples for `B_4` with `d = 0`:: + + sage: B = BraidGroup(4) + sage: b = B([1, 2, -3]) + sage: b.TL_matrix(0) + [1 - A^4 -A^-2] + [ -A^6 0] + sage: R. = LaurentPolynomialRing(GF(2)) + sage: b.TL_matrix(0, variab=x) + [1 + x^4 x^-2] + [ x^6 0] + sage: b = B([]) + sage: b.TL_matrix(0) + [1 0] + [0 1] + + Test of one of the relations in `B_8`:: + + sage: B = BraidGroup(8) + sage: d = 0 + sage: B([4,5,4]).TL_matrix(d) == B([5,4,5]).TL_matrix(d) + True + + An element of the kernel of the Burau representation, following + [Big99]_:: + + sage: B = BraidGroup(6) + sage: psi1 = B([4, -5, -2, 1]) + sage: psi2 = B([-4, 5, 5, 2, -1, -1]) + sage: w1 = psi1^(-1) * B([3]) * psi1 + sage: w2 = psi2^(-1) * B([3]) * psi2 + sage: (w1 * w2 * w1^(-1) * w2^(-1)).TL_matrix(4) + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] + + REFERENCES: + + .. [Big99] Stephen J. Bigelow. The Burau representation is + not faithful for `n = 5`. Geom. Topol., 3:397–404, 1999. + .. [JonesNotes] Vaughan Jones. The Jones Polynomial. + https://math.berkeley.edu/~vfr/jones.pdf + """ + if variab is None: + R = LaurentPolynomialRing(IntegerRing(), 'A') + else: + R = variab.parent() + rep = self.parent().TL_representation(drain_size, variab) + M = identity_matrix(R, self.parent().dimension_of_TL_space(drain_size), + sparse=sparse) + for i in self.Tietze(): + if i > 0: + M = M*rep[i-1][0] + if i < 0: + M = M*rep[-i-1][1] + return M + def tropical_coordinates(self): r""" Return the tropical coordinates of ``self`` in the braid group `B_n`. @@ -613,6 +762,205 @@ def tropical_coordinates(self): T = TropicalSemiring(IntegerRing()) return [T(_) for _ in coord] + def markov_trace(self, variab=None, normalized=True): + """ + Return the Markov trace of the braid. + + The normalization is so that in the underlying braid group + representation, the eigenvalues of the standard generators of + the braid group are `1` and `-A^4`. + + INPUT: + + - ``variab`` -- variable (default: ``None``); the variable in the + resulting polynomial; if ``None``, then use the variable `A` + in `\ZZ[A,A^{-1}]` + + - ``normalized`` - boolean (default: ``True``); if specified to be + ``False``, return instead a rescaled Laurent polynomial version of + the Markov trace + + OUTPUT: + + If ``normalized`` is ``False``, return instead the Markov trace + of the braid, normalized by a factor of `(A^2+A^{-2})^n`. The + result is then a Laurent polynomial in ``variab``. Otherwise it + is a quotient of Laurent polynomials in ``variab``. + + EXAMPLES:: + + sage: B = BraidGroup(4) + sage: b = B([1, 2, -3]) + sage: mt = b.markov_trace(); mt + A^4/(A^12 + 3*A^8 + 3*A^4 + 1) + sage: mt.factor() + A^4 * (A^4 + 1)^-3 + + We now give the non-normalized Markov trace:: + + sage: mt = b.markov_trace(normalized=False); mt + A^-4 + 1 + sage: mt.parent() + Univariate Laurent Polynomial Ring in A over Integer Ring + """ + if variab is None: + R = LaurentPolynomialRing(IntegerRing(), 'A') + A = R.gens()[0] + one = IntegerRing().one() + quantum_integer = lambda d: R({i: one for i in range(-2*d, 2*d+1, 4)}) + else: + A = variab + quantum_integer = lambda d: (A**(2*(d+1))-A**(-2*(d+1))) // (A**2-A**(-2)) + + n = self.strands() + trace_sum = sum(quantum_integer(d) * self.TL_matrix(d, variab=variab).trace() + for d in range(n+1) if (n+d) % 2 == 0) + + if normalized: + delta = A**2 + A**(-2) + trace_sum = trace_sum / delta**n + return trace_sum + + @lazy_attribute + def _jones_polynomial(self): + """ + Cached version of the Jones polynomial in a generic variable + with the Skein normalization. + + The computation of the Jones polynomial uses the representation + of the braid group on the Temperley--Lieb algebra. We cache the + part of the calculation which does not depend on the choices of + variables or normalizations. + + .. SEEALSO:: + + :meth:`jones_polynomial` + + TESTS:: + + sage: B = BraidGroup(9) + sage: b = B([1, 2, 3, 4, 5, 6, 7, 8]) + sage: b.jones_polynomial() + 1 + + sage: B = BraidGroup(2) + sage: b = B([]) + sage: b._jones_polynomial + -A^-2 - A^2 + sage: b = B([-1, -1, -1]) + sage: b._jones_polynomial + -A^-16 + A^-12 + A^-4 + """ + trace = self.markov_trace(normalized=False) + A = trace.parent().gens()[0] + D = A**2 + A**(-2) + exp_sum = self.exponent_sum() + num_comp = self.components_in_closure() + return (-1)**(num_comp-1) * A**(2*exp_sum) * trace // D + + def jones_polynomial(self, variab=None, skein_normalization=False): + """ + Return the Jones polynomial of the trace closure of the braid. + + The normalization is so that the unknot has Jones polynomial `1`. If + ``skein_normalization`` is ``True``, the variable of the result is + replaced by a itself to the power of `4`, so that the result + agrees with the conventions of [Lic]_ (which in particular differs + slightly from the conventions used otherwise in this class), had + one used the conventional Kauffman bracket variable notation directly. + + If ``variab`` is ``None`` return a polynomial in the variable `A` + or `t`, depending on the value ``skein_normalization``. In + particular, if ``skein_normalization`` is ``False``, return the + result in terms of the variable `t`, also used in [Lic]_. + + INPUT: + + - ``variab`` -- variable (default: ``None``); the variable in the + resulting polynomial; if unspecified, use either a default variable + in `ZZ[A,A^{-1}]` or the variable `t` in the symbolic ring + + - ``skein_normalization`` -- boolean (default: ``False``); determines + the variable of the resulting polynomial + + OUTPUT: + + If ``skein_normalization`` if ``False``, this returns an element + in the symbolic ring as the Jones polynomial of the closure might + have fractional powers when the closure of the braid is not a knot. + Otherwise the result is a Laurant polynomial in ``variab``. + + EXAMPLES: + + The unknot:: + + sage: B = BraidGroup(9) + sage: b = B([1, 2, 3, 4, 5, 6, 7, 8]) + sage: b.jones_polynomial() + 1 + + Two unlinked unknots:: + + sage: B = BraidGroup(2) + sage: b = B([]) + sage: b.jones_polynomial() + -sqrt(t) - 1/sqrt(t) + + The Hopf link:: + + sage: B = BraidGroup(2) + sage: b = B([-1,-1]) + sage: b.jones_polynomial() + -1/sqrt(t) - 1/t^(5/2) + + Different representations of the trefoil and one of its mirror:: + + sage: B = BraidGroup(2) + sage: b = B([-1, -1, -1]) + sage: b.jones_polynomial(skein_normalization=True) + -A^-16 + A^-12 + A^-4 + sage: b.jones_polynomial() + 1/t + 1/t^3 - 1/t^4 + sage: B = BraidGroup(3) + sage: b = B([-1, -2, -1, -2]) + sage: b.jones_polynomial(skein_normalization=True) + -A^-16 + A^-12 + A^-4 + sage: R. = LaurentPolynomialRing(GF(2)) + sage: b.jones_polynomial(skein_normalization=True, variab=x) + x^-16 + x^-12 + x^-4 + sage: B = BraidGroup(3) + sage: b = B([1, 2, 1, 2]) + sage: b.jones_polynomial(skein_normalization=True) + A^4 + A^12 - A^16 + + K11n42 (the mirror of the "Kinoshita-Terasaka" knot) and K11n34 (the + mirror of the "Conway" knot):: + + sage: B = BraidGroup(4) + sage: b11n42 = B([1, -2, 3, -2, 3, -2, -2, -1, 2, -3, -3, 2, 2]) + sage: b11n34 = B([1, 1, 2, -3, 2, -3, 1, -2, -2, -3, -3]) + sage: cmp(b11n42.jones_polynomial(), b11n34.jones_polynomial()) + 0 + + REFERENCES: + + .. [Lic] William B. Raymond Lickorish. An Introduction to Knot Theory, + volume 175 of Graduate Texts in Mathematics. Springer-Verlag, + New York, 1997. ISBN 0-387-98254-X + """ + if skein_normalization: + if variab is None: + return self._jones_polynomial + else: + return self._jones_polynomial(variab) + else: + from sage.symbolic.ring import SR + from sage.rings.integer_ring import ZZ + if variab is None: + variab = 't' + # We force the result to be in the symbolic ring because of the expand + return self._jones_polynomial(SR(variab)**(ZZ(1)/ZZ(4))).expand() + @cached_method def left_normal_form(self): """ @@ -784,6 +1132,9 @@ def __init__(self, names): FinitelyPresentedGroup.__init__(self, free_group, tuple(rels)) self._nstrands_ = n+1 + # For caching TL_representation() + self._TL_representation_dict = {} + def __reduce__(self): """ TESTS:: @@ -1070,6 +1421,356 @@ def _LKB_matrix_(self, braid, variab): A[l.index(Set([j, k])), m] = 1 return A + def dimension_of_TL_space(self, drain_size): + """ + Return the dimension of a particular Templerley--Lieb representation + summand of ``self``. + + Following the notation of :meth:`TL_basis_with_drain`, the summand + is the one corresponding to the number of drains being fixed to be + ``drain_size``. + + INPUT: + + - ``drain_size`` -- integer between 0 and the number of strands + (both inclusive) + + EXAMPLES: + + Calculation of the dimension of the representation of `B_8` + corresponding to having `2` drains:: + + sage: B = BraidGroup(8) + sage: B.dimension_of_TL_space(2) + 28 + + The direct sum of endomorphism spaces of these vector spaces make up + the entire Temperley--Lieb algebra:: + + sage: import sage.combinat.diagram_algebras as da + sage: B = BraidGroup(6) + sage: dimensions = [B.dimension_of_TL_space(d)**2 for d in [0, 2, 4, 6]] + sage: total_dim = sum(dimensions) + sage: total_dim == len(list(da.temperley_lieb_diagrams(6))) + True + """ + n = self.strands() + if drain_size > n: + raise ValueError("number of drains must not exceed number of strands") + if (n + drain_size) % 2 == 1: + raise ValueError("parity of strands and drains must agree") + + m = (n - drain_size) // 2 + return Integer(n-1).binomial(m) - Integer(n-1).binomial(m - 2) + + def TL_basis_with_drain(self, drain_size): + """ + Return a basis of a summand of the Temperley--Lieb--Jones + representation of ``self``. + + The basis elements are given by non-intersecting pairings of `n+d` + points in a square with `n` points marked 'on the top' and `d` points + 'on the bottom' so that every bottom point is paired with a top point. + Here, `n` is the number of strands of the braid group, and `d` is + specified by ``drain_size``. + + A basis element is specified as a list of integers obtained by + considering the pairings as obtained as the 'highest term' of + trivalent trees marked by Jones--Wenzl projectors (see e.g. [Wan10]_). + In practice, this is a list of non-negative integers whose first + element is ``drain_size``, whose last element is `0`, and satisfying + that consecutive integers have difference `1`. Moreover, the length + of each basis element is `n + 1`. + + Given these rules, the list of lists is constructed recursively + in the natural way. + + INPUT: + + - ``drain_size`` -- integer between 0 and the number of strands + (both inclusive) + + OUTPUT: + + A list of basis elements, each of which is a list of integers. + + EXAMPLES: + + We calculate the basis for the appropriate vector space for `B_5` when + `d = 3`:: + + sage: B = BraidGroup(5) + sage: B.TL_basis_with_drain(3) + [[3, 4, 3, 2, 1, 0], + [3, 2, 3, 2, 1, 0], + [3, 2, 1, 2, 1, 0], + [3, 2, 1, 0, 1, 0]] + + The number of basis elements hopefully correponds to the general + formula for the dimension of the representation spaces:: + + sage: B = BraidGroup(10) + sage: d = 2 + sage: B.dimension_of_TL_space(d) == len(B.TL_basis_with_drain(d)) + True + + REFERENCES: + + .. [Wan10] Zhenghan Wang. Tolological quantum computation. Providence, + RI: American Mathematical Society (AMS), 2010. + ISBN 978-0-8218-4930-9 + """ + def fill_out_forest(forest, treesize): + # The basis elements are built recursively using this function, + # which takes a collection of partial basis elements, given in + # terms of trivalent trees (i.e. a 'forest') and extends each of + # the trees by one branch. + if not forest: + raise ValueError("forest has to start with a tree") + if forest[0][0] + treesize % 2 == 0: + raise ValueError("parity mismatch in forest creation") + # Loop over all trees + newforest = list(forest) + for tree in forest: + if len(tree) < treesize: + newtreeup = list(tree) + newtreedown = list(tree) + newforest.remove(tree) # Cut down the original tree + # Add two greater trees, admissibly. We need to check two + # things to ensure that the tree will eventually define a + # basis elements: that its 'colour' is not too large, and + # that it is positive. + if tree[-1] < treesize - len(tree) + 1: + newtreeup.append(tree[-1] + 1) + newforest.append(newtreeup) + if tree[-1] > 0: + newtreedown.append(tree[-1] - 1) + newforest.append(newtreedown) + # Are we there yet? + if len(newforest[0]) == treesize: + return newforest + else: + return fill_out_forest(newforest, treesize) + + n = self.strands() + if drain_size > n: + raise ValueError("number of drains must not exceed number of strands") + if (n + drain_size) % 2 == 1: + raise ValueError("parity of strands and drains must agree") + + # We can now start the process: all we know is that our basis elements + # have a drain size of d, so we use fill_out_forest to build all basis + # elements out of this + basis = [[drain_size]] + forest = fill_out_forest(basis, n-1) + for tree in forest: + tree.extend([1, 0]) + return forest + + @cached_method + def _TL_action(self, drain_size): + """ + Return a matrix representing the action of cups and caps on + Temperley--Lieb diagrams corresponding to ``self``. + + The action space is the space of non-crossing diagrams of `n+d` + points, where `n` is the number of strands, and `d` is specified by + ``drain_size``. As in :meth:`TL_basis_with_drain`, we put certain + constraints on the diagrams. + + We essentially calculate the action of the TL-algebra generators + `e_i` on the algebra itself: the action of `e_i` on one of our basis + diagrams is itself a basis diagram, and ``auxmat`` will store the + index of this new basis diagram. + + In some cases, the new diagram will connect two bottom points which + we explicitly disallow (as such a diagram is not one of our basis + elements). In this case, the corresponding ``auxmat`` entry will + be `-1`. + + This is used in :meth:`TL_representation` and could be included + entirely in that method. They are split for purposes of caching. + + INPUT: + + - ``drain_size`` -- integer between 0 and the number of strands + (both inclusive) + + EXAMPLES:: + + sage: B = BraidGroup(4) + sage: B._TL_action(2) + [ 0 0 -1] + [ 1 1 1] + [-1 2 2] + sage: B._TL_action(0) + [1 1] + [0 0] + [1 1] + sage: B = BraidGroup(6) + sage: B._TL_action(2) + [ 1 1 2 3 1 2 3 -1 -1] + [ 0 0 5 6 5 5 6 5 6] + [ 1 1 1 -1 4 4 8 8 8] + [ 5 2 2 2 5 5 5 7 7] + [-1 -1 3 3 8 6 6 8 8] + """ + n = self.strands() + basis = self.TL_basis_with_drain(drain_size) + auxmat = matrix(n-1, len(basis)) + for i in range(1, n): # For each of the e_i + for v in range(len(basis)): # For each basis element + tree = basis[v] + if tree[i-1] < tree[i] and tree[i+1] < tree[i]: + # Here, for instance, we've created an unknot. + auxmat[i-1, v] = v + if tree[i-1] > tree[i] and tree[i+1] > tree[i]: + newtree = list(tree) + newtree[i] += 2 + auxmat[i-1, v] = basis.index(newtree) + if tree[i-1] > tree[i] and tree[i+1] < tree[i]: + newtree = list(tree) + newtree[i-1] -= 2 + j = 2 + while newtree[i-j] != newtree[i] and i-j >= 0: + newtree[i-j] -= 2 + j += 1 + if newtree in basis: + auxmat[i-1, v] = basis.index(newtree) + else: + auxmat[i-1, v] = -1 + if tree[i-1] < tree[i] and tree[i+1] > tree[i]: + newtree = list(tree) + newtree[i+1] -= 2 + j = 2 + while newtree[i+j] != newtree[i] and i+j <= n: + newtree[i+j] -= 2 + j += 1 + if newtree in basis: + auxmat[i-1, v] = basis.index(newtree) + else: + auxmat[i-1, v] = -1 + return auxmat + + def TL_representation(self, drain_size, variab=None): + r""" + Return representation matrices of the Temperley--Lieb--Jones + representation of standard braid group generators and inverses + of ``self``. + + The basis is given by non-intersecting pairings of `(n+d)` points, + where `n` is the number of strands, and `d` is given by + ``drain_size``, and the pairings satisfy certain rules. See + :meth:`TL_basis_with_drain()` for details. This basis has + the useful property that all resulting entries can be regarded as + Laurent polynomials. + + We use the convention that the eigenvalues of the standard generators + are `1` and `-A^4`, where `A` is the generator of the Laurent + polynomial ring. + + When `d = n - 2` and the variables are picked appropriately, the + resulting representation is equivalent to the reduced Burau + representation. When `d = n`, the resulting representation is + trivial and 1-dimensional. + + INPUT: + + - ``drain_size`` -- integer between 0 and the number of strands + (both inclusive) + - ``variab`` -- variable (default: ``None``); the variable in the + entries of the matrices; if ``None``, then use a default variable + in `\ZZ[A,A^{-1}]` + + OUTPUT: + + A list of matrices corresponding to the representations of each + of the standard generators and their inverses. + + EXAMPLES:: + + sage: B = BraidGroup(4) + sage: B.TL_representation(0) + [( + [ 1 0] [ 1 0] + [ A^2 -A^4], [ A^-2 -A^-4] + ), + ( + [-A^4 A^2] [-A^-4 A^-2] + [ 0 1], [ 0 1] + ), + ( + [ 1 0] [ 1 0] + [ A^2 -A^4], [ A^-2 -A^-4] + )] + sage: R. = LaurentPolynomialRing(GF(2)) + sage: B.TL_representation(0, variab=A) + [( + [ 1 0] [ 1 0] + [A^2 A^4], [A^-2 A^-4] + ), + ( + [A^4 A^2] [A^-4 A^-2] + [ 0 1], [ 0 1] + ), + ( + [ 1 0] [ 1 0] + [A^2 A^4], [A^-2 A^-4] + )] + sage: B = BraidGroup(8) + sage: B.TL_representation(8) + [([1], [1]), + ([1], [1]), + ([1], [1]), + ([1], [1]), + ([1], [1]), + ([1], [1]), + ([1], [1])] + """ + if variab is None: + if drain_size in self._TL_representation_dict: + return self._TL_representation_dict[drain_size] + R = LaurentPolynomialRing(IntegerRing(), 'A') + A = R.gens()[0] + else: + R = variab.parent() + A = variab + + n = self.strands() + auxmat = self._TL_action(drain_size) + dimension = auxmat.ncols() + # The action of the sigma_i is given in terms of the actions of the + # e_i which is what auxmat describes. Our choice of normalization means + # that \sigma_i acts by the identity + A**2 e_i. + rep_matrices = [] # The list which will store the actions of sigma_i + + # Store the respective powers + Ap2 = A**2 + Apm2 = A**(-2) + Ap4 = -A**4 + Apm4 = -A**(-4) + + for i in range(n-1): # For each \sigma_{i+1} + rep_mat_new = identity_matrix(R, dimension, sparse=True) + rep_mat_new_inv = identity_matrix(R, dimension, sparse=True) + for v in range(dimension): + new_mat_entry = auxmat[i, v] + if new_mat_entry == v: # Did we create an unknot? + rep_mat_new[v, v] = Ap4 + rep_mat_new_inv[v, v] = Apm4 + elif new_mat_entry >= 0: + rep_mat_new[new_mat_entry, v] = Ap2 + rep_mat_new_inv[new_mat_entry, v] = Apm2 + rep_matrices.append((rep_mat_new, rep_mat_new_inv)) + + if variab is None: # Cache the result in this case + for mat_pair in rep_matrices: + mat_pair[0].set_immutable() + mat_pair[1].set_immutable() + self._TL_representation_dict[drain_size] = rep_matrices + + return rep_matrices + def mapping_class_action(self, F): """ Return the action of self in the free group F as mapping class group. diff --git a/src/sage/groups/conjugacy_classes.py b/src/sage/groups/conjugacy_classes.py index 3553af8e8a0..d528090a295 100644 --- a/src/sage/groups/conjugacy_classes.py +++ b/src/sage/groups/conjugacy_classes.py @@ -180,7 +180,7 @@ def __iter__(self): sage: a = G(a) sage: C = ConjugacyClass(G, a) sage: it = iter(C) - sage: [next(it) for _ in range(5)] + sage: [next(it) for _ in range(5)] # random (nothing guarantees enumeration order) [ [1 1] [ 2 1] [ 0 1] [ 3 1] [ 3 4] [0 1], [-1 0], [-1 2], [-4 -1], [-1 -1] diff --git a/src/sage/groups/finitely_presented_named.py b/src/sage/groups/finitely_presented_named.py index 31214fc9751..88fd6df7420 100644 --- a/src/sage/groups/finitely_presented_named.py +++ b/src/sage/groups/finitely_presented_named.py @@ -130,14 +130,14 @@ def FinitelyGeneratedAbelianPresentation(int_list): sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) - Finitely presented group < a, b, c | a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) - Finitely presented group < a, b, c | a^2, b^2, a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^2, b^2, a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > Outputs are reduced to minimal generators and relations:: @@ -193,7 +193,7 @@ def FinitelyGeneratedAbelianPresentation(int_list): ret_rls = [F([i+1])**invariants[i] for i in range(len(invariants)) if invariants[i]!=0] # Build commutator relations - gen_pairs = list(Set(F.gens()).subsets(2)) + gen_pairs = [[F.gen(i),F.gen(j)] for i in range(F.ngens()-1) for j in range(i+1,F.ngens())] ret_rls = ret_rls + [x[0]**(-1)*x[1]**(-1)*x[0]*x[1] for x in gen_pairs] return FinitelyPresentedGroup(F, tuple(ret_rls)) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 0bdef462d65..45ef7a1c10c 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -227,6 +227,17 @@ def __init__(self, parent, x): x = AbstractWordTietzeWord(l, parent._gap_gens()) ElementLibGAP.__init__(self, parent, x) + def __hash__(self): + r""" + TESTS:: + + sage: G. = FreeGroup() + sage: hash(a*b*b*~a) + -485698212495963022 # 64-bit + -1876767630 # 32-bit + """ + return hash(self.Tietze()) + def _latex_(self): """ Return a LaTeX representation diff --git a/src/sage/groups/indexed_free_group.py b/src/sage/groups/indexed_free_group.py index d41d5491ce2..e80966897a8 100644 --- a/src/sage/groups/indexed_free_group.py +++ b/src/sage/groups/indexed_free_group.py @@ -206,33 +206,6 @@ def gen(self, x): return self.element_class(self, ((x,1),)) class Element(IndexedFreeMonoidElement): - def __lt__(self, other): - """ - Return whether ``self`` is smaller than ``y``. - - This is done by comparing lexicographically the words for - ``self`` and ``y``. In particular this assumes that the - (index of) the generators are totally ordered. - - EXAMPLES:: - - sage: G = Groups().free(index_set=ZZ) - sage: a,b,c,d,e = [G.gen(i) for i in range(5)] - sage: a < b - True - sage: a^-1*b < b^-1*a - True - sage: a*b < a*a^-1 - False - sage: a^-1*a < a^2 - True - sage: a^2*b < a*b^-1*a*b - True - """ - if not isinstance(other, IndexedMonoidElement): - return False - return self.to_word_list() < other.to_word_list() - def __len__(self): """ Return the length of ``self``. diff --git a/src/sage/groups/matrix_gps/coxeter_group.py b/src/sage/groups/matrix_gps/coxeter_group.py index bf1c04ec892..d4a46d4933b 100644 --- a/src/sage/groups/matrix_gps/coxeter_group.py +++ b/src/sage/groups/matrix_gps/coxeter_group.py @@ -23,6 +23,7 @@ from sage.categories.coxeter_groups import CoxeterGroups from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract +from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_generic from sage.groups.matrix_gps.group_element import MatrixGroupElement_generic from sage.graphs.graph import Graph @@ -31,7 +32,7 @@ from sage.rings.all import ZZ from sage.rings.infinity import infinity from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField - +from sage.misc.superseded import deprecated_function_alias class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentation): r""" @@ -151,7 +152,7 @@ class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentat [ 3 1 2 15] [ 2 2 1 7] [ 3 15 7 1] - sage: G2 = W.coxeter_graph() + sage: G2 = W.coxeter_diagram() sage: CoxeterGroup(G2) is W True @@ -202,67 +203,13 @@ def __classcall_private__(cls, data, base_ring=None, index_set=None): sage: W4 = CoxeterGroup(G2) sage: W1 is W4 True - - Check with `\infty` because of the hack of using `-1` to represent - `\infty` in the Coxeter matrix:: - - sage: G = Graph([(0, 1, 3), (1, 2, oo)]) - sage: W1 = CoxeterGroup(matrix([[1, 3, 2], [3,1,-1], [2,-1,1]])) - sage: W2 = CoxeterGroup(G) - sage: W1 is W2 - True - sage: CoxeterGroup(W1.coxeter_graph()) is W1 - True """ - if isinstance(data, CartanType_abstract): - if index_set is None: - index_set = data.index_set() - data = data.coxeter_matrix() - elif isinstance(data, Graph): - G = data - n = G.num_verts() - - # Setup the basis matrix as all 2 except 1 on the diagonal - data = matrix(ZZ, [[2]*n]*n) - for i in range(n): - data[i, i] = ZZ.one() - - verts = G.vertices() - for e in G.edges(): - m = e[2] - if m is None: - m = 3 - elif m == infinity or m == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - m = -1 - elif m <= 1: - raise ValueError("invalid Coxeter graph label") - i = verts.index(e[0]) - j = verts.index(e[1]) - data[j, i] = data[i, j] = m - - if index_set is None: - index_set = G.vertices() - else: - try: - data = matrix(data) - except (ValueError, TypeError): - data = CartanType(data).coxeter_matrix() - if not data.is_symmetric(): - raise ValueError("the Coxeter matrix is not symmetric") - if any(d != 1 for d in data.diagonal()): - raise ValueError("the Coxeter matrix diagonal is not all 1") - if any(val <= 1 and val != -1 for i, row in enumerate(data.rows()) - for val in row[i+1:]): - raise ValueError("invalid Coxeter label") - - if index_set is None: - index_set = range(data.nrows()) + data = CoxeterMatrix(data, index_set=index_set) if base_ring is None: base_ring = UniversalCyclotomicField() - data.set_immutable() return super(CoxeterMatrixGroup, cls).__classcall__(cls, - data, base_ring, tuple(index_set)) + data, base_ring, data.index_set()) def __init__(self, coxeter_matrix, base_ring, index_set): """ @@ -301,8 +248,7 @@ def __init__(self, coxeter_matrix, base_ring, index_set): True """ self._matrix = coxeter_matrix - self._index_set = index_set - n = ZZ(coxeter_matrix.nrows()) + n = coxeter_matrix.rank() # Compute the matrix with entries `2 \cos( \pi / m_{ij} )`. MS = MatrixSpace(base_ring, n, sparse=True) MC = MS._get_matrix_class() @@ -313,31 +259,19 @@ def __init__(self, coxeter_matrix, base_ring, index_set): from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2*cos(pi / x)) if x != -1 else base_ring(2) - gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[i, j]) + gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[index_set[i], index_set[j]]) for j in range(n)}, coerce=True, copy=True) for i in range(n)] - # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. - # This describes the bilinear form corresponding to this - # Coxeter system, and might lead us out of our base ring. - base_field = base_ring.fraction_field() - MS2 = MatrixSpace(base_field, n, sparse=True) - MC2 = MS2._get_matrix_class() - self._bilinear = MC2(MS2, entries={(i, j): val(coxeter_matrix[i, j]) / base_field(-2) - for i in range(n) for j in range(n) - if coxeter_matrix[i, j] != 2}, - coerce=True, copy=True) - self._bilinear.set_immutable() category = CoxeterGroups() # Now we shall see if the group is finite, and, if so, refine # the category to ``category.Finite()``. Otherwise the group is # infinite and we refine the category to ``category.Infinite()``. - is_finite = self._finite_recognition() - if is_finite: + if self._matrix.is_finite(): category = category.Finite() else: category = category.Infinite() - FinitelyGeneratedMatrixGroup_generic.__init__(self, n, base_ring, + FinitelyGeneratedMatrixGroup_generic.__init__(self, ZZ(n), base_ring, gens, category=category) def _finite_recognition(self): @@ -512,7 +446,7 @@ def index_set(self): sage: W = CoxeterGroup([[1,3],[3,1]]) sage: W.index_set() - (0, 1) + (1, 2) sage: W = CoxeterGroup([[1,3],[3,1]], index_set=['x', 'y']) sage: W.index_set() ('x', 'y') @@ -520,7 +454,7 @@ def index_set(self): sage: W.index_set() (1, 2, 3) """ - return self._index_set + return self._matrix.index_set() def coxeter_matrix(self): """ @@ -540,37 +474,29 @@ def coxeter_matrix(self): """ return self._matrix - def coxeter_graph(self): + def coxeter_diagram(self): """ - Return the Coxeter graph of ``self``. + Return the Coxeter diagram of ``self``. EXAMPLES:: sage: W = CoxeterGroup(['H',3], implementation="reflection") - sage: G = W.coxeter_graph(); G + sage: G = W.coxeter_diagram(); G Graph on 3 vertices sage: G.edges() - [(1, 2, None), (2, 3, 5)] + [(1, 2, 3), (2, 3, 5)] sage: CoxeterGroup(G) is W True sage: G = Graph([(0, 1, 3), (1, 2, oo)]) sage: W = CoxeterGroup(G) - sage: W.coxeter_graph() == G + sage: W.coxeter_diagram() == G True - sage: CoxeterGroup(W.coxeter_graph()) is W + sage: CoxeterGroup(W.coxeter_diagram()) is W True """ - G = Graph() - G.add_vertices(self.index_set()) - for i, row in enumerate(self._matrix.rows()): - for j, val in enumerate(row[i+1:]): - if val == 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j]) - elif val > 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j], val) - elif val == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - G.add_edge(self._index_set[i], self._index_set[i+1+j], infinity) - return G + return self._matrix.coxeter_graph() + + coxeter_graph = deprecated_function_alias(17798, coxeter_diagram) def bilinear_form(self): r""" @@ -596,7 +522,7 @@ def bilinear_form(self): [ 0 -1/2 1 0] [ 0 -1/2 0 1] """ - return self._bilinear + return self._matrix.bilinear_form() def is_finite(self): """ @@ -689,9 +615,9 @@ def simple_reflection(self, i): [ 0 1 0] [ 0 1 -1] """ - if not i in self._index_set: + if not i in self.index_set(): raise ValueError("%s is not in the index set %s" % (i, self.index_set())) - return self.gen(self._index_set.index(i)) + return self.gen(self.index_set().index(i)) class Element(MatrixGroupElement_generic): """ @@ -723,7 +649,7 @@ def has_right_descent(self, i): sage: map(lambda i: elt.has_right_descent(i), [1, 2, 3]) [True, False, True] """ - i = self.parent()._index_set.index(i) + i = self.parent().index_set().index(i) col = self.matrix().column(i) return all(x <= 0 for x in col) @@ -743,3 +669,4 @@ def canonical_matrix(self): [ 0 1 -1] """ return self.matrix() + diff --git a/src/sage/groups/matrix_gps/group_element.py b/src/sage/groups/matrix_gps/group_element.py index 5c0ae88326b..434c7ce8027 100644 --- a/src/sage/groups/matrix_gps/group_element.py +++ b/src/sage/groups/matrix_gps/group_element.py @@ -117,12 +117,23 @@ class MatrixGroupElement_base(MultiplicativeGroupElement): EXAMPLES:: sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] sage: G = MatrixGroup(gens) sage: g = G.random_element() sage: type(g) """ + def __hash__(self): + r""" + TESTS:: + + sage: MS = MatrixSpace(GF(3), 2) + sage: G = MatrixGroup([MS([1,1,0,1]), MS([1,0,1,1])]) + sage: g = G.an_element() + sage: hash(g) + 0 + """ + return hash(self.matrix()) def _repr_(self): """ diff --git a/src/sage/groups/matrix_gps/morphism.py b/src/sage/groups/matrix_gps/morphism.py index 775fb239b90..2d4a0e67d47 100644 --- a/src/sage/groups/matrix_gps/morphism.py +++ b/src/sage/groups/matrix_gps/morphism.py @@ -109,6 +109,19 @@ def __init__(self, homset, imgsH, check=True): sage: G = MatrixGroup([MS([3,0,0,1])]) sage: a = G.gens()[0]^2 sage: phi = G.hom([a]) + + TESTS: + + Check that :trac:`19406` is fixed:: + + sage: G = GL(2, GF(3)) + sage: H = GL(3, GF(2)) + sage: mat1 = H([[-1,0,0],[0,0,-1],[0,-1,0]]) + sage: mat2 = H([[1,1,1],[0,0,-1],[-1,0,0]]) + sage: phi = G.hom([mat1, mat2]) + Traceback (most recent call last): + ... + TypeError: images do not define a group homomorphism """ MatrixGroupMorphism.__init__(self, homset) # sets the parent from sage.libs.gap.libgap import libgap @@ -118,7 +131,7 @@ def __init__(self, homset, imgsH, check=True): imgs = [to_libgap(x) for x in imgsH] self._phi = phi = libgap.GroupHomomorphismByImages(G.gap(), H.gap(), gens, imgs) if not phi.IsGroupHomomorphism(): - raise ValueError('The map '+str(gensG)+'-->'+str(imgsH)+' is not a homomorphism') + raise ValueError('the map {}-->{} is not a homomorphism'.format(G.gens(), imgsH)) def gap(self): """ diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 162c4426766..517575ccfca 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -1058,6 +1058,42 @@ def smallest_moved_point(self): """ return self._domain_from_gap[Integer(self._gap_().SmallestMovedPoint())] + def representative_action(self,x,y): + r""" + Return an element of self that maps `x` to `y` if it exists. + + This method wraps the gap function ``RepresentativeAction``, which can + also return elements that map a given set of points on another set of + points. + + INPUT: + + - ``x,y`` -- two elements of the domain. + + EXAMPLE:: + + sage: G = groups.permutation.Cyclic(14) + sage: g = G.representative_action(1,10) + sage: all(g(x) == 1+((x+9-1)%14) for x in G.domain()) + True + + TESTS:: + + sage: g = graphs.PetersenGraph() + sage: g.relabel(list("abcdefghik")) + sage: g.vertices() + ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'] + sage: ag = g.automorphism_group() + sage: a = ag.representative_action('a','b') + sage: g == g.relabel(a,inplace=False) + True + sage: a('a') == 'b' + True + """ + ans = self._gap_().RepresentativeAction(self._domain_to_gap[x], + self._domain_to_gap[y]) + return self._element_class()(ans, self, check=False) + @cached_method def orbits(self): """ diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index daab932818e..75ac680ba0a 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -1017,6 +1017,34 @@ def _repr_(self): """ return "Janko group J%s of order %s as a permutation group"%(self._n,self.order()) +class SuzukiSporadicGroup(PermutationGroup_unique): + def __init__(self): + r""" + Suzuki Sporadic Group + + EXAMPLES:: + + sage: G = groups.permutation.SuzukiSporadic(); G # optional - gap_packages internet + Sporadic Suzuki group acting on 1782 points + + TESTS:: + + sage: G.category() # optional - gap_packages internet + Category of finite permutation groups + sage: TestSuite(G).run(skip=["_test_enumerated_set_contains", "_test_enumerated_set_iter_list"]) # optional - gap_packages internet + """ + gap.load_package("atlasrep") + PermutationGroup_generic.__init__(self, gap_group='AtlasGroup("Suz")') + + def _repr_(self): + """ + EXAMPLES:: + + sage: G = groups.permutation.SuzukiSporadic(); G # optional - gap_packages internet + Sporadic Suzuki group acting on 1782 points + """ + return "Sporadic Suzuki group acting on 1782 points" + class QuaternionGroup(DiCyclicGroup): r""" The quaternion group of order 8. @@ -2174,9 +2202,8 @@ def PrimitiveGroups(d=None): .. attention:: - PrimitiveGroups requires the optional GAP database - package. Please install it with - ``install_package(`database_gap')``. + PrimitiveGroups requires the optional GAP database package. + Please install it by running ``sage -i database_gap``. EXAMPLES:: diff --git a/src/sage/groups/perm_gps/permutation_groups_catalog.py b/src/sage/groups/perm_gps/permutation_groups_catalog.py index ee22c4860aa..8f2593ae282 100644 --- a/src/sage/groups/perm_gps/permutation_groups_catalog.py +++ b/src/sage/groups/perm_gps/permutation_groups_catalog.py @@ -23,6 +23,7 @@ from permgroup_named import DiCyclicGroup as DiCyclic from permgroup_named import MathieuGroup as Mathieu from permgroup_named import JankoGroup as Janko +from permgroup_named import SuzukiSporadicGroup as SuzukiSporadic from permgroup_named import SuzukiGroup as Suzuki from permgroup_named import (PGL, PSL, PSp,PSU,PGU,) from permgroup_named import TransitiveGroup as Transitive diff --git a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py index fd7f3aaf888..27cbac008c9 100644 --- a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py +++ b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py @@ -82,7 +82,7 @@ class SemimonomialTransformationGroup(FiniteGroup, UniqueRepresentation): is defined by .. math:: - + (\phi, \pi, \alpha)(\psi, \sigma, \beta) = (\phi \cdot \psi^{\pi, \alpha}, \pi\sigma, \alpha \circ \beta) diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py new file mode 100644 index 00000000000..dd3cc3c304d --- /dev/null +++ b/src/sage/homology/algebraic_topological_model.py @@ -0,0 +1,592 @@ +# -*- coding: utf-8 -*- +r""" +Algebraic topological model for a cell complex + +This file contains two functions, :func:`algebraic_topological_model` +and :func:`algebraic_topological_model_delta_complex`. The second +works more generally: for all simplicial, cubical, and +`\Delta`-complexes. The first only works for simplicial and cubical +complexes, but it is faster in those case. + +AUTHORS: + +- John H. Palmieri (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# 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/ +######################################################################## + +# TODO: cythonize this. + +from sage.modules.free_module_element import vector +from sage.modules.free_module import VectorSpace +from sage.matrix.constructor import matrix, zero_matrix +from sage.matrix.matrix_space import MatrixSpace +from chain_complex import ChainComplex +from chain_complex_morphism import ChainComplexMorphism +from chain_homotopy import ChainContraction +from sage.rings.rational_field import QQ + +def algebraic_topological_model(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + INPUT: + + - ``K`` -- either a simplicial complex or a cubical complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + This construction appears in a paper by Pilarczyk and Réal [PR]_. + Given a cell complex `K` and a field `F`, there is a chain complex + `C` associated to `K` with coefficients in `F`. The *algebraic + topological model* for `K` is a chain complex `M` with trivial + differential, along with chain maps `\pi: C \to M` and `\iota: M + \to C` such that + + - `\pi \iota = 1_M`, and + - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`. + + In particular, `\pi` and `\iota` induce isomorphisms on homology, + and since `M` has trivial differential, it is its own homology, + and thus also the homology of `C`. Thus `\iota` lifts homology + classes to their cycle representatives. + + The chain homotopy `\phi` satisfies some additional properties, + making it a *chain contraction*: + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Given an algebraic topological model for `K`, it is then easy to + compute cup products and cohomology operations on the cohomology + of `K`, as described in [G-DR03]_ and [PR]_. + + Implementation details: the cell complex `K` must have an + :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` + method from which we can extract a list of cells in each + dimension. Combining the lists in increasing order of dimension + then defines a filtration of the complex: a list of cells in which + the boundary of each cell consists of cells earlier in the + list. This is required by Pilarczyk and Réal's algorithm. There + must also be a + :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` + method, to construct the chain complex `C` associated to this + chain complex. + + In particular, this works for simplicial complexes and cubical + complexes. It doesn't work for `\Delta`-complexes, though: the list + of their `n`-cells has the wrong format. + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = algebraic_topological_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = cubical_complexes.Torus() + sage: phi, M = algebraic_topological_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = simplicial_complexes.Simplex(2) + sage: phi, M = algebraic_topological_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = T.algebraic_topological_model() + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + # For each n, phi_dict[n] is a dictionary of the form {idx: + # vector}, where idx is the index of an n-cell in the list of + # n-cells in K, and vector is the image of that n-cell, as an + # element in the free module of (n+1)-chains for K. + phi_dict = {} + # For each n, pi_dict[n] is a dictionary of the same form, except + # that the target vectors should be elements of the chain complex M. + pi_dict = {} + # For each n, iota_dict[n] is a dictionary of the form {cell: + # vector}, where cell is one of the generators for M and vector is + # its image in C, as an element in the free module of n-chains. + iota_dict = {} + + for n in range(K.dimension()+1): + gens[n] = [] + phi_dict[n] = {} + pi_dict[n] = {} + iota_dict[n] = {} + + C = K.chain_complex(base_ring=base_ring) + # old_cells: cells one dimension lower. + old_cells = [] + + for dim in range(K.dimension()+1): + n_cells = K.n_cells(dim) + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + V_old = VectorSpace(base_ring, old_rank) + zero = V_old.zero_vector() + + for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())): + # c is the pair (cell, the corresponding standard basis + # vector in the free module of chains). Separate its + # components, calling them c and c_vec: + c_vec = c[1] + c = c[0] + # No need to set zero values for any of the maps: we will + # assume any unset values are zero. + # From the paper: phi_dict[c] = 0. + + # c_bar = c - phi(bdry(c)) + c_bar = c_vec + bdry_c = diff * c_vec + # Apply phi to bdry_c and subtract from c_bar. + for (idx, coord) in bdry_c.iteritems(): + try: + c_bar -= coord * phi_dict[dim-1][idx] + except KeyError: + pass + + bdry_c_bar = diff * c_bar + + # Evaluate pi(bdry(c_bar)). + pi_bdry_c_bar = zero + + for (idx, coeff) in bdry_c_bar.iteritems(): + try: + pi_bdry_c_bar += coeff * pi_dict[dim-1][idx] + except KeyError: + pass + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c) + # iota(c) = c_bar + iota_dict[dim][c] = c_bar + # pi(c) = c + pi_dict[dim][c_idx] = c_vec + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + # Now find the actual cell. + u = old_cells[u_idx] + if u in gens[dim-1]: + break + + # pi(c) = 0: no need to do anything about this. + for c_j_idx in range(old_rank): + # eta_ij = . + try: + eta_ij = pi_dict[dim-1][c_j_idx][u_idx] + except (KeyError, IndexError): + eta_ij = 0 + if eta_ij: + # Adjust phi(c_j). + try: + phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + except KeyError: + phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + try: + pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + except KeyError: + pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + gens[dim-1].remove(u) + del iota_dict[dim-1][u] + old_cells = n_cells + + # Now we have constructed the raw data for M, pi, iota, phi, so we + # have to convert that to data which can be used to construct chain + # complexes, chain maps, and chain contractions. + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + # pi_data: the matrices defining pi. Similar for iota_data and phi_data. + pi_data = {} + iota_data = {} + phi_data = {} + for n in range(K.dimension()+1): + n_cells = K.n_cells(n) + # Remove zero entries from pi_dict and phi_dict. + pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]} + phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]} + # Convert gens to data defining the chain complex M with + # trivial differential. + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + # Convert the dictionaries for pi, iota, phi to matrices which + # will define chain maps and chain homotopies. + pi_cols = [] + phi_cols = [] + for (idx, c) in enumerate(n_cells): + # First pi: + if idx in pi_dict[n]: + column = vector(base_ring, M_rows) + for (entry, coeff) in pi_dict[n][idx].iteritems(): + # Translate from cells in n_cells to cells in gens[n]. + column[gens[n].index(n_cells[entry])] = coeff + else: + column = vector(base_ring, M_rows) + pi_cols.append(column) + + # Now phi: + try: + column = phi_dict[n][idx] + except KeyError: + column = vector(base_ring, len(K.n_cells(n+1))) + phi_cols.append(column) + # Now iota: + iota_cols = [iota_dict[n][c] for c in gens[n]] + + pi_data[n] = matrix(base_ring, pi_cols).transpose() + iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose() + phi_data[n] = matrix(base_ring, phi_cols).transpose() + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + +def algebraic_topological_model_delta_complex(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + This has the same basic functionality as + :func:`algebraic_topological_model`, but it also works for + `\Delta`-complexes. For simplicial and cubical complexes it is + somewhat slower, though. + + INPUT: + + - ``K`` -- a simplicial complex, a cubical complex, or a + `\Delta`-complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + See :func:`algebraic_topological_model` for the main + documentation. The difference in implementation between the two: + this uses matrix and vector algebra. The other function does more + of the computations "by hand" and uses cells (given as simplices + or cubes) to index various dictionaries. Since the cells in + `\Delta`-complexes are not as nice, the other function does not + work for them, while this function relies almost entirely on the + structure of the associated chain complex. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex as AT_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = AT_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = delta_complexes.Torus() + sage: phi, M = AT_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = delta_complexes.Simplex(2) + sage: phi, M = AT_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = AT_model(T, QQ) + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + def conditionally_sparse(m): + """ + Return a sparse matrix if the characteristic is zero. + + Multiplication of matrices with low density seems to be quicker + if the matrices are sparse, when over the rationals. Over + finite fields, dense matrices are faster regardless of + density. + """ + if base_ring == QQ: + return m.sparse_matrix() + else: + return m + + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + pi_data = {} + phi_data = {} + iota_data = {} + + for n in range(-1, K.dimension()+1): + gens[n] = [] + + C = K.chain_complex(base_ring=base_ring) + n_cells = [] + pi_cols = [] + iota_cols = {} + + for dim in range(K.dimension()+1): + # old_cells: cells one dimension lower. + old_cells = n_cells + # n_cells: the standard basis for the vector space C.free_module(dim). + n_cells = C.free_module(dim).gens() + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + + # Create some matrix spaces to try to speed up matrix creation. + MS_pi_t = MatrixSpace(base_ring, old_rank, len(gens[dim-1])) + + pi_old = MS_pi_t.matrix(pi_cols).transpose() + iota_cols_old = iota_cols + iota_cols = {} + pi_cols_old = pi_cols + pi_cols = [] + phi_old = MatrixSpace(base_ring, rank, old_rank, sparse=(base_ring==QQ)).zero() + phi_old_cols = phi_old.columns() + phi_old = conditionally_sparse(phi_old) + to_be_deleted = [] + + zero_vector = vector(base_ring, rank) + pi_nrows = pi_old.nrows() + + for c_idx, c in enumerate(n_cells): + # c_bar = c - phi(bdry(c)): + # Avoid a bug in matrix-vector multiplication (trac 19378): + if not diff: + c_bar = c + pi_bdry_c_bar = False + else: + if base_ring == QQ: + c_bar = c - phi_old * (diff * c) + pi_bdry_c_bar = conditionally_sparse(pi_old) * (diff * c_bar) + else: + c_bar = c - phi_old * diff * c + pi_bdry_c_bar = conditionally_sparse(pi_old) * diff * c_bar + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c_idx) + # iota(c) = c_bar + iota_cols[c_idx] = c_bar + # pi(c) = c + pi_cols.append(c) + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + (u_idx, lambda_i) = pi_bdry_c_bar.leading_item() + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + if u_idx not in to_be_deleted: + break + # This element/column needs to be deleted from gens and + # iota_old. Do that later. + to_be_deleted.append(u_idx) + # pi(c) = 0. + pi_cols.append(zero_vector) + for c_j_idx, c_j in enumerate(old_cells): + # eta_ij = . + # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: + eta_ij = c_j.dot_product(pi_old.row(u_idx)) + if eta_ij: + # Adjust phi(c_j). + phi_old_cols[c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + pi_cols_old[c_j_idx] -= eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + # The matrices involved have many zero entries. For + # such matrices, using sparse matrices is faster over + # the rationals, slower over finite fields. + phi_old = matrix(base_ring, phi_old_cols, sparse=(base_ring==QQ)).transpose() + keep = vector(base_ring, pi_nrows, {i:1 for i in range(pi_nrows) + if i not in to_be_deleted}) + cols = [v.pairwise_product(keep) for v in pi_cols_old] + pi_old = MS_pi_t.matrix(cols).transpose() + + # Here cols is a temporary storage for the columns of iota. + cols = [iota_cols_old[i] for i in sorted(iota_cols_old.keys())] + for r in sorted(to_be_deleted, reverse=True): + del cols[r] + del gens[dim-1][r] + iota_data[dim-1] = matrix(base_ring, len(gens[dim-1]), old_rank, cols).transpose() + # keep: rows to keep in pi_cols_old. Start with all + # columns, then delete those in to_be_deleted. + keep = sorted(set(range(pi_nrows)).difference(to_be_deleted)) + # Now cols is a temporary storage for columns of pi. + cols = [v.list_from_positions(keep) for v in pi_cols_old] + pi_data[dim-1] = matrix(base_ring, old_rank, len(gens[dim-1]), cols).transpose() + phi_data[dim-1] = phi_old + + V_gens = VectorSpace(base_ring, len(gens[dim])) + if pi_cols: + cols = [] + for v in pi_cols: + cols.append(V_gens(v.list_from_positions(gens[dim]))) + pi_cols = cols + + pi_data[dim] = matrix(base_ring, rank, len(gens[dim]), pi_cols).transpose() + cols = [iota_cols[i] for i in sorted(iota_cols.keys())] + iota_data[dim] = matrix(base_ring, len(gens[dim]), rank, cols).transpose() + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + for n in range(K.dimension()+1): + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + diff --git a/src/sage/homology/cell_complex.py b/src/sage/homology/cell_complex.py index 8bbd56b3ad6..f0767a22201 100644 --- a/src/sage/homology/cell_complex.py +++ b/src/sage/homology/cell_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Generic cell complexes @@ -37,6 +38,8 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.misc.cachefunc import cached_method class GenericCellComplex(SageObject): r""" @@ -410,7 +413,7 @@ def homology(self, dim=None, **kwds): .. note:: The keyword arguments to this function get passed on to - :meth:``chain_complex`` and its homology. + :meth:`chain_complex` and its homology. ALGORITHM: @@ -422,14 +425,14 @@ def homology(self, dim=None, **kwds): CHomP computes homology, not cohomology, and only works over the integers or finite prime fields. Therefore if any of these conditions fails, or if CHomP is not present, or if - ``algorithm`` is set to 'no_chomp', go to plan B: if ``self`` + ``algorithm`` is set to 'no_chomp', go to plan B: if this complex has a ``_homology`` method -- each simplicial complex has this, for example -- then call that. Such a method implements specialized algorithms for the particular type of cell complex. Otherwise, move on to plan C: compute the chain complex of - ``self`` and compute its homology groups. To do this: over a + this complex and compute its homology groups. To do this: over a field, just compute ranks and nullities, thus obtaining dimensions of the homology groups as vector spaces. Over the integers, compute Smith normal form of the boundary matrices @@ -650,6 +653,266 @@ def betti(self, dim=None, subcomplex=None): except AttributeError: return H.dimension() + def n_chains(self, n, base_ring=None, cochains=False): + r""" + Return the free module of chains in degree ``n`` over ``base_ring``. + + INPUTS: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + The only difference between chains and cochains is + notation. In a simplicial complex, for example, a simplex + ``(0,1,2)`` is written as "(0,1,2)" in the group of chains but + as "\chi_(0,1,2)" in the group of cochains. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: S2.n_chains(1, QQ) + Free module generated by {(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)} over Rational Field + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=False).basis()) + [(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)] + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=True).basis()) + [\chi_(2, 3), \chi_(0, 2), \chi_(1, 3), \chi_(1, 2), \chi_(0, 3), \chi_(0, 1)] + """ + return Chains(tuple(self.n_cells(n)), base_ring, cochains) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this cell complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR]_. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this cell + complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = simplicial_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from algebraic_topological_model import algebraic_topological_model, algebraic_topological_model_delta_complex + from cubical_complex import CubicalComplex + from simplicial_complex import SimplicialComplex + from delta_complex import DeltaComplex + if base_ring is None: + base_ring = QQ + if not isinstance(self, (CubicalComplex, SimplicialComplex, DeltaComplex)): + raise NotImplementedError('only implemented for simplicial, cubical, and Delta complexes') + if isinstance(self, DeltaComplex): + return algebraic_topological_model_delta_complex(self, base_ring) + return algebraic_topological_model(self, base_ring) + + def homology_with_basis(self, base_ring=None, cohomology=False): + r""" + Return the unreduced homology of this complex with + coefficients in ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + - ``cohomology`` -- boolean (optional, default ``False``); if + ``True``, return cohomology instead of homology + + Homology basis elements are named 'h_{dim,i}' where i ranges + between 0 and `r-1`, if `r` is the rank of the homology + group. Cohomology basis elements are denoted `h^{dim,i}` + instead. + + .. SEEALSO:: + + If ``cohomology`` is ``True``, this returns the cohomology + as a graded module. For the ring structure, use + :meth:`cohomology_ring`. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.homology_with_basis(QQ); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}] + sage: H = K.homology_with_basis(GF(2)); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}, h_{1,1}, h_{2,0}] + + The homology is constructed as a graded object, so for + example, you can ask for the basis in a single degree:: + + sage: H.basis(1) + Finite family {(1, 0): h_{1,0}, (1, 1): h_{1,1}} + sage: S3 = delta_complexes.Sphere(3) + sage: H = S3.homology_with_basis(QQ, cohomology=True) + sage: list(H.basis(3)) + [h^{3,0}] + """ + from homology_vector_space_with_basis import HomologyVectorSpaceWithBasis + if base_ring is None: + base_ring = QQ + return HomologyVectorSpaceWithBasis(base_ring, self, cohomology) + + def cohomology_ring(self, base_ring=None): + r""" + Return the unreduced cohomology with coefficients in + ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + The resulting elements are suitable for computing cup + products. For simplicial complexes, they should be suitable + for computing cohomology operations; so far, only mod 2 + cohomology operations have been implemented. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + + The basis elements in dimension ``dim`` are named 'h^{dim,i}' + where `i` ranges between 0 and `r-1`, if `r` is the rank of + the cohomology group. + + .. NOTE:: + + For all but the smallest complexes, this is likely to be + slower than :meth:`cohomology` (with field coefficients), + possibly by several orders of magnitute. This and its + companion :meth:`homology_with_basis` carry extra + information which allows computation of cup products, for + example, but because of speed issues, you may only wish to + use these if you need that extra information. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}] + sage: H = K.cohomology_ring(GF(2)); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}, h^{1,1}, h^{2,0}] + + sage: X = delta_complexes.SurfaceOfGenus(2) + sage: H = X.cohomology_ring(QQ); H + Cohomology ring of Delta complex with 3 vertices and 29 simplices + over Rational Field + sage: sorted(H.basis(1), key=str) + [h^{1,0}, h^{1,1}, h^{1,2}, h^{1,3}] + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: x * y # alternate notation + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + Cohomology operations:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0]; y + h^{2,0} + sage: y.Sq(1) + h^{3,0} + + To compute the cohomology ring, the complex must be + "immutable". This is only relevant for simplicial complexes, + and most simplicial complexes are immutable, but certain + constructions make them mutable. The suspension is one + example, and this is the reason for calling + ``K.set_immutable()`` above. Another example:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: T = S1.product(S1) + sage: T.is_immutable() + False + sage: T.cohomology_ring() + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: T.set_immutable() + sage: T.cohomology_ring() + Cohomology ring of Simplicial complex with 9 vertices and + 18 facets over Rational Field + """ + from homology_vector_space_with_basis import CohomologyRing + if base_ring is None: + base_ring = QQ + return CohomologyRing(base_ring, self) + ############################################################ # end of chain complexes, homology ############################################################ @@ -784,3 +1047,133 @@ def _repr_(self): else: cells_string = " and 1 %s" % cell_name return Name + " complex " + vertex_string + cells_string + + +class Chains(CombinatorialFreeModule): + r""" + Class for the free module of chains and/or cochains in a given + degree. + + INPUT: + + - ``n_cells`` -- tuple of `n`-cells, which thus forms a basis for + this module + - ``base_ring`` -- optional (default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + One difference between chains and cochains is notation. In a + simplicial complex, for example, a simplex ``(0,1,2)`` is written + as "(0,1,2)" in the group of chains but as "\chi_(0,1,2)" in the + group of cochains. + + Also, since the free modules of chains and cochains are dual, + there is a pairing `\langle c, z \rangle`, sending a cochain `c` + and a chain `z` to a scalar. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + """ + def __init__(self, n_cells, base_ring=None, cochains=False): + """ + EXAMPLES:: + + sage: T = cubical_complexes.Torus() + sage: T.n_chains(2, QQ) + Free module generated by {[1,1] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,1] x [1,1], [0,0] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,0] x [0,1], [0,1] x [1,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [0,0], + [0,1] x [1,1] x [0,0] x [0,1], [0,0] x [0,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,1] x [0,0], [0,1] x [0,0] x [1,1] x [0,1], + [0,1] x [1,1] x [1,1] x [0,1], [0,1] x [0,0] x [0,1] x [1,1], + [1,1] x [0,1] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [1,1], + [0,1] x [1,1] x [0,1] x [1,1]} over Rational Field + sage: T.n_chains(2).dimension() + 16 + + TESTS:: + + sage: T.n_chains(2).base_ring() + Integer Ring + sage: T.n_chains(8).dimension() + 0 + sage: T.n_chains(-3).dimension() + 0 + """ + if base_ring is None: + base_ring=ZZ + self._cochains = cochains + if cochains: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='\\chi', bracket=['_', '']) + else: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='', bracket=False) + + class Element(CombinatorialFreeModuleElement): + + def eval(self, other): + """ + Evaluate this cochain on the chain ``other``. + + INPUT: + + - ``other`` -- a chain for the same cell complex in the + same dimension with the same base ring + + OUTPUT: scalar + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + + TESTS:: + + sage: z.eval(c) # z is not a cochain + Traceback (most recent call last): + ... + ValueError: this element is not a cochain + sage: c.eval(c) # can't evaluate a cochain on a cochain + Traceback (most recent call last): + ... + ValueError: the elements are not compatible + """ + if not self.parent()._cochains: + raise ValueError('this element is not a cochain') + if not (other.parent().indices() == self.parent().indices() + and other.base_ring() == self.base_ring() + and not other.parent()._cochains): + raise ValueError('the elements are not compatible') + result = sum(coeff * other.coefficient(cell) for cell, coeff in self) + return result + diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index 8951751e73d..d9544dceb41 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -114,7 +114,7 @@ def ChainComplex(data=None, **kwds): indexed. - ``degree_of_differential`` -- element of grading_group - (optional, default ``1``). The degree of the differential. + (optional, default ``1``). The degree of the differential. - ``degree`` -- alias for ``degree_of_differential``. @@ -457,7 +457,7 @@ def is_cycle(self): of the differentials. EXAMPLES:: - + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) sage: c = C({0:vector([0, 1, 2]), 1:vector([3, 4])}) sage: c.is_cycle() @@ -480,7 +480,7 @@ def is_boundary(self): the differentials. EXAMPLES:: - + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) sage: c = C({0:vector([0, 1, 2]), 1:vector([3, 4])}) sage: c.is_boundary() @@ -799,7 +799,7 @@ def ordered_degrees(self, start=None, exclude_first=False): are returned in sort order. EXAMPLES:: - + sage: one = matrix(ZZ, [[1]]) sage: D = ChainComplex({0: one, 2: one, 6:one}) sage: ascii_art(D) @@ -827,7 +827,6 @@ def ordered_degrees(self, start=None, exclude_first=False): return tuple(result) import collections - deg = start result = collections.deque() result.append(start) @@ -1025,24 +1024,49 @@ def _homology_chomp(self, deg, base_ring, verbose, generators): """ Helper function for :meth:`homology`. + INPUT: + + - ``deg`` -- integer (one specific homology group) or ``None`` + (all of those that can be non-zero) + + - ``base_ring`` -- the base ring (must be the integers + or a prime field) + + - ``verbose`` -- boolean, whether to print some messages + + - ``generators`` -- boolean, whether to also return generators + for homology + EXAMPLES:: sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}, base_ring=GF(2)) sage: C._homology_chomp(None, GF(2), False, False) # optional - CHomP {0: Vector space of dimension 2 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2} + + sage: D = ChainComplex({0: matrix(ZZ,1,0,[]), 1: matrix(ZZ,1,1,[0]), + ....: 2: matrix(ZZ,0,1,[])}) + sage: D._homology_chomp(None, GF(2), False, False) # optional - CHomP + {1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} """ from sage.interfaces.chomp import homchain - H = homchain(self, base_ring=base_ring, verbose=verbose, generators=generators) + H = homchain(self, base_ring=base_ring, verbose=verbose, + generators=generators) if H is None: raise RuntimeError('ran CHomP, but no output') if deg is None: - return H - try: + # all the homology groups that could be non-zero + # one has to complete the answer of chomp + result = H + for idx in self.nonzero_degrees(): + if not(idx in H): + result[idx] = HomologyGroup(0, base_ring) + return result + if deg in H: return H[deg] - except KeyError: + else: return HomologyGroup(0, base_ring) - @rename_keyword(deprecation=15151, dim='deg') def homology(self, deg=None, **kwds): r""" @@ -1054,7 +1078,7 @@ def homology(self, deg=None, **kwds): complex (default: ``None``); the degree in which to compute homology -- if this is ``None``, return the homology in every degree in which the chain complex is - possibly nonzero + possibly nonzero. - ``base_ring`` -- a commutative ring (optional, default is the base ring for the chain complex); must be either the @@ -1152,7 +1176,7 @@ def homology(self, deg=None, **kwds): sage: d2 = matrix(ZZ, 3,2, [[1,1], [1,-1], [-1,1]]) sage: C_k = ChainComplex({0:d0, 1:d1, 2:d2}, degree=-1) sage: C_k.homology(generators=true) # optional - CHomP - {0: (Z, [(1)]), 1: (Z x C2, [(0, 0, 1), (0, 1, -1)])} + {0: (Z, [(1)]), 1: (Z x C2, [(0, 0, 1), (0, 1, -1)]), 2: 0} From a torus using a field:: @@ -1167,7 +1191,7 @@ def homology(self, deg=None, **kwds): 2: [(Vector space of dimension 1 over Rational Field, Chain(2:(1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1)))]} """ - from sage.interfaces.chomp import have_chomp, homchain + from sage.interfaces.chomp import have_chomp if deg is not None and deg not in self.grading_group(): raise ValueError('degree is not an element of the grading group') @@ -1215,7 +1239,7 @@ def _homology_in_degree(self, deg, base_ring, verbose, generators, algorithm): else: return zero_homology if verbose: - print('Computing homology of the chain complex in dimension %s...' % n) + print('Computing homology of the chain complex in dimension %s...' % deg) fraction_field = base_ring.fraction_field() def change_ring(X): diff --git a/src/sage/homology/chain_complex_homspace.py b/src/sage/homology/chain_complex_homspace.py index d63f9e690e7..4bfc7e2da34 100644 --- a/src/sage/homology/chain_complex_homspace.py +++ b/src/sage/homology/chain_complex_homspace.py @@ -23,7 +23,9 @@ sage: i = H.identity() sage: x = i.associated_chain_complex_morphism(augmented=True) sage: x - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x._matrix_dictionary {-1: [1], 0: [1 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0] @@ -62,11 +64,15 @@ sage: i = A.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: y = x*4 sage: z = y*y sage: (y+z) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -93,7 +99,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.chain_complex_morphism as chain_complex_morphism +from sage.homology.chain_complex_morphism import ChainComplexMorphism def is_ChainComplexHomspace(x): """ @@ -142,4 +148,4 @@ def __call__(self, f): True """ - return chain_complex_morphism.ChainComplexMorphism(f, self.domain(), self.codomain()) + return ChainComplexMorphism(f, self.domain(), self.codomain()) diff --git a/src/sage/homology/chain_complex_morphism.py b/src/sage/homology/chain_complex_morphism.py index 57150ec7cca..5e79d25db3b 100644 --- a/src/sage/homology/chain_complex_morphism.py +++ b/src/sage/homology/chain_complex_morphism.py @@ -14,7 +14,6 @@ EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -27,7 +26,7 @@ sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -52,9 +51,10 @@ # #***************************************************************************** -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject -from sage.rings.integer_ring import ZZ +from sage.matrix.constructor import block_diagonal_matrix, zero_matrix +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.category_types import ChainComplexes def is_ChainComplexMorphism(x): """ @@ -71,14 +71,15 @@ def is_ChainComplexMorphism(x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x # indirect doctest - Chain complex morphism from Chain complex with at most 7 nonzero terms over - Integer Ring to Chain complex with at most 7 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 7 nonzero terms over Integer Ring + To: Chain complex with at most 7 nonzero terms over Integer Ring sage: is_ChainComplexMorphism(x) True """ return isinstance(x,ChainComplexMorphism) -class ChainComplexMorphism(SageObject): +class ChainComplexMorphism(Morphism): """ An element of this class is a morphism of chain complexes. """ @@ -88,7 +89,6 @@ def __init__(self, matrices, C, D, check=True): EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -101,9 +101,7 @@ def __init__(self, matrices, C, D, check=True): sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms - over Integer Ring to Chain complex with at most 2 nonzero terms over - Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -117,9 +115,22 @@ def __init__(self, matrices, C, D, check=True): sage: Y = simplicial_complexes.Simplex(0) sage: g = Hom(X,Y)({0:0, 1:0}) sage: g.associated_chain_complex_morphism() - Chain complex morphism from Chain complex with at most 2 nonzero - terms over Integer Ring to Chain complex with at most 1 nonzero terms - over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring + + Check that an error is raised if the matrices are the wrong size:: + + sage: C = ChainComplex({0: zero_matrix(ZZ, 0, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 0, 2)}) + sage: Hom(C,D)({0: matrix(1, 2, [1, 1])}) # 1x2 is the wrong size. + Traceback (most recent call last): + ... + ValueError: matrix in degree 0 is not the right size + sage: Hom(C,D)({0: matrix(2, 1, [1, 1])}) # 2x1 is right. + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring """ if not C.base_ring() == D.base_ring(): raise NotImplementedError('morphisms between chain complexes of different' @@ -140,14 +151,19 @@ def __init__(self, matrices, C, D, check=True): try: matrices[i] = initial_matrices.pop(i) except KeyError: - matrices[i] = matrix.zero_matrix(C.base_ring(), - D.differential(i).ncols(), - C.differential(i).ncols(), sparse=True) + matrices[i] = zero_matrix(C.base_ring(), + D.differential(i).ncols(), + C.differential(i).ncols(), sparse=True) if check: - # all remaining matrices given must be 0x0 + # All remaining matrices given must be 0x0. if not all(m.ncols() == m.nrows() == 0 for m in initial_matrices.values()): raise ValueError('the remaining matrices are not empty') - # check commutativity + # Check sizes of matrices. + for i in matrices: + if (matrices[i].nrows() != D.free_module_rank(i) or + matrices[i].ncols() != C.free_module_rank(i)): + raise ValueError('matrix in degree {} is not the right size'.format(i)) + # Check commutativity. for i in degrees: if i - d not in degrees: if not (C.free_module_rank(i) == D.free_module_rank(i) == 0): @@ -161,9 +177,117 @@ def __init__(self, matrices, C, D, check=True): mC = matrices[i+d] * C.differential(i) if mC != Dm: raise ValueError('matrices must define a chain complex morphism') - self._matrix_dictionary = matrices - self._domain = C - self._codomain = D + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + Morphism.__init__(self, Hom(C,D, ChainComplexes(C.base_ring()))) + + def in_degree(self, n): + """ + The matrix representing this morphism in degree n + + INPUT: + + - ``n`` -- degree + + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f.in_degree(0) + [1] + + Note that if the matrix is not specified in the definition of + the map, it is assumed to be zero:: + + sage: f.in_degree(2) + [] + sage: f.in_degree(2).nrows(), f.in_degree(2).ncols() + (1, 0) + sage: C.free_module(2) + Ambient free module of rank 0 over the principal ideal domain Integer Ring + sage: D.free_module(2) + Ambient free module of rank 1 over the principal ideal domain Integer Ring + """ + try: + return self._matrix_dictionary[n] + except KeyError: + rows = self.codomain().free_module_rank(n) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def to_matrix(self, deg=None): + """ + The matrix representing this chain map. + + If the degree ``deg`` is specified, return the matrix in that + degree; otherwise, return the (block) matrix for the whole + chain map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f.to_matrix(0) + [1] + sage: f.to_matrix() + [1|0|] + [-+-+] + [0|0|] + [-+-+] + [0|0|] + """ + if deg is not None: + return self.in_degree(deg) + row = 0 + col = 0 + blocks = [self._matrix_dictionary[n] + for n in sorted(self._matrix_dictionary.keys())] + return block_diagonal_matrix(blocks) + + def dual(self): + """ + The dual chain map to this one. + + That is, the map from the dual of the codomain of this one to + the dual of its domain, represented in each degree by the + transpose of the corresponding matrix. + + EXAMPLES:: + + sage: X = simplicial_complexes.Simplex(1) + sage: Y = simplicial_complexes.Simplex(0) + sage: g = Hom(X,Y)({0:0, 1:0}) + sage: f = g.associated_chain_complex_morphism() + sage: f.in_degree(0) + [1 1] + sage: f.dual() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: f.dual().in_degree(0) + [1] + [1] + sage: ascii_art(f.domain()) + [-1] + [ 1] + 0 <-- C_0 <----- C_1 <-- 0 + sage: ascii_art(f.dual().codomain()) + [-1 1] + 0 <-- C_1 <-------- C_0 <-- 0 + """ + matrix_dict = self._matrix_dictionary + matrices = {i: matrix_dict[i].transpose() for i in matrix_dict} + return ChainComplexMorphism(matrices, self.codomain().dual(), self.domain().dual()) def __neg__(self): """ @@ -196,7 +320,7 @@ def __neg__(self): f = dict() for i in self._matrix_dictionary.keys(): f[i] = -self._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __add__(self,x): """ @@ -226,12 +350,12 @@ def __add__(self,x): [0 0 0 2]} """ - if not isinstance(x,ChainComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): + if not isinstance(x,ChainComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): raise TypeError("Unsupported operation.") f = dict() for i in self._matrix_dictionary.keys(): f[i] = self._matrix_dictionary[i] + x._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __mul__(self,x): """ @@ -277,20 +401,48 @@ def __mul__(self,x): [0 0 4 0] [0 0 0 4]} + TESTS: + + Make sure that the product is taken in the correct order + (``self * x``, not ``x * self`` -- see :trac:`19065`):: + + sage: C = ChainComplex({0: zero_matrix(ZZ, 0, 2)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 0, 1)}) + sage: f = Hom(C,D)({0: matrix(1, 2, [1, 1])}) + sage: g = Hom(D,C)({0: matrix(2, 1, [1, 1])}) + sage: (f*g).in_degree(0) + [2] + + Before :trac:`19065`, the following multiplication produced a + ``KeyError`` because `f` was not explicitly defined in degree 2:: + + sage: C0 = ChainComplex({0: zero_matrix(ZZ, 0, 1)}) + sage: C1 = ChainComplex({1: zero_matrix(ZZ, 0, 1)}) + sage: C2 = ChainComplex({2: zero_matrix(ZZ, 0, 1)}) + sage: f = ChainComplexMorphism({}, C0, C1) + sage: g = ChainComplexMorphism({}, C1, C2) + sage: g * f + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring + sage: f._matrix_dictionary + {0: [], 1: []} + sage: g._matrix_dictionary + {1: [], 2: []} """ - if not isinstance(x,ChainComplexMorphism) or self._codomain != x._domain: + if not isinstance(x,ChainComplexMorphism) or self.domain() != x.codomain(): try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() - for i in self._matrix_dictionary.keys(): + for i in self._matrix_dictionary: f[i] = self._matrix_dictionary[i] * y - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) f = dict() - for i in self._matrix_dictionary.keys(): - f[i] = x._matrix_dictionary[i]*self._matrix_dictionary[i] - return ChainComplexMorphism(f,self._domain,x._codomain) + for i in self._matrix_dictionary: + f[i] = self._matrix_dictionary[i]*x.in_degree(i) + return ChainComplexMorphism(f,x.domain(),self.codomain()) def __rmul__(self,x): """ @@ -308,13 +460,13 @@ def __rmul__(self,x): False """ try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() for i in self._matrix_dictionary.keys(): f[i] = y * self._matrix_dictionary[i] - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) def __sub__(self,x): """ @@ -342,7 +494,6 @@ def __sub__(self,x): [0 0 0 0] [0 0 0 0] [0 0 0 0]} - """ return self + (-x) @@ -357,8 +508,9 @@ def __eq__(self,x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring + Chain complex morphism: + From: Trivial chain complex over Integer Ring + To: Trivial chain complex over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -367,13 +519,13 @@ def __eq__(self,x): True """ return isinstance(x,ChainComplexMorphism) \ - and self._codomain == x._codomain \ - and self._domain == x._domain \ + and self.codomain() == x.codomain() \ + and self.domain() == x.domain() \ and self._matrix_dictionary == x._matrix_dictionary - def _repr_(self): + def is_identity(self): """ - Return the string representation of ``self``. + True if this is the identity map. EXAMPLES:: @@ -381,12 +533,73 @@ def _repr_(self): sage: H = Hom(S,S) sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() - sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring - sage: x._repr_() - 'Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring' + sage: x.is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): """ - return "Chain complex morphism from {} to {}".format(self._domain, self._codomain) + True if this map is surjective. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_surjective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_surjective() + False + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_surjective() + True + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective. + + EXAMPLES:: + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_injective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_injective() + True + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_injective() + False + """ + return self.to_matrix().right_nullity() == 0 + + def __hash__(self): + """ + TESTS:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: hash(f) # random + 17 + """ + return hash(self.domain()) ^ hash(self.codomain()) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})._repr_type() + 'Chain complex' + """ + return "Chain complex" diff --git a/src/sage/homology/chain_homotopy.py b/src/sage/homology/chain_homotopy.py new file mode 100644 index 00000000000..11350ec6c43 --- /dev/null +++ b/src/sage/homology/chain_homotopy.py @@ -0,0 +1,600 @@ +# -*- coding: utf-8 -*- +r""" +Chain homotopies and chain contractions + +Chain homotopies are standard constructions in homological algebra: +given chain complexes `C` and `D` and chain maps `f, g: C \to D`, say +with differential of degree `-1`, a *chain homotopy* `H` between `f` and +`g` is a collection of maps `H_n: C_n \to D_{n+1}` satisfying + +.. MATH:: + + \partial_D H + H \partial_C = f - g. + +The presence of a chain homotopy defines an equivalence relation +(*chain homotopic*) on chain maps. If `f` and `g` are chain homotopic, +then one can show that `f` and `g` induce the same map on homology. + +Chain contractions are not as well known. The papers [M-AR]_, [RM-A]_, +and [PR]_ provide some references. Given two chain complexes `C` and +`D`, a *chain contraction* is a chain homotopy `H: C \to C` for which +there are chain maps `\pi: C \to D` ("projection") and `\iota: D \to +C` ("inclusion") such that + +- `H` is a chain homotopy between `1_C` and `\iota \pi`, +- `\pi \iota = 1_D`, +- `\pi H = 0`, +- `H \iota = 0`, +- `H H = 0`. + +Such a chain homotopy provides a strong relation between the chain +complexes `C` and `D`; for example, their homology groups are +isomorphic. + +REFERENCES: + +.. [M-AR] H. Molina-Abril and P. Réal, *Homology computation using spanning + trees* in Progress in Pattern Recognition, Image Analysis, + Computer Vision, and Applications, Lecture Notes in Computer + Science, volume 5856, pp 272-278, Springer, Berlin (2009). + +.. [PR] P. Pilarczyk and P. Réal, *Computation of cubical homology, + cohomology, and (co)homological operations via chain contraction*, + Adv. Comput. Math. 41 (2015), pp 253--275. + +.. [RM-A] P. Réal and H. Molina-Abril, *Cell AT-models for digital + volumes* in Torsello, Escolano, Brun (eds.), Graph-Based + Representations in Pattern Recognition, Lecture Notes in + Computer Science, volume 5534, pp. 314-3232, Springer, Berlin (2009). +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.homology.chain_complex_morphism import ChainComplexMorphism + +# In a perfect world, this would inherit from something like +# "TwoMorphism" rather than "Morphism"... +class ChainHomotopy(Morphism): + r""" + A chain homotopy. + + A chain homotopy `H` between chain maps `f, g: C \to D` is a sequence + of maps `H_n: C_n \to D_{n+1}` (if the chain complexes are graded + homologically) satisfying + + .. MATH:: + + \partial_D H + H \partial_C = f - g. + + INPUT: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``f`` -- chain map `C \to D` + - ``g`` (optional) -- chain map `C \to D` + + The dictionary ``matrices`` defines ``H`` by specifying the matrix + defining it in each degree: the entry `m` corresponding to key `i` + gives the linear transformation `C_i \to D_{i+1}`. + + If `f` is specified but not `g`, then `g` can be recovered from + the defining formula. That is, if `g` is not specified, then it + is defined to be `f - \partial_D H - H \partial_C`. + + Note that the degree of the differential on the chain complex `C` + must agree with that for `D`, and those degrees determine the + "degree" of the chain homotopy map: if the degree of the + differential is `d`, then the chain homotopy consists of a + sequence of maps `C_n \to C_{n-d}`. The keys in the dictionary + ``matrices`` specify the starting degrees. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + + Note that the maps `f` and `g` are stored in the attributes ``H._f`` + and ``H._g``:: + + sage: H._f + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: H._f.in_degree(0) + [1] + sage: H._g.in_degree(0) + [0] + + A non-example:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1)}, f, g) + Traceback (most recent call last): + ... + ValueError: the data do not define a valid chain homotopy + """ + def __init__(self, matrices, f, g=None): + r""" + Create a chain homotopy between the given chain maps + from a dictionary of matrices. + + EXAMPLES: + + If ``g`` is not specified, it is set equal to + `f - (H \partial + \partial H)`. :: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 1, 2, (1,0)), 2: matrix(ZZ, 2, 1, (0, 2))}, degree_of_differential=-1) + sage: D = ChainComplex({2: matrix(ZZ, 1, 1, (6,))}, degree_of_differential=-1) + sage: f_d = {1: matrix(ZZ, 1, 2, (0,3)), 2: identity_matrix(ZZ, 1)} + sage: f = Hom(C,D)(f_d) + sage: H_d = {0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 2, (2,2))} + sage: H = ChainHomotopy(H_d, f) + sage: H._g.in_degree(0) + [] + sage: H._g.in_degree(1) + [-13 -9] + sage: H._g.in_degree(2) + [-3] + + TESTS: + + Try to construct a chain homotopy in which the maps do not + have matching domains and codomains:: + + sage: g = Hom(C,C)({}) # the zero chain map + sage: H = ChainHomotopy(H_d, f, g) + Traceback (most recent call last): + ... + ValueError: the chain maps are not compatible + """ + domain = f.domain() + codomain = f.codomain() + deg = domain.degree_of_differential() + # Check that the chain complexes are compatible. This should + # never arise, because first there should be errors in + # constructing the chain maps. But just in case... + if domain.degree_of_differential() != codomain.degree_of_differential(): + raise ValueError('the chain complexes are not compatible') + if g is not None: + # Check that the chain maps are compatible. + if not (domain == g.domain() and codomain == + g.codomain()): + raise ValueError('the chain maps are not compatible') + # Check that the data define a chain homotopy. + for i in domain.differential(): + if i in matrices and i+deg in matrices: + if not (codomain.differential(i-deg) * matrices[i] + matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i in matrices: + if not (codomain.differential(i-deg) * matrices[i] == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i+deg in matrices: + if not (matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + else: + # Define g. + g_data = {} + for i in domain.differential(): + if i in matrices and i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) - codomain.differential(i-deg) * matrices[i] + elif i in matrices: + g_data[i] = f.in_degree(i) - codomain.differential(i-deg) * matrices[i] + elif i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) + g = ChainComplexMorphism(g_data, domain, codomain) + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + self._f = f + self._g = g + Morphism.__init__(self, Hom(domain, codomain)) + + def is_algebraic_gradient_vector_field(self): + r""" + An algebraic gradient vector field is a linear map + `H: C \to C` such that `H H = 0`. + + (Some authors also require that `H \partial H = H`, whereas + some make this part of the definition of "homology gradient + vector field. We have made the second choice.) See + Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_homology_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + The chain complex `C` is chain homotopy equivalent to a copy of + `\ZZ` in degree 0. Two chain maps `C \to C` will be chain + homotopic as long as they agree in degree 0. :: + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + True + + A chain homotopy which is not an algebraic gradient vector field:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + False + """ + if self.domain() != self.codomain(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i-deg in matrices: + if matrices[i-deg] * matrices[i] != 0: + return False + return True + + def is_homology_gradient_vector_field(self): + r""" + A homology gradient vector field is an algebraic gradient vector + field `H: C \to C` (i.e., a chain homotopy satisfying `H + H = 0`) such that `\partial H \partial = \partial` and `H + \partial H = H`. + + See Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_algebraic_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_homology_gradient_vector_field() + True + """ + if not self.is_algebraic_gradient_vector_field(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i+deg in matrices: + diff_i = self.domain().differential(i) + if diff_i * matrices[i+deg] * diff_i != diff_i: + return False + if matrices[i] * self.domain().differential(i-deg) * matrices[i] != matrices[i]: + return False + return True + + def in_degree(self, n): + """ + The matrix representing this chain homotopy in degree ``n``. + + INPUT: + + - ``n`` -- degree + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + + This returns an appropriately sized zero matrix if the chain + homotopy is not defined in degree n:: + + sage: H.in_degree(-3) + [] + """ + try: + return self._matrix_dictionary[n] + except KeyError: + from sage.matrix.constructor import zero_matrix + deg = self.domain().degree_of_differential() + rows = self.codomain().free_module_rank(n-deg) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def dual(self): + r""" + Dual chain homotopy to this one. + + That is, if this one is a chain homotopy between chain maps + `f, g: C \to D`, then its dual is a chain homotopy between the + dual of `f` and the dual of `g`, from `D^*` to `C^*`. It is + represented in each degree by the transpose of the + corresponding matrix. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + sage: H.dual().in_degree(0) + [3] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainHomotopy(matrices, self._f.dual(), self._g.dual()) + + def __hash__(self): + """ + TESTS:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: hash(H) # random + 314159265358979 + """ + return hash(self._f) ^ hash(self._g) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_(self): + """ + String representation + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + Chain homotopy between: + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + and Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + """ + s = 'Chain homotopy between:' + s += '\n {}'.format('\n '.join(self._f._repr_().split('\n'))) + s += '\n and {}'.format('\n '.join(self._g._repr_().split('\n'))) + return s + +class ChainContraction(ChainHomotopy): + r""" + A chain contraction. + + An algebraic gradient vector field `H: C \to C` (that is a chain + homotopy satisfying `H H = 0`) for which there are chain + maps `\pi: C \to D` ("projection") and `\iota: D \to C` + ("inclusion") such that + + - `H` is a chain homotopy between `1_C` and `\iota \pi`, + - `\pi \iota = 1_D`, + - `\pi H = 0`, + - `H \iota = 0`. + + ``H`` is defined by a dictionary ``matrices`` of matrices. + + INPUTS: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``pi`` -- a chain map `C \to D` + - ``iota`` -- a chain map `D \to C` + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, which is just + a copy of `\ZZ` in degree 0, and we construct a chain contraction:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + """ + def __init__(self, matrices, pi, iota): + r""" + Create a chain contraction from the given data. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, + which is just a copy of `\ZZ` in degree 0, and we try + construct a chain contraction, but get the map `\iota` wrong:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: zero_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the composite 'pi iota' is not the identity + + Another bad `\iota`:: + + sage: iota = pi # wrong domain, codomain + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the chain maps are not composable + + `\iota` is okay, but wrong data defining `H`:: + + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: not an algebraic gradient vector field + """ + from sage.matrix.constructor import identity_matrix + from chain_complex_morphism import ChainComplexMorphism + + if not (pi.domain() == iota.codomain() + and pi.codomain() == iota.domain()): + raise ValueError('the chain maps are not composable') + C = pi.domain() + D = pi.codomain() + base_ring = C.base_ring() + + # Check that the composite 'pi iota' is 1. + for i in D.nonzero_degrees(): + if pi.in_degree(i) * iota.in_degree(i) != identity_matrix(base_ring, D.free_module_rank(i)): + raise ValueError("the composite 'pi iota' is not the identity") + + # Construct the chain map 'id_C'. + id_C_dict = {} + for i in C.nonzero_degrees(): + id_C_dict[i] = identity_matrix(base_ring, C.free_module_rank(i)) + id_C = ChainComplexMorphism(id_C_dict, C, C) + + # Now check that + # - `H` is a chain homotopy between `id_C` and `\iota \pi` + # - `HH = 0` + ChainHomotopy.__init__(self, matrices, id_C, iota * pi) + if not self.is_algebraic_gradient_vector_field(): + raise ValueError('not an algebraic gradient vector field') + # Check that `\pi H = 0`: + deg = C.degree_of_differential() + for i in matrices: + if pi.in_degree(i-deg) * matrices[i] != 0: + raise ValueError('the data do not define a valid chain contraction: pi H != 0') + # Check that `H \iota = 0`: + for i in iota._matrix_dictionary: + if i in matrices: + if matrices[i] * iota.in_degree(i) != 0: + raise ValueError('the data do not define a valid chain contraction: H iota != 0') + self._pi = pi + self._iota = iota + + def pi(self): + r""" + The chain map `\pi` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + sage: phi.pi().in_degree(0) # Every vertex represents a homology class. + [1 1 1 1] + sage: phi.pi().in_degree(1) # No homology in degree 1. + [] + + The degree 2 homology generator is detected on a single simplex:: + + sage: phi.pi().in_degree(2) + [0 0 0 1] + """ + return self._pi + + def iota(self): + r""" + The chain map `\iota` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + + Lifting the degree two homology class gives the signed sum of + all of the 2-simplices:: + + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + """ + return self._iota + + def dual(self): + """ + The chain contraction dual to this one. + + This is useful when switching from homology to cohomology. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex, + but the degree zero cohomology class needs to be detected on + every vertex, and vice versa for degree 2:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + [1] + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + sage: phi.dual().iota().in_degree(2) + [0] + [0] + [0] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainContraction(matrices, self.iota().dual(), self.pi().dual()) + diff --git a/src/sage/homology/cubical_complex.py b/src/sage/homology/cubical_complex.py index c0dc67555ea..07e5e3a19e5 100644 --- a/src/sage/homology/cubical_complex.py +++ b/src/sage/homology/cubical_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Finite cubical complexes @@ -547,6 +548,67 @@ def _triangulation_(self): simplices.append(S.join(Simplex((v,)), rename_vertices=False)) return simplices + def alexander_whitney(self, dim): + r""" + Subdivide this cube into pairs of cubes. + + This provides a cubical approximation for the diagonal map + `K \to K \times K`. + + INPUT: + + - ``dim`` -- integer between 0 and one more than the + dimension of this cube + + OUTPUT: + + - a list containing triples ``(coeff, left, right)`` + + This uses the algorithm described by Pilarczyk and Réal [PR]_ + on p. 267; the formula is originally due to Serre. Calling + this method ``alexander_whitney`` is an abuse of notation, + since the actual Alexander-Whitney map goes from `C(K \times + L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated + chain complex, but this subdivision of cubes is at the heart + of it. + + EXAMPLES:: + + sage: from sage.homology.cubical_complex import Cube + sage: C1 = Cube([[0,1], [3,4]]) + sage: C1.alexander_whitney(0) + [(1, [0,0] x [3,3], [0,1] x [3,4])] + sage: C1.alexander_whitney(1) + [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])] + sage: C1.alexander_whitney(2) + [(1, [0,1] x [3,4], [1,1] x [4,4])] + """ + from sage.sets.set import Set + N = Set(self.nondegenerate_intervals()) + result = [] + for J in N.subsets(dim): + Jprime = N.difference(J) + nu = 0 + for i in J: + for j in Jprime: + if j +# +# 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/ +######################################################################## + +# To do: implement morphisms of cubical complexes, with methods +# - domain +# - codomain +# - associated_chain_complex_morphism +# Once this is done, the code here ought to work without modification. + +from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis +from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.rings.rational_field import QQ + +class InducedHomologyMorphism(Morphism): + r""" + An element of this class is a morphism of (co)homology groups + induced by a map of simplicial complexes. It requires working + with field coefficients. + + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + .. note:: + + This is not intended to be used directly by the user, but instead + via the method + :meth:`~sage.homology.simplicial_complex_morphism.SimplicialComplexMorphism.induced_homology_morphism`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: f = H({0:0, 1:2, 2:1}) # f switches two vertices + sage: f_star = f.induced_homology_morphism(QQ, cohomology=True) + sage: f_star + Graded algebra endomorphism of Cohomology ring of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + Defn: induced by: + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + Defn: 0 |--> 0 + 1 |--> 2 + 2 |--> 1 + sage: f_star.to_matrix(1) + [-1] + sage: f_star.to_matrix() + [ 1| 0] + [--+--] + [ 0|-1] + + sage: T = simplicial_complexes.Torus() + sage: y = T.homology_with_basis(QQ).basis()[(1,1)] + sage: y.to_cycle() + (0, 3) - (0, 6) + (3, 6) + + Since `(0,3) - (0,6) + (3,6)` is a cycle representing a homology + class in the torus, we can define a map `S^1 \to T` inducing an + inclusion on `H_1`:: + + sage: Hom(S1, T)({0:0, 1:3, 2:6}) + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 14 facets + Defn: 0 |--> 0 + 1 |--> 3 + 2 |--> 6 + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g_star.to_matrix(0) + [1] + sage: g_star.to_matrix(1) + [0] + [1] + sage: g_star.to_matrix() + [1|0] + [-+-] + [0|0] + [0|1] + [-+-] + [0|0] + + We can evaluate such a map on (co)homology classes:: + + sage: H = S1.homology_with_basis(QQ) + sage: a = H.basis()[(1,0)] + sage: g_star(a) + h_{1,1} + + sage: T = S1.product(S1, is_mutable=False) + sage: diag = Hom(S1,T).diagonal_morphism() + sage: b,c = list(T.cohomology_ring().basis(1)) + sage: diag_c = diag.induced_homology_morphism(cohomology=True) + sage: diag_c(b) + h^{1,0} + sage: diag_c(c) + h^{1,0} + """ + def __init__(self, map, base_ring=None, cohomology=False): + """ + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + EXAMPLES:: + + sage: from sage.homology.homology_morphism import InducedHomologyMorphism + sage: K = simplicial_complexes.RandomComplex(8, 3) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: f = InducedHomologyMorphism(id, QQ) + sage: f.to_matrix(0) == 1 and f.to_matrix(1) == 1 and f.to_matrix(2) == 1 + True + sage: f = InducedHomologyMorphism(id, ZZ) + Traceback (most recent call last): + ... + ValueError: the coefficient ring must be a field + sage: S1 = simplicial_complexes.Sphere(1).barycentric_subdivision() + sage: S1.is_mutable() + True + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S1.set_immutable() + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + """ + if map.domain().is_mutable() or map.codomain().is_mutable(): + raise ValueError('the domain and codomain complexes must be immutable') + if base_ring is None: + base_ring = QQ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + self._cohomology = cohomology + self._map = map + self._base_ring = base_ring + if cohomology: + domain = map.domain().cohomology_ring(base_ring=base_ring) + codomain = map.codomain().cohomology_ring(base_ring=base_ring) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedAlgebrasWithBasis(base_ring))) + else: + domain = map.domain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + codomain = map.codomain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedModulesWithBasis(base_ring))) + + def base_ring(self): + """ + The base ring for this map + + EXAMPLES:: + + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: id.induced_homology_morphism(QQ).base_ring() + Rational Field + sage: id.induced_homology_morphism(GF(13)).base_ring() + Finite Field of size 13 + """ + return self._base_ring + + def to_matrix(self, deg=None): + """ + The matrix for this map. + + If degree ``deg`` is specified, return the matrix just in that + degree; otherwise, return the block matrix representing the + entire map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1_b = S1.barycentric_subdivision() + sage: S1_b.set_immutable() + sage: d = {(0,): 0, (0,1): 1, (1,): 2, (1,2): 0, (2,): 1, (0,2): 2} + sage: f = Hom(S1_b, S1)(d) + sage: h = f.induced_homology_morphism(QQ) + sage: h.to_matrix(1) + [2] + sage: h.to_matrix() + [1|0] + [-+-] + [0|2] + """ + base_ring = self.base_ring() + if self._cohomology: + domain = self._map.codomain() + codomain = self._map.domain() + else: + domain = self._map.domain() + codomain = self._map.codomain() + phi_codomain, H_codomain = codomain.algebraic_topological_model(base_ring) + phi_domain, H_domain = domain.algebraic_topological_model(base_ring) + mat = phi_codomain.pi().to_matrix(deg) * self._map.associated_chain_complex_morphism(self.base_ring(), cochain=self._cohomology).to_matrix(deg) * phi_domain.iota().to_matrix(deg) + if deg is None: + import numpy as np + betti_domain = [H_domain.free_module_rank(n) + for n in range(domain.dimension()+1)] + betti_codomain = [H_codomain.free_module_rank(n) + for n in range(codomain.dimension()+1)] + # Compute cumulative sums of Betti numbers to get subdivisions: + row_subdivs = list(np.cumsum(betti_codomain[:-1])) + col_subdivs = list(np.cumsum(betti_domain[:-1])) + mat.subdivide(row_subdivs, col_subdivs) + return mat + + def __call__(self, elt): + """ + Evaluate this map on ``elt``, an element of (co)homology. + + INPUT: + + - ``elt`` -- informally, an element of the domain of this + map. More formally, an element of + :class:`homology_vector_space_with_basis.HomologyVectorSpaceWithBasis`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: f = {0:0, 1:2, 2:1} + sage: H = Hom(S1,S1) + sage: g = H(f) + sage: h = g.induced_homology_morphism(QQ) + sage: x = S1.homology_with_basis().basis()[(1,0)] + sage: x + h_{1,0} + sage: h(x) # indirect doctest + -h_{1,0} + """ + base_ring = self.base_ring() + if self._cohomology: + codomain = self._map.domain().homology_with_basis(base_ring, cohomology=True) + if elt.parent().complex() != self._map.codomain(): + raise ValueError('element is not a cohomology class for the correct complex') + else: + codomain = self._map.codomain().homology_with_basis(base_ring) + if elt.parent().complex() != self._map.domain(): + raise ValueError('element is not a homology class for the correct complex') + + return codomain.from_vector(self.to_matrix() * elt.to_vector()) + + def __eq__(self, other): + """ + Return ``True`` if and only if this map agrees with ``other``. + + INPUTS: + + - ``other`` -- another induced homology morphism + + This automatically returns ``False`` if the morphisms have + different domains, codomains, base rings, or values for their + cohomology flags + + Otherwise, determine this by computing the matrices for this + map and ``other`` using the (same) basis for the homology + vector spaces. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: g = Hom(S1, K)({0: 0, 1:0, 2:0}) + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(QQ) + True + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(GF(2)) + False + sage: id = Hom(K, K).identity() # different domain + sage: f.induced_homology_morphism(QQ) == id.induced_homology_morphism(QQ) + False + """ + if (self._map.domain() != other._map.domain() + or self._map.codomain() != other._map.codomain() + or self.base_ring() != other.base_ring() + or self._cohomology != other._cohomology): + return False + dim = min(self._map.domain().dimension(), self._map.codomain().dimension()) + return all(self.to_matrix(d) == other.to_matrix(d) for d in range(dim+1)) + + def is_identity(self): + """ + True if this is the identity map on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.induced_homology_morphism(QQ).is_identity() + False + sage: flip.induced_homology_morphism(GF(2)).is_identity() + True + sage: rotate = H({0:1, 1:2, 2:0}) + sage: rotate.induced_homology_morphism(QQ).is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): + """ + True if this map is surjective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_surjective() + True + sage: f.induced_homology_morphism(cohomology=True).is_surjective() + False + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_injective() + False + sage: f.induced_homology_morphism(cohomology=True).is_injective() + True + + sage: T = simplicial_complexes.Torus() + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g.is_injective() + True + """ + return self.to_matrix().right_nullity() == 0 + + def _repr_type(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: f.induced_homology_morphism()._repr_type() + 'Graded vector space' + sage: f.induced_homology_morphism(cohomology=True)._repr_type() + 'Graded algebra' + """ + return "Graded vector space" if not self._cohomology else "Graded algebra" + + def _repr_defn(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: print f.induced_homology_morphism()._repr_defn() + induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + """ + s = "induced by:" + s += '\n {}'.format('\n '.join(self._map._repr_().split('\n'))) + return s diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py new file mode 100644 index 00000000000..15c42394f4c --- /dev/null +++ b/src/sage/homology/homology_vector_space_with_basis.py @@ -0,0 +1,834 @@ +# -*- coding: utf-8 -*- +""" +Homology and cohomology with a basis + +This module provides homology and cohomology vector spaces suitable +for computing cup products and cohomology operations. + +REFERENCES: + +.. [G-DR03] R. González-Díaz and P. Réal, *Computation of cohomology + operations on finite simplicial complexes* in Homology, + Homotopy and Applications 5 (2003), 83-93. + +.. [G-DR99] R. González-Díaz and P. Réal, *A combinatorial method for + computing Steenrod squares* in J. Pure Appl. Algebra 139 (1999), 89-108. + +AUTHORS: + +- John H. Palmieri, Travis Scrimshaw (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.misc.cachefunc import cached_method +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.sets.family import Family +from simplicial_complex import SimplicialComplex + +class HomologyVectorSpaceWithBasis(CombinatorialFreeModule): + r""" + Homology (or cohomology) vector space. + + This provides enough structure to allow the computation of cup + products and cohomology operations. See the class + :class:`CohomologyRing` (which derives from this) for examples. + + It also requires field coefficients (hence the "VectorSpace" in + the name of the class). + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the methods + :meth:`~sage.homology.cell_complex.GenericCellComplex.homology_with_basis` and + :meth:`~sage.homology.cell_complex.GenericCellComplex.cohomology_ring` + for the class of :class:`cell + complexes`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + - ``cohomology`` -- (default: ``False``) if ``True``, return + the cohomology as a module + - ``category`` -- (optional) a subcategory of modules with basis + + EXAMPLES: + + Homology classes are denoted by ``h_{d,i}`` where ``d`` is the + degree of the homology class and ``i`` is their index in the list + of basis elements in that degree. Cohomology classes are denoted + ``h^{1,0}``:: + + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: RP2.homology_with_basis(GF(2)) + Homology module of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: RP2.cohomology_ring(GF(2)) + Cohomology ring of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + + To access a basis element, use its degree and index (0 or 1 in the 1st + cohomology group of a torus):: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.basis(1) + Finite family {(1, 0): h^{1,0}, (1, 1): h^{1,1}} + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + sage: 2*x-3*y + 2*h^{1,0} - 3*h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + This works with simplicial, cubical, and `\Delta`-complexes:: + + sage: Klein_c = cubical_complexes.KleinBottle() + sage: H = Klein_c.cohomology_ring(GF(2)) + sage: x,y = H.basis(1) + sage: x.cup_product(x) + h^{2,0} + sage: x.cup_product(y) + 0 + sage: y.cup_product(y) + h^{2,0} + + sage: Klein_d = delta_complexes.KleinBottle() + sage: H = Klein_d.cohomology_ring(GF(2)) + sage: u,v = H.basis(1) + sage: u.cup_product(u) + h^{2,0} + sage: u.cup_product(v) + 0 + sage: v.cup_product(v) + h^{2,0} + + The basis elements in the simplicial complex case have been chosen + differently; apply the change of basis `x \mapsto a + b`, `y \mapsto + b` to see the same product structure. :: + + sage: Klein_s = simplicial_complexes.KleinBottle() + sage: H = Klein_s.cohomology_ring(GF(2)) + sage: a,b = H.basis(1) + sage: a.cup_product(a) + 0 + sage: a.cup_product(b) + h^{2,0} + sage: (a+b).cup_product(a+b) + h^{2,0} + sage: b.cup_product(b) + h^{2,0} + """ + def __init__(self, base_ring, cell_complex, cohomology=False, cat=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: TestSuite(H).run() + sage: H = RP2.homology_with_basis(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + sage: H = simplicial_complexes.ComplexProjectivePlane().cohomology_ring() + sage: TestSuite(H).run() + """ + # phi is the associated chain contraction. + # M is the homology chain complex. + phi, M = cell_complex.algebraic_topological_model(base_ring) + if cohomology: + phi = phi.dual() + # We only need the rank of M in each degree, and since + # we're working over a field, we don't need to dualize M + # if working with cohomology. + cat = Modules(base_ring).WithBasis().Graded().or_subcategory(cat) + self._contraction = phi + self._complex = cell_complex + self._cohomology = cohomology + self._graded_indices = {deg: range(M.free_module_rank(deg)) + for deg in range(cell_complex.dimension()+1)} + indices = [(deg, i) for deg in self._graded_indices + for i in self._graded_indices[deg]] + CombinatorialFreeModule.__init__(self, base_ring, indices, category=cat) + + def basis(self, d=None): + """ + Return (the degree ``d`` homogeneous component of) the basis + of this graded vector space. + + INPUT: + + - ``d`` -- (optional) the degree + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: H.basis() + Finite family {(0, 0): h_{0,0}} + sage: H.basis(0) + Finite family {(0, 0): h_{0,0}} + sage: H.basis(1) + Finite family {} + sage: H.basis(2) + Finite family {} + """ + if d is None: + return Family(self._indices, self.monomial) + else: + indices = [(d, i) for i in self._graded_indices.get(d, [])] + return Family(indices, self.monomial) + + def degree_on_basis(self, i): + r""" + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(GF(7)) + sage: H.degree_on_basis((2,0)) + 2 + """ + return i[0] + + def contraction(self): + r""" + The chain contraction associated to this homology computation. + + That is, to work with chain representatives of homology + classes, we need the chain complex `C` associated to the cell + complex, the chain complex `H` of its homology (with trivial + differential), chain maps `\pi: C \to H` and `\iota: H \to C`, + and a chain contraction `\phi` giving a chain homotopy between + `1_C` and `\iota \circ \pi`. + + OUTPUT: `\phi` + + See :class:`~sage.homology.chain_homotopy.ChainContraction` for information + about chain contractions, and see + :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model` + for the construction of this particular chain contraction `\phi`. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.contraction() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + + From the chain contraction, one can also recover the maps `\pi` + and `\iota`:: + + sage: phi = H.contraction() + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 1 nonzero terms over Rational Field + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + """ + return self._contraction + + def complex(self): + """ + The cell complex whose homology is being computed. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.complex() + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + """ + return self._complex + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + if self._cohomology: + base = "Cohomology" + else: + base = "Homology" + return base + " module of {} over {}".format(self._complex, self.base_ring()) + + def _repr_term(self, i): + """ + Return ``'h_{i[0],i[1]}'`` for homology, ``'h^{i[0],i[1]}'`` for + cohomology, for the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(QQ) + sage: H.basis()[1,0] # indirect doctest + h_{1,0} + sage: latex(H.basis()[1,1]) # indirect doctest + h_{1,1} + sage: co = simplicial_complexes.KleinBottle().cohomology_ring(GF(2)) + sage: co.basis()[1,0] # indirect doctest + h^{1,0} + + """ + sym = '^' if self._cohomology else '_' + return 'h{}{{{},{}}}'.format(sym, i[0], i[1]) + + _latex_term = _repr_term + + @cached_method + def _to_cycle_on_basis(self, i): + """ + Return the (co)cycle representative of the basis element + indexed by ``i``. + + .. SEEALSO:: + + :meth:`HomologyVectorSpaceWithBasis.Element.to_cocycle` + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: H._to_cycle_on_basis((2,0)) + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((2,0)) + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((0,0)) + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: H._to_cycle_on_basis((0,0)) + \chi_(1,) + \chi_(2,) + \chi_(3,) + \chi_(4,) + \chi_(5,) + \chi_(6,) + + \chi_(7,) + \chi_(8,) + \chi_(9,) + \chi_(10,) + \chi_(11,) + sage: H._to_cycle_on_basis((1,0)) + \chi_(1, 2) + \chi_(1, 3) + \chi_(1, 4) + \chi_(1, 7) + + \chi_(1, 10) + \chi_(2, 4) + \chi_(2, 6) + \chi_(2, 9) + + \chi_(2, 10) + \chi_(2, 11) + \chi_(3, 4) + \chi_(3, 5) + + \chi_(3, 11) + \chi_(4, 8) + \chi_(4, 9) + \chi_(5, 9) + + \chi_(5, 10) + \chi_(7, 9) + \chi_(8, 10) + sage: H._to_cycle_on_basis((2,0)) + \chi_(2, 3, 8) + \chi_(2, 7, 8) + \chi_(3, 4, 8) + \chi_(3, 5, 9) + + \chi_(3, 6, 7) + \chi_(3, 6, 8) + \chi_(3, 6, 10) + + \chi_(3, 8, 9) + \chi_(3, 9, 10) + \chi_(4, 5, 7) + + \chi_(4, 5, 9) + \chi_(5, 6, 7) + \chi_(5, 7, 8) + sage: H._to_cycle_on_basis((3,0)) + \chi_(3, 4, 5, 9) + """ + vec = self.contraction().iota().in_degree(i[0]).column(i[1]) + chains = self.complex().n_chains(i[0], self.base_ring(), + cochains=self._cohomology) + return chains.from_vector(vec) + + class Element(CombinatorialFreeModuleElement): + def to_cycle(self): + r""" + (Co)cycle representative of this homogeneous (co)homology class. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: h20 = H.basis()[2,0]; h20 + h_{2,0} + sage: h20.to_cycle() + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + Chains are written as linear combinations of simplices + `\sigma`. Cochains are written as linear combinations of + characteristic functions `\chi_{\sigma}` for those + simplices:: + + sage: S2.cohomology_ring(QQ).basis()[2,0].to_cycle() + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ).basis()[0,0].to_cycle() + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + """ + if not self.is_homogeneous(): + raise ValueError("only defined for homogeneous elements") + return sum(c * self.parent()._to_cycle_on_basis(i) for i,c in self) + +class CohomologyRing(HomologyVectorSpaceWithBasis): + """ + The cohomology ring. + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the + :meth:`cohomology ring` + of a :class:`cell + complex`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + + EXAMPLES:: + + sage: CP2 = simplicial_complexes.ComplexProjectivePlane() + sage: H = CP2.cohomology_ring(QQ) + sage: H.basis(2) + Finite family {(2, 0): h^{2,0}} + sage: x = H.basis(2)[2,0] + + The product structure is the cup product:: + + sage: x.cup_product(x) + h^{4,0} + sage: x * x + h^{4,0} + + There are mod 2 cohomology operations defined, also:: + + sage: Hmod2 = CP2.cohomology_ring(GF(2)) + sage: y = Hmod2.basis(2)[2,0] + sage: y.Sq(2) + h^{4,0} + """ + def __init__(self, base_ring, cell_complex): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + """ + cat = Algebras(base_ring).WithBasis().Graded() + HomologyVectorSpaceWithBasis.__init__(self, base_ring, cell_complex, True, cat) + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().cohomology_ring(QQ) + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + return "Cohomology ring of {} over {}".format(self._complex, self.base_ring()) + + @cached_method + def one(self): + """ + The multiplicative identity element. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.one() + h^{0,0} + sage: all(H.one() * x == x == x * H.one() for x in H.basis()) + True + """ + one = self.base_ring().one() + d = {(0,i): one for i in self._graded_indices[0]} + return self._from_dict(d, remove_zeros=False) + + @cached_method + def product_on_basis(self, li, ri): + r""" + The cup product of the basis elements indexed by ``li`` and ``ri`` + in this cohomology ring. + + INPUT: + + - ``li``, ``ri`` -- index of a cohomology class + + .. SEEALSO:: + + :meth:`CohomologyRing.Element.cup_product` -- the + documentation for this method describes the algorithm. + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c).cup_product(c) # indirect doctest + h^{3,0} + + sage: T = simplicial_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + h^{2,0} + sage: x.cup_product(x) + 0 + + sage: one = T.cohomology_ring(QQ).basis()[0,0] + sage: x.cup_product(one) + h^{1,0} + sage: one.cup_product(y) == y + True + sage: one.cup_product(one) + h^{0,0} + sage: x.cup_product(y) + y.cup_product(x) + 0 + + This also works with cubical complexes:: + + sage: T = cubical_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + -h^{2,0} + sage: x.cup_product(x) + 0 + + and `\Delta`-complexes:: + + sage: T_d = delta_complexes.Torus() + sage: a,b = T_d.cohomology_ring(QQ).basis(1) + sage: a.cup_product(b) + h^{2,0} + sage: b.cup_product(a) + -h^{2,0} + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: w = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: w.cup_product(w) + h^{2,0} + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Torus()) + sage: a,b,c,d = K.cohomology_ring(QQ).basis(1) + sage: x,y = K.cohomology_ring(QQ).basis(0) + sage: a.cup_product(x) == a + True + sage: a.cup_product(y) + 0 + """ + B = self.basis() + scomplex = self.complex() + base_ring = self.base_ring() + deg_left = li[0] + deg_right = ri[0] + deg_tot = deg_left + deg_right + left_cycle = self._to_cycle_on_basis(li) + right_cycle = self._to_cycle_on_basis(ri) + n_chains_left = scomplex.n_chains(deg_left, base_ring) + n_chains_right = scomplex.n_chains(deg_right, base_ring) + + result = {} + H = scomplex.homology_with_basis(base_ring) + for gamma_index in H._graded_indices.get(deg_tot, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((deg_tot, gamma_index)): + if hasattr(cell, 'alexander_whitney'): + # Simplicial and cubical case: each cell has a + # method 'alexander_whitney' which computes + # the appropriate faces. + for (c, left_cell, right_cell) in cell.alexander_whitney(deg_left): + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += c * coeff * left_cycle.eval(left) * right_cycle.eval(right) + else: + # Delta complex case: each "cell" in n_chains + # is just a pair (integer, tuple), where the + # integer is its index in the list, and the + # jth entry of the tuple is the index of its + # jth face in the list of (n-1)-chains. Use + # this data to compute the appropriate faces + # by hand. + left_cell = cell + for i in range(deg_tot, deg_left, -1): + idx = left_cell[1][i] + left_cell = (idx, scomplex.n_cells(i-1)[idx]) + right_cell = cell + for i in range(deg_tot, deg_right, -1): + idx = right_cell[1][0] + right_cell = (idx, scomplex.n_cells(i-1)[idx]) + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += coeff * left_cycle.eval(left) * right_cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(deg_tot, gamma_index)] = gamma_coeff + return self._from_dict(result, remove_zeros=False) + + class Element(HomologyVectorSpaceWithBasis.Element): + def cup_product(self, other): + r""" + Return the cup product of this element and ``other``. + + Algorithm: see González-Díaz and Réal [G-DR03]_, p. 88. + Given two cohomology classes, lift them to cocycle + representatives via the chain contraction for this + complex, using + :meth:`~HomologyVectorSpaceWithBasis.Element.to_cycle`. In + the sum of their dimensions, look at all of the homology + classes `\gamma`: lift each of those to a cycle + representative, apply the Alexander-Whitney diagonal map + to each cell in the cycle, evaluate the two cocycles on + these factors, and multiply. The result is the value of + the cup product cocycle on this homology class. After this + has been done for all homology classes, since homology and + cohomology are dual, one can tell which cohomology class + corresponds to the cup product. + + .. SEEALSO:: + + :meth:`CohomologyRing.product_on_basis` + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c) + h^{2,0} + sage: c * c * c + h^{3,0} + + We can also take powers:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: a = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: a**0 + h^{0,0} + sage: a**1 + h^{1,0} + sage: a**2 + h^{2,0} + sage: a**3 + 0 + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Sphere(2)) + sage: a,b = K.cohomology_ring(QQ).basis(2) + sage: a**0 + h^{0,0} + h^{0,1} + + """ + return self * other + + def Sq(self, i): + r""" + Return the result of applying `Sq^i` to this element. + + INPUT: + + - ``i`` -- nonnegative integer + + .. WARNING:: + + This is only implemented for simplicial complexes. + + This cohomology operation is only defined in + characteristic 2. + + Algorithm: see González-Díaz and Réal [G-DR99]_, + Corollary 3.2. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectiveSpace(2) + sage: x = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + h^{2,0} + + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0] + sage: y.Sq(1) + h^{3,0} + + sage: RP4 = simplicial_complexes.RealProjectiveSpace(4) + sage: H = RP4.cohomology_ring(GF(2)) + sage: x = H.basis()[1,0] + sage: y = H.basis()[2,0] + sage: z = H.basis()[3,0] + sage: x.Sq(1) == y + True + sage: z.Sq(1) # long time + h^{4,0} + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: x = T.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + NotImplementedError: Steenrod squares are only implemented for simplicial complexes + sage: S2 = simplicial_complexes.Sphere(2) + sage: x = S2.cohomology_ring(GF(7)).basis()[2,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + ValueError: Steenrod squares are only defined in characteristic 2 + """ + P = self.parent() + scomplex = P.complex() + if not isinstance(scomplex, SimplicialComplex): + raise NotImplementedError('Steenrod squares are only implemented for simplicial complexes') + base_ring = P.base_ring() + if base_ring.characteristic() != 2: + raise ValueError('Steenrod squares are only defined in characteristic 2') + # We keep the same notation as in [G-DR99]. + # The trivial cases: + if i == 0: + # Sq^0 is the identity. + return self + + # Construct each graded component of ``self`` + ret = P.zero() + H = scomplex.homology_with_basis(base_ring) + deg_comp = {} + for index,coeff in self: + d = deg_comp.get(index[0], {}) + d[index] = coeff + deg_comp[index[0]] = d + + # Do the square on each graded componenet of ``self``. + for j in deg_comp: + # Make it into an actual element + m = j + i + if not P._graded_indices.get(m, []) or i > j: + continue + elt = P._from_dict(deg_comp[j], remove_zeros=False) + if i == j: + ret += elt.cup_product(elt) + continue + + n = j - i + # Now assemble the indices over which the sums take place. + # S(n) is defined to be floor((m+1)/2) + floor(n/2). + S_n = (m+1) // 2 + n // 2 + if n == 0: + sums = [[S_n]] + else: + sums = [[i_n] + l for i_n in range(S_n, m+1) + for l in sum_indices(n-1, i_n, S_n)] + # At this point, 'sums' is a list of lists of the form + # [i_n, i_{n-1}, ..., i_0]. (It is reversed from the + # obvious order because this is closer to the order in + # which the face maps will be applied.) Now we sum over + # these, according to the formula in [G-DR99], Corollary 3.2. + result = {} + cycle = elt.to_cycle() + n_chains = scomplex.n_chains(j, base_ring) + for gamma_index in H._graded_indices.get(m, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((m, gamma_index)): + for indices in sums: + indices = list(indices) + left = cell + right = cell + # Since we are working with a simplicial complex, 'cell' is a simplex. + if not m % 2: + left_endpoint = m + while indices: + right_endpoint = indices[0] - 1 + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + try: + left_endpoint = indices[0] - 1 + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + except IndexError: + pass + for k in range(right_endpoint, -1, -1): + right = right.face(k) + else: + right_endpoint = m + while indices: + left_endpoint = indices[0] - 1 + try: + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + right_endpoint = indices[0] - 1 + except IndexError: + pass + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + for k in range(right_endpoint, -1, -1): + right = right.face(k) + + left = n_chains(left) + right = n_chains(right) + gamma_coeff += coeff * cycle.eval(left) * cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(m, gamma_index)] = gamma_coeff + ret += P._from_dict(result, remove_zeros=False) + return ret + +def sum_indices(k, i_k_plus_one, S_k_plus_one): + r""" + This is a recursive function for computing the indices for the + nested sums in González-Díaz and Réal [G-DR99]_, Corollary 3.2. + + In the paper, given indices `i_n`, `i_{n-1}`, ..., `i_{k+1}`, + given `k`, and given `S(k+1)`, the number `S(k)` is defined to be + + .. MATH:: + + S(k) = -S(k+1) + floor(k/2) + floor((k+1)/2) + i_{k+1}, + + and `i_k` ranges from `S(k)` to `i_{k+1}-1`. There are two special + cases: if `k=0`, then `i_0 = S(0)`. Also, the initial case of + `S(k)` is `S(n)`, which is set in the method :meth:`Sq` before + calling this function. For this function, given `k`, `i_{k+1}`, + and `S(k+1)`, return a list consisting of the allowable possible + indices `[i_k, i_{k-1}, ..., i_1, i_0]` given by the above + formula. + + INPUT: + + - ``k`` -- non-negative integer + - ``i_k_plus_one`` -- the positive integer `i_{k+1}` + - ``S_k_plus_one`` -- the integer `S(k+1)` + + EXAMPLES:: + + sage: from sage.homology.homology_vector_space_with_basis import sum_indices + sage: sum_indices(1, 3, 3) + [[1, 0], [2, 1]] + sage: sum_indices(0, 4, 2) + [[2]] + """ + S_k = -S_k_plus_one + k//2 + (k+1)//2 + i_k_plus_one + if k == 0: + return [[S_k]] + return [[i_k] + l for i_k in range(S_k, i_k_plus_one) + for l in sum_indices(k-1, i_k, S_k)] + diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index c8c188552f6..98c5ab48003 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -158,18 +158,19 @@ from sage.misc.lazy_import import lazy_import from sage.homology.cell_complex import GenericCellComplex from sage.structure.sage_object import SageObject -from sage.structure.category_object import CategoryObject +from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.sets.set import Set from sage.rings.integer_ring import ZZ from sage.structure.parent_gens import normalize_names from sage.misc.latex import latex +from sage.misc.misc import union from sage.matrix.constructor import matrix from sage.homology.chain_complex import ChainComplex from sage.graphs.graph import Graph from functools import reduce -lazy_import('sage.categories.category_types', 'SimplicialComplexes') +lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') def lattice_paths(t1, t2, length=None): """ @@ -625,6 +626,50 @@ def product(self, other, rename_vertices=True): answer.append(Simplex(new)) return answer + def alexander_whitney(self, dim): + r""" + Subdivide this simplex into a pair of simplices. + + If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then + subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and + `(v_{dim}, v_{dim + 1}, ..., v_n)`. + + INPUTS: + + - ``dim`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: + + - a list containing just the triple ``(1, left, right)``, + where ``left`` and ``right`` are the two simplices described + above. + + This method allows one to construct a coproduct from the + `p+q`-chains to the tensor product of the `p`-chains and the + `q`-chains. The number 1 (a Sage integer) is the coefficient + of ``left tensor right`` in this coproduct. (The corresponding + formula is more complicated for the cubes that make up a + cubical complex, and the output format is intended to be + consistent for both cubes and simplices.) + + Calling this method ``alexander_whitney`` is an abuse of + notation, since the actual Alexander-Whitney map goes from + `C(X \times Y) \to C(X) \otimes C(Y)`, where `C(-)` denotes + the chain complex of singular chains, but this subdivision of + simplices is at the heart of it. + + EXAMPLES:: + + sage: s = Simplex((0,1,3,4)) + sage: s.alexander_whitney(0) + [(1, (0,), (0, 1, 3, 4))] + sage: s.alexander_whitney(2) + [(1, (0, 1, 3), (3, 4))] + """ + return [(ZZ.one(), Simplex(self.tuple()[:dim+1]), + Simplex(self.tuple()[dim:]))] + def __cmp__(self, other): """ Return ``True`` iff this simplex is the same as ``other``: that @@ -691,7 +736,7 @@ def _latex_(self): """ return latex(self.__tuple) -class SimplicialComplex(CategoryObject, GenericCellComplex): +class SimplicialComplex(Parent, GenericCellComplex): r""" Define a simplicial complex. @@ -775,7 +820,7 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): sage: l=designs.ProjectiveGeometryDesign(2,1,GF(4,name='a')) sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) - sage: SimplicialComplex(from_characteristic_function=(f, range(21))) + sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) Simplicial complex with 21 vertices and 168 facets TESTS: @@ -792,7 +837,15 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): True sage: SimplicialComplex(S, is_immutable=False).is_mutable() True - """ + + .. WARNING:: + + Simplicial complexes are not proper parents as they do + not possess element classes. In particular, parents are assumed + to be hashable (and hence immutable) by the coercion framework. + However this is close enough to being a parent with elements + being the faces of ``self`` that we currently allow this abuse. + """ def __init__(self, maximal_faces=None, @@ -833,8 +886,7 @@ def __init__(self, if (maximal_faces is not None and from_characteristic_function is not None): raise ValueError("maximal_faces and from_characteristic_function cannot be both defined") - CategoryObject.__init__(self, category=SimplicialComplexes()) - from sage.misc.misc import union + Parent.__init__(self, category=SimplicialComplexes().Finite()) C = None vertex_set = [] @@ -987,7 +1039,7 @@ def __cmp__(self,right): sage: X == SimplicialComplex([[1,3]]) True """ - if set(self._facets) == set(right._facets): + if isinstance(right, SimplicialComplex) and set(self._facets) == set(right._facets): return 0 else: return -1 @@ -1033,6 +1085,57 @@ def vertices(self): """ return self._vertex_set + def _an_element_(self): + """ + The first facet of this complex. + + EXAMPLES:: + + sage: SimplicialComplex()._an_element_() + () + sage: simplicial_complexes.Sphere(3)._an_element_() + (1, 2, 3, 4) + """ + return self.facets()[0] + + def __contains__(self, x): + """ + True if ``x`` is a simplex which is contained in this complex. + + EXAMPLES:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: Simplex((0,2)) in K + True + sage: Simplex((1,3)) in K + False + sage: 0 in K # not a simplex + False + """ + if not isinstance(x, Simplex): + return False + dim = x.dimension() + return x in self.n_faces(dim) + + def __call__(self, simplex): + """ + If ``simplex`` is a simplex in this complex, return it. + Otherwise, raise a ``ValueError``. + + EXAMPLE:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: K(Simplex((1,2))) + (1, 2) + sage: K(Simplex((0,1,3))) + Traceback (most recent call last): + ... + ValueError: the simplex is not in this complex + """ + if simplex not in self: + raise ValueError('the simplex is not in this complex') + return simplex + def maximal_faces(self): """ The maximal faces (a.k.a. facets) of this simplicial complex. @@ -2081,8 +2184,6 @@ def add_face(self, face): self._facets = Facets # Update the vertex set - from sage.misc.misc import union - if self._sorted: self._vertex_set = Simplex(sorted(reduce(union, [self._vertex_set, new_face]))) else: @@ -3284,11 +3385,18 @@ def _Hom_(self, other, category=None): sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) # indirect doctest sage: H - Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + in Category of finite simplicial complexes sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: S._Hom_(T, Objects()) Traceback (most recent call last): diff --git a/src/sage/homology/simplicial_complex_homset.py b/src/sage/homology/simplicial_complex_homset.py index 46d3aed15d4..041288618b6 100644 --- a/src/sage/homology/simplicial_complex_homset.py +++ b/src/sage/homology/simplicial_complex_homset.py @@ -6,9 +6,7 @@ - Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to work with the homset cache. -EXAMPLES: - -:: +EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) @@ -16,7 +14,12 @@ sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: x.is_injective() True sage: x.is_surjective() @@ -33,7 +36,7 @@ sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) - sage: loads(dumps(H))==H + sage: loads(dumps(H)) == H True """ @@ -55,7 +58,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.simplicial_complex_morphism as simplicial_complex_morphism +from sage.homology.simplicial_complex_morphism import SimplicialComplexMorphism def is_SimplicialComplexHomset(x): """ @@ -67,7 +70,9 @@ def is_SimplicialComplexHomset(x): sage: T = SimplicialComplex(is_mutable=False) sage: H = Hom(S, T) sage: H - Set of Morphisms from Simplicial complex with vertex set () and facets {()} to Simplicial complex with vertex set () and facets {()} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set () and facets {()} + to Simplicial complex with vertex set () and facets {()} + in Category of finite simplicial complexes sage: from sage.homology.simplicial_complex_homset import is_SimplicialComplexHomset sage: is_SimplicialComplexHomset(H) True @@ -80,7 +85,7 @@ def __call__(self, f): INPUT: - ``f`` -- a dictionary with keys exactly the vertices of the domain - and values vertices of the codomain + and values vertices of the codomain EXAMPLES:: @@ -90,13 +95,16 @@ def __call__(self, f): sage: H = Hom(S,T) sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 2, 4: 2} from Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: [0, 1, 2, 3, 4] --> [0, 1, 2, 2, 2] """ - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self.domain(),self.codomain()) + return SimplicialComplexMorphism(f,self.domain(),self.codomain()) def diagonal_morphism(self,rename_vertices=True): r""" - Returns the diagonal morphism in `Hom(S, S \times S)`. + Return the diagonal morphism in `Hom(S, S \times S)`. EXAMPLES:: @@ -104,36 +112,40 @@ def diagonal_morphism(self,rename_vertices=True): sage: H = Hom(S,S.product(S, is_mutable=False)) sage: d = H.diagonal_morphism() sage: d - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3'} from - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - to Simplicial complex with 16 vertices and 96 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with 16 vertices and 96 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + 3 |--> L3R3 sage: T = SimplicialComplex([[0], [1]], is_mutable=False) sage: U = T.product(T,rename_vertices = False, is_mutable=False) sage: G = Hom(T,U) sage: e = G.diagonal_morphism(rename_vertices = False) sage: e - Simplicial complex morphism {0: (0, 0), 1: (1, 1)} from - Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} - to Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} + To: Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Defn: 0 |--> (0, 0) + 1 |--> (1, 1) """ - - if self._codomain == self._domain.product(self._domain,rename_vertices=rename_vertices): - X = self._domain.product(self._domain,rename_vertices=rename_vertices) - f = dict() - if rename_vertices: - for i in self._domain.vertices().set(): - f[i] = "L"+str(i)+"R"+str(i) - else: - for i in self._domain.vertices().set(): - f[i] = (i,i) - return simplicial_complex_morphism.SimplicialComplexMorphism(f, self._domain,X) + # Preserve whether the codomain is mutable when renaming the vertices. + mutable = self._codomain.is_mutable() + X = self._domain.product(self._domain,rename_vertices=rename_vertices, is_mutable=mutable) + if self._codomain != X: + raise TypeError("diagonal morphism is only defined for Hom(X,XxX)") + f = {} + if rename_vertices: + f = {i: "L{0}R{0}".format(i) for i in self._domain.vertices().set()} else: - raise TypeError("Diagonal morphism is only defined for Hom(X,XxX).") + f = {i: (i,i) for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, X) def identity(self): """ - Returns the identity morphism of `Hom(S,S)`. + Return the identity morphism of `Hom(S,S)`. EXAMPLES:: @@ -146,21 +158,18 @@ def identity(self): sage: T = SimplicialComplex([[0,1]], is_mutable=False) sage: G = Hom(T,T) sage: G.identity() - Simplicial complex morphism {0: 0, 1: 1} from - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} to - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Defn: 0 |--> 0 + 1 |--> 1 """ - if self.is_endomorphism_set(): - f = dict() - for i in self._domain.vertices().set(): - f[i]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) - else: - raise TypeError("Identity map is only defined for endomorphism sets.") + if not self.is_endomorphism_set(): + raise TypeError("identity map is only defined for endomorphism sets") + f = {i:i for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, self._codomain) def an_element(self): """ - Returns a (non-random) element of ``self``. + Return a (non-random) element of ``self``. EXAMPLES:: @@ -169,17 +178,19 @@ def an_element(self): sage: H = Hom(S,T) sage: x = H.an_element() sage: x - Simplicial complex morphism {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets to Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Defn: [0, 1, 2, 3, 4, 5, 6, 7] --> [0, 0, 0, 0, 0, 0, 0, 0] """ X_vertices = self._domain.vertices().set() try: i = next(self._codomain.vertices().set().__iter__()) except StopIteration: - if len(X_vertices) == 0: - return dict() + if not X_vertices: + return {} else: - raise TypeError("There are no morphisms from a non-empty simplicial complex to an empty simplicial comples.") - f = dict() - for x in X_vertices: - f[x]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) + raise TypeError("there are no morphisms from a non-empty simplicial complex to an empty simplicial complex") + f = {x:i for x in X_vertices} + return SimplicialComplexMorphism(f, self._domain, self._codomain) + diff --git a/src/sage/homology/simplicial_complex_morphism.py b/src/sage/homology/simplicial_complex_morphism.py index 75b4d0172a2..4ad792a2778 100644 --- a/src/sage/homology/simplicial_complex_morphism.py +++ b/src/sage/homology/simplicial_complex_morphism.py @@ -20,7 +20,10 @@ sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: H = Hom(S,S.product(S, is_mutable=False)) sage: H.diagonal_morphism() - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3', 4: 'L4R4', 5: 'L5R5'} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} to Simplicial complex with 36 vertices and 18 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} + To: Simplicial complex with 36 vertices and 18 facets + Defn: [0, 1, 2, 3, 4, 5] --> ['L0R0', 'L1R1', 'L2R2', 'L3R3', 'L4R4', 'L5R5'] sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: T = SimplicialComplex([[0,2],[1,3]], is_mutable=False) @@ -53,8 +56,13 @@ sage: i = H.identity() sage: j = i.fiber_product(i) sage: j - Simplicial complex morphism {'L1R1': 1, 'L3R3': 3, 'L2R2': 2, 'L0R0': 0} from Simplicial complex with 4 vertices and 4 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and 4 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: L1R1 |--> 1 + L3R3 |--> 3 + L2R2 |--> 2 + L0R0 |--> 0 sage: S = simplicial_complexes.Sphere(2) sage: T = S.product(SimplicialComplex([[0,1]]), rename_vertices = False, is_mutable=False) sage: H = Hom(T,S) @@ -72,7 +80,10 @@ sage: y = G(g) sage: z = y.fiber_product(x) sage: z # this is the mapping path space - Simplicial complex morphism {'L2R(2, 0)': 2, 'L2R(2, 1)': 2, 'L0R(0, 0)': 0, 'L0R(0, 1)': 0, 'L1R(1, 0)': 1, 'L1R(1, 1)': 1} from Simplicial complex with 6 vertices and 6 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with 6 vertices and 6 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: ['L2R(2, 0)', 'L2R(2, 1)', 'L0R(0, 0)', 'L0R(0, 1)', 'L1R(1, 0)', 'L1R(1, 1)'] --> [2, 2, 0, 0, 1, 1] """ #***************************************************************************** @@ -91,13 +102,15 @@ # #***************************************************************************** -import sage.homology.simplicial_complex as simplicial_complex -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject +from sage.homology.simplicial_complex import Simplex, SimplicialComplex +from sage.matrix.constructor import matrix, zero_matrix from sage.rings.integer_ring import ZZ from sage.homology.chain_complex_morphism import ChainComplexMorphism from sage.combinat.permutation import Permutation from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.simplicial_complexes import SimplicialComplexes def is_SimplicialComplexMorphism(x): """ @@ -116,7 +129,7 @@ def is_SimplicialComplexMorphism(x): """ return isinstance(x,SimplicialComplexMorphism) -class SimplicialComplexMorphism(SageObject): +class SimplicialComplexMorphism(Morphism): """ An element of this class is a morphism of simplicial complexes. """ @@ -143,7 +156,7 @@ def __init__(self,f,X,Y): sage: x.image() == y.image() False """ - if not isinstance(X,simplicial_complex.SimplicialComplex) or not isinstance(Y,simplicial_complex.SimplicialComplex): + if not isinstance(X,SimplicialComplex) or not isinstance(Y,SimplicialComplex): raise ValueError("X and Y must be SimplicialComplexes.") if not set(f.keys()) == X._vertex_set.set(): raise ValueError("f must be a dictionary from the vertex set of X to single values in the vertex set of Y.") @@ -155,12 +168,11 @@ def __init__(self,f,X,Y): fi = [] for j in tup: fi.append(f[j]) - v = simplicial_complex.Simplex(set(fi)) + v = Simplex(set(fi)) if not v in Y_faces[v.dimension()]: raise ValueError("f must be a dictionary from the vertices of X to the vertices of Y.") self._vertex_dictionary = f - self._domain = X - self._codomain = Y + Morphism.__init__(self, Hom(X,Y,SimplicialComplexes())) def __eq__(self,x): """ @@ -172,7 +184,11 @@ def __eq__(self,x): sage: H = Hom(S,S) sage: i = H.identity() sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: f = {0:0,1:1,2:2,3:2} sage: j = H(f) sage: i==j @@ -187,9 +203,8 @@ def __eq__(self,x): sage: l = G(g) sage: k == l True - """ - if not isinstance(x,SimplicialComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._vertex_dictionary != x._vertex_dictionary: + if not isinstance(x,SimplicialComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._vertex_dictionary != x._vertex_dictionary: return False else: return True @@ -227,8 +242,8 @@ def __call__(self,x,orientation=False): sage: g(Simplex([0,1]), orientation=True) ((0, 1), -1) """ - dim = self._domain.dimension() - if not isinstance(x,simplicial_complex.Simplex) or x.dimension() > dim or not x in self._domain.faces()[x.dimension()]: + dim = self.domain().dimension() + if not isinstance(x,Simplex) or x.dimension() > dim or not x in self.domain().faces()[x.dimension()]: raise ValueError("x must be a simplex of the source of f") tup=x.tuple() fx=[] @@ -239,25 +254,42 @@ def __call__(self,x,orientation=False): oriented = Permutation(convert_perm(fx)).signature() else: oriented = 1 - return (simplicial_complex.Simplex(set(fx)), oriented) + return (Simplex(set(fx)), oriented) else: - return simplicial_complex.Simplex(set(fx)) + return Simplex(set(fx)) - def _repr_(self): + def _repr_type(self): """ - Return a string representation of ``self``. + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2} + sage: H(f)._repr_type() + 'Simplicial complex' + """ + return "Simplicial complex" + + def _repr_defn(self): + """ + If there are fewer than 5 vertices, print the image of each vertex + on a separate line. Otherwise, print the map as a single line. EXAMPLES:: - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - sage: i._repr_() - 'Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}' + sage: S = simplicial_complexes.Simplex(1) + sage: print Hom(S,S).identity()._repr_defn() + 0 |--> 0 + 1 |--> 1 + sage: T = simplicial_complexes.Torus() + sage: print Hom(T,T).identity()._repr_defn() + [0, 1, 2, 3, 4, 5, 6] --> [0, 1, 2, 3, 4, 5, 6] """ - return "Simplicial complex morphism " + str(self._vertex_dictionary) + " from " + self._domain._repr_() + " to " + self._codomain._repr_() + vd = self._vertex_dictionary + if len(vd) < 5: + return '\n'.join("{} |--> {}".format(v, vd[v]) for v in vd) + return "{} --> {}".format(vd.keys(), vd.values()) def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): """ @@ -271,10 +303,17 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= sage: f = {0:0,1:1,2:2} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 sage: a = x.associated_chain_complex_morphism() sage: a - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: a._matrix_dictionary {0: [0 0 0] [0 1 0] @@ -288,13 +327,21 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= [0 0 1], 2: []} sage: x.associated_chain_complex_morphism(augmented=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(cochain=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(base_ring=GF(11)) - Chain complex morphism from Chain complex with at most 2 nonzero terms over Finite Field of size 11 to Chain complex with at most 3 nonzero terms over Finite Field of size 11 + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 + To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 Some simplicial maps which reverse the orientation of a few simplices:: @@ -315,20 +362,18 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= {0: [0 1] [1 0], 1: [-1]} """ - max_dim = max(self._domain.dimension(),self._codomain.dimension()) - min_dim = min(self._domain.dimension(),self._codomain.dimension()) + max_dim = max(self.domain().dimension(),self.codomain().dimension()) + min_dim = min(self.domain().dimension(),self.codomain().dimension()) matrices = {} if augmented is True: - m = matrix.Matrix(base_ring,1,1,1) + m = matrix(base_ring,1,1,1) if not cochain: matrices[-1] = m else: matrices[-1] = m.transpose() for dim in range(min_dim+1): -# X_faces = list(self._domain.faces()[dim]) -# Y_faces = list(self._codomain.faces()[dim]) - X_faces = list(self._domain.n_cells(dim)) - Y_faces = list(self._codomain.n_cells(dim)) + X_faces = list(self.domain().n_cells(dim)) + Y_faces = list(self.codomain().n_cells(dim)) num_faces_X = len(X_faces) num_faces_Y = len(Y_faces) mval = [0 for i in range(num_faces_X*num_faces_Y)] @@ -338,33 +383,33 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= pass else: mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented - m = matrix.Matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) + m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() for dim in range(min_dim+1,max_dim+1): try: - l1 = len(self._codomain.n_cells(dim)) + l1 = len(self.codomain().n_cells(dim)) except KeyError: l1 = 0 try: - l2 = len(self._domain.n_cells(dim)) + l2 = len(self.domain().n_cells(dim)) except KeyError: l2 = 0 - m = matrix.zero_matrix(base_ring,l1,l2,sparse=True) + m = zero_matrix(base_ring,l1,l2,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() if not cochain: return ChainComplexMorphism(matrices,\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) else: return ChainComplexMorphism(matrices,\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) def image(self): """ @@ -408,41 +453,8 @@ def image(self): Simplicial complex with vertex set (0, 2) and facets {(0, 2)} """ - fa = [self(i) for i in self._domain.facets()] - return simplicial_complex.SimplicialComplex(fa, maximality_check=True) - - def domain(self): - """ - Returns the domain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.domain() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 1)} - """ - return self._domain - - def codomain(self): - """ - Returns the codomain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.codomain() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - - """ - return self._codomain + fa = [self(i) for i in self.domain().facets()] + return SimplicialComplex(fa, maximality_check=True) def is_surjective(self): """ @@ -469,7 +481,7 @@ def is_surjective(self): sage: x.is_surjective() True """ - return self._codomain == self.image() + return self.codomain() == self.image() def is_injective(self): """ @@ -492,7 +504,7 @@ def is_injective(self): True """ - v = [self._vertex_dictionary[i[0]] for i in self._domain.faces()[0]] + v = [self._vertex_dictionary[i[0]] for i in self.domain().faces()[0]] for i in v: if v.count(i) > 1: return False @@ -519,16 +531,21 @@ def is_identity(self): sage: f = {0:0,1:1,2:2,3:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: x.is_identity() False - """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): return False else: f = dict() - for i in self._domain._vertex_set.set(): + for i in self.domain()._vertex_set.set(): f[i] = i if self._vertex_dictionary != f: return False @@ -555,28 +572,31 @@ def fiber_product(self, other, rename_vertices = True): sage: y = G(g) sage: z = x.fiber_product(y) sage: z - Simplicial complex morphism {'L1R2': 1, 'L1R1': 1, 'L2R0': 0, 'L0R0': 0} - from Simplicial complex with 4 vertices and facets - {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} to Simplicial complex - with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and facets {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Defn: L1R2 |--> 1 + L1R1 |--> 1 + L2R0 |--> 0 + L0R0 |--> 0 """ - if self._codomain != other._codomain: + if self.codomain() != other.codomain(): raise ValueError("self and other must have the same codomain.") - X = self._domain.product(other._domain,rename_vertices = rename_vertices) + X = self.domain().product(other.domain(),rename_vertices = rename_vertices) v = [] f = dict() - eff1 = self._domain._vertex_set - eff2 = other._domain._vertex_set + eff1 = self.domain()._vertex_set + eff2 = other.domain()._vertex_set for i in eff1: for j in eff2: - if self(simplicial_complex.Simplex([i])) == other(simplicial_complex.Simplex([j])): + if self(Simplex([i])) == other(Simplex([j])): if rename_vertices: v.append("L"+str(i)+"R"+str(j)) f["L"+str(i)+"R"+str(j)] = self._vertex_dictionary[i] else: v.append((i,j)) f[(i,j)] = self._vertex_dictionary[i] - return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self._codomain) + return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self.codomain()) def mapping_torus(self): r""" @@ -612,15 +632,93 @@ def mapping_torus(self): ... ValueError: self must have the same domain and codomain. """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): raise ValueError("self must have the same domain and codomain.") map_dict = self._vertex_dictionary - interval = simplicial_complex.SimplicialComplex([["I0","I1"],["I1","I2"]]) - product = interval.product(self._domain,False) + interval = SimplicialComplex([["I0","I1"],["I1","I2"]]) + product = interval.product(self.domain(),False) facets = list(product.maximal_faces()) - for facet in self._domain._facets: + for facet in self.domain()._facets: left = [ ("I0",v) for v in facet ] right = [ ("I2",map_dict[v]) for v in facet ] for i in range(facet.dimension()+1): facets.append(tuple(left[:i+1]+right[i:])) - return simplicial_complex.SimplicialComplex(facets) + return SimplicialComplex(facets) + + def induced_homology_morphism(self, base_ring=None, cohomology=False): + """ + The map in (co)homology induced by this map + + INPUTS: + + - ``base_ring`` -- must be a field (optional, default ``QQ``) + + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, the map induced in cohomology rather than homology. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = S.product(S, is_mutable=False) + sage: H = Hom(S,T) + sage: diag = H.diagonal_morphism() + sage: h = diag.induced_homology_morphism(QQ) + sage: h + Graded vector space morphism: + From: Homology module of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + To: Homology module of Simplicial complex with 9 vertices and 18 facets over Rational Field + Defn: induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with 9 vertices and 18 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + + We can view the matrix form for the homomorphism:: + + sage: h.to_matrix(0) # in degree 0 + [1] + sage: h.to_matrix(1) # in degree 1 + [ 2] + [-1] + sage: h.to_matrix() # the entire homomorphism + [ 1| 0] + [--+--] + [ 0| 2] + [ 0|-1] + [--+--] + [ 0| 0] + + We can evaluate it on (co)homology classes:: + + sage: coh = diag.induced_homology_morphism(QQ, cohomology=True) + sage: coh.to_matrix(1) + [1 1] + sage: x,y = list(T.cohomology_ring(QQ).basis(1)) + sage: coh(x) + h^{1,0} + sage: coh(2*x+3*y) + 5*h^{1,0} + + Note that the complexes must be immutable for this to + work. Many, but not all, complexes are immutable when + constructed:: + + sage: S.is_immutable() + True + sage: S.barycentric_subdivision().is_immutable() + False + sage: S2 = S.suspension() + sage: S2.is_immutable() + False + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S2.set_immutable(); S2.is_immutable() + True + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + """ + from homology_morphism import InducedHomologyMorphism + return InducedHomologyMorphism(self, base_ring, cohomology) diff --git a/src/sage/interacts/decorator.py b/src/sage/interacts/decorator.py new file mode 100644 index 00000000000..dbea48df9a9 --- /dev/null +++ b/src/sage/interacts/decorator.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- +""" +The Interact Decorator +""" + +#***************************************************************************** +# Copyright (C) 2015 Volker Braun +# +# 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/ +#***************************************************************************** + + +import inspect +import textwrap +from sage.misc.html import html + + +def interact(func, **kwds): + """ + The interact decorator + + This is the global @interact-decorator. It should always return an + interact implementation appropriate to the UI. Right now only + SageNB is supported. + + INPUT: + + - ``func`` -- function. The interact function. + + - ``**kwds`` -- optional keyword arguments. To be passed to the + UI interact decorator. + + EXAMPLES:: + + sage: @interact + ....: def f(x=[1,2,3]): + ....: print(x) + ... + """ + from sagenb.notebook.interact import interact as sagenb_interact + return sagenb_interact(func, **kwds) + + + + + diff --git a/src/sage/interacts/library.py b/src/sage/interacts/library.py index f23ee6f0003..5b086d28314 100644 --- a/src/sage/interacts/library.py +++ b/src/sage/interacts/library.py @@ -497,7 +497,11 @@ def unit_circle( C_graph += Cf_point + Cf_line1 + Cf_line2 G_graph += Gf + Gf_point + Gf_line - html.table([[r"$\text{Unit Circle}$",r"$\text{Function}$"], [C_graph, G_graph]], header=True) + pretty_print(table([ + [r"$\text{Unit Circle}$", r"$\text{Function}$"], + [C_graph, G_graph] + ], header=True)) + @library_interact def special_points( @@ -894,7 +898,7 @@ def _newton_method(f, c, maxn, h): s = [["$n$","$x_n$","$f(x_n)$", "$f(x_n-h)\,f(x_n+h)$"]] for i, c in enumerate(midpoints): s.append([i+1, c, f(c), (c-h)*f(c+h)]) - html.table(s,header=True) + pretty_print(table(s, header=True)) else: P = plot(f, x, interval, color="blue") L = sum(line([(c, 0), (c, f(c))], color="green") for c in midpoints[:-1]) @@ -1008,7 +1012,7 @@ def trapezoid_integration( else: j = 2 s.append([i, xs[i], ys[i],j,N(j*ys[i])]) - html.table(s,header=True) + pretty_print(table(s, header=True)) @library_interact def simpson_integration( @@ -1128,7 +1132,7 @@ def parabola(a, b, c): j = (i+1)%2*(-2)+4 s.append([i, xs[i], ys[i],j,N(j*ys[i])]) s.append(['','','','$\sum$','$%s$'%latex(3/dx*approx)]) - html.table(s,header=True) + pretty_print(table(s, header=True)) html(r'$\int_{%.2f}^{%.2f} {f(x) \, \mathrm{d}x}\approx\frac {%.2f}{3}\cdot %s=%s$'% (interval[0], interval[1],dx,latex(3/dx*approx),latex(approx))) @@ -1198,9 +1202,11 @@ def riemann_sum( show(plot(func(x),(x,a,b),zorder=5) + rects) delka_intervalu=[division[i+1]-division[i] for i in range(n)] if list_table: - html.table([["$i$","$[x_{i-1},x_i]$","$\eta_i$","$f(\eta_i)$","$x_{i}-x_{i-1}$"]]\ - +[[i+1,[division[i],division[i+1]],xs[i],ys[i],delka_intervalu[i]] for i in range(n)],\ - header=True) + pretty_print(table([ + ["$i$", "$[x_{i-1},x_i]$", "$\eta_i$", "$f(\eta_i)$", "$x_{i}-x_{i-1}$"] + ] + [ + [i+1,[division[i],division[i+1]],xs[i],ys[i],delka_intervalu[i]] for i in range(n) + ], header=True)) html('Riemann sum: $\displaystyle\sum_{i=1}^{%s} f(\eta_i)(x_i-x_{i-1})=%s$ '% (latex(n),latex(sum([ys[i]*delka_intervalu[i] for i in range(n)])))) diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py index 2217737ed4e..d4d45b60d9b 100644 --- a/src/sage/interfaces/chomp.py +++ b/src/sage/interfaces/chomp.py @@ -126,10 +126,10 @@ def __call__(self, program, complex, subcomplex=None, **kwds): EXAMPLES:: - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP - {0: 0, 1: Z x Z, 2: Z} + sage: from sage.interfaces.chomp import CHomP + sage: T = cubical_complexes.Torus() + sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP + {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes @@ -256,10 +256,11 @@ def __call__(self, program, complex, subcomplex=None, **kwds): if verbose: print "Generators:" print gens - # # process output # + if output.find('ERROR') != -1: + raise RuntimeError('error inside CHomP') # output contains substrings of one of the forms # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2", # "H_1 = Z + Z_2 + Z" @@ -535,6 +536,7 @@ def homcubes(complex=None, subcomplex=None, **kwds): else: raise TypeError("Complex and/or subcomplex are not cubical complexes.") + def homchain(complex=None, **kwds): r""" Compute the homology of a chain complex using the CHomP program @@ -589,6 +591,7 @@ def homchain(complex=None, **kwds): else: raise TypeError("Complex is not a chain complex.") + def process_generators_cubical(gen_string, dim): r""" Process CHomP generator information for cubical complexes. diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index c9c6f23cc87..01913fdcee2 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -242,7 +242,7 @@ def interact(self): Interactively interact with the ECM program. EXAMPLES:: - + sage: ecm.interact() # not tested """ print "Enter numbers to run ECM on them." diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index 789da270163..67f267f9154 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -67,6 +67,8 @@ from sage.env import SAGE_EXTCODE, LOCAL_IDENTIFIER from sage.misc.object_multiplexer import Multiplex +from six import reraise as raise_ + BAD_SESSION = -2 # The subprocess is a shared resource. In a multi-threaded @@ -883,7 +885,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if except (TypeError, RuntimeError): pass return self._eval_line(line,allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False) - raise RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2] + raise_(RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2]) if len(line)>0: try: @@ -1329,7 +1331,7 @@ def __init__(self, parent, value, is_name=False, name=None): # coercion to work properly. except (RuntimeError, ValueError) as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/interfaces/gap3.py b/src/sage/interfaces/gap3.py index 9b6d570c0a3..888b0d54ecb 100644 --- a/src/sage/interfaces/gap3.py +++ b/src/sage/interfaces/gap3.py @@ -354,6 +354,7 @@ def _object_class(self): Return the class used for constructing GAP3 elements. TESTS:: + sage: gap3._object_class() """ diff --git a/src/sage/interfaces/giac.py b/src/sage/interfaces/giac.py index 00fd268fbcc..845a21663d8 100644 --- a/src/sage/interfaces/giac.py +++ b/src/sage/interfaces/giac.py @@ -1083,21 +1083,19 @@ def sum(self, var, min=None, max=None): INPUT: - - ``var`` - variable - ``min`` - default: None - ``max`` - default: None - Returns the definite integral if xmin is not None, otherwise returns an indefinite integral. EXAMPLES:: - sage: giac('1/(1+k^2)').sum('k',-oo,+infinity).simplify() # optional - giac - (pi*exp(pi)^2+pi)/(exp(pi)^2-1) + sage: giac('1/(1+k^2)').sum('k',-oo,+infinity).simplify() # optional - giac + (pi*exp(pi)^2+pi)/(exp(pi)^2-1) """ if min is None: return giac('sum(%s,%s)'%(self.name(),var)) diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 6d68cd935c5..a1d6b804130 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -98,6 +98,7 @@ def rand_seed(self): other than a small positive integer. EXAMPLES:: + from sage.misc.random_testing import random_testing sage: from sage.interfaces.interface import Interface sage: i = Interface("") @@ -553,7 +554,7 @@ def __getattr__(self, attrname): TESTS:: sage: ParentWithBase.__getattribute__(singular, '_coerce_map_from_') - + """ try: return ParentWithBase.__getattribute__(self, attrname) diff --git a/src/sage/interfaces/lie.py b/src/sage/interfaces/lie.py index 323bf5142ac..fa05a031d74 100644 --- a/src/sage/interfaces/lie.py +++ b/src/sage/interfaces/lie.py @@ -289,6 +289,7 @@ from expect import Expect, ExpectElement, ExpectFunction, FunctionElement, AsciiArtString from sage.misc.all import prod from sage.env import DOT_SAGE, SAGE_LOCAL +import os COMMANDS_CACHE = '%s/lie_commandlist_cache.sobj'%DOT_SAGE @@ -363,10 +364,15 @@ def _read_info_files(self, use_disk_cache=True): True sage: lie._read_info_files(use_disk_cache=False) #optional - lie sage: lie._trait_names_list # optional - lie - ['history', - 'version', + ['Adams', + ... + 'history', + ... + 'sort', ... - 'sort'] + 'version', + 'void', + 'write'] """ import sage.misc.persist if use_disk_cache: @@ -376,7 +382,7 @@ def _read_info_files(self, use_disk_cache=True): v = [] for key in trait_dict: v += trait_dict[key] - self._trait_names_list = v + self._trait_names_list = sorted(v) self._trait_names_dict = trait_dict self._help_dict = help_dict return @@ -451,7 +457,7 @@ def _read_info_files(self, use_disk_cache=True): #Save the data self._trait_names_dict = commands - self._trait_names_list = l + self._trait_names_list = sorted(l) self._help_dict = help #Write them to file @@ -513,16 +519,20 @@ def trait_names(self, type=None, verbose=False, use_disk_cache=True): EXAMPLES:: sage: lie.trait_names() # optional - lie - ['Cartan_type', + ['Adams', + ... + 'Cartan_type', + ... 'cent_roots', ... - 'n_comp'] - + 'n_comp', + ... + 'write'] """ if self._trait_names_dict is None: self._read_info_files() if type: - return self._trait_names_dict[type] + return sorted(self._trait_names_dict[type]) else: return self._trait_names_list @@ -743,11 +753,15 @@ def trait_names(self): sage: a4 = lie('A4') # optional - lie sage: a4.trait_names() # optional - lie - ['center', + ['Cartan', + ... + 'center', + 'det_Cartan', 'diagram', ... - 'n_comp'] - + 'n_comp', + ... + 'res_mat'] """ return self.parent().trait_names(type=self.type()) @@ -903,7 +917,7 @@ def reduce_load_lie(): """ return lie -import os + def lie_console(): """ Spawn a new LiE command-line session. @@ -929,7 +943,7 @@ def lie_version(): sage: lie_version() # optional - lie '2.1' """ - f = open(SAGE_LOCAL + 'lib/LiE/INFO.0') + f = open(os.path.join(SAGE_LOCAL, 'lib', 'LiE', 'INFO.0')) lines = f.readlines() f.close() i = lines.index('@version()\n') diff --git a/src/sage/interfaces/maple.py b/src/sage/interfaces/maple.py index eb2dd88188c..d6779112719 100644 --- a/src/sage/interfaces/maple.py +++ b/src/sage/interfaces/maple.py @@ -577,7 +577,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if def _eval_line_using_file(self, line, *args, **kwargs): """ EXAMPLES:: - + sage: maple._eval_line_using_file('2+2') # optional - maple '4' """ diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 86a81d6b26b..14ac6b522bd 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -2195,7 +2195,7 @@ def _add_(self, f): Note that you may get unexpected results when calling symbolic expressions and not explicitly giving the variables:: - + sage: (f+maxima.cos(x))(2) cos(_SAGE_VAR_x)+sin(2) sage: (f+maxima.cos(y))(2) @@ -2221,7 +2221,7 @@ def _sub_(self, f): Note that you may get unexpected results when calling symbolic expressions and not explicitly giving the variables:: - + sage: (f-maxima.cos(x))(2) sin(2)-cos(_SAGE_VAR_x) sage: (f-maxima.cos(y))(2) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 3bb841faed4..c90abbd055f 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -972,7 +972,7 @@ def _missing_assumption(self,errstr): assumption was missing. EXAMPLES:: - + sage: from sage.interfaces.maxima_lib import maxima_lib sage: maxima_lib._missing_assumption('Is xyz a thing?') Traceback (most recent call last): diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index df08d363fe9..c599690cfc3 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -94,9 +94,8 @@ sage: a = octave.eval(t + ';') # optional - octave, < 1/100th of a second sage: a = octave(t) # optional - octave -Note that actually reading a back out takes forever. This *must* -be fixed ASAP - see -http://trac.sagemath.org/sage_trac/ticket/940/. +Note that actually reading ``a`` back out takes forever. This *must* +be fixed as soon as possible, see :trac:`940`. Tutorial -------- @@ -286,7 +285,7 @@ def quit(self, verbose=False): # to signals. if not self._expect is None: if verbose: - print "Exiting spawned %s process."%self + print "Exiting spawned %s process." % self return def _start(self): @@ -308,9 +307,38 @@ def _start(self): # set random seed self.set_seed(self._seed) + def _equality_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + sage: octave('1 == 1') # optional - octave + 1 + """ + return '==' + + def _true_symbol(self): + """ + EXAMPLES:: + + sage: octave('1 == 1') # optional - octave + 1 + """ + return '1' + + def _false_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + """ + return '0' + def set(self, var, value): """ - Set the variable var to the given value. + Set the variable ``var`` to the given ``value``. EXAMPLES:: @@ -325,7 +353,7 @@ def set(self, var, value): def get(self, var): """ - Get the value of the variable var. + Get the value of the variable ``var``. EXAMPLES:: @@ -344,9 +372,9 @@ def clear(self, var): EXAMPLES:: sage: octave.set('x', '2') # optional - octave - sage: octave.clear('x') # optional - octave - sage: octave.get('x') # optional - octave - "error: `x' undefined near line ... column 1" + sage: octave.clear('x') # optional - octave + sage: octave.get('x') # optional - octave + "error: 'x' undefined near line ... column 1" """ self.eval('clear %s'%var) @@ -387,18 +415,16 @@ def version(self): return octave_version() def solve_linear_system(self, A, b): - """ + r""" Use octave to compute a solution x to A\*x = b, as a list. INPUT: + - ``A`` -- mxn matrix A with entries in `\QQ` or `\RR` - - ``A`` - mxn matrix A with entries in QQ or RR + - ``b`` -- m-vector b entries in `\QQ` or `\RR` (resp) - - ``b`` - m-vector b entries in QQ or RR (resp) - - - OUTPUT: An list x (if it exists) which solves M\*x = b + OUTPUT: A list x (if it exists) which solves M\*x = b EXAMPLES:: @@ -507,33 +533,211 @@ def _object_class(self): return OctaveElement +octave_functions = set() + +def to_complex(octave_string, R): + r""" + Helper function to convert octave complex number + + TESTS:: + + sage: from sage.interfaces.octave import to_complex + sage: to_complex('(0,1)', CDF) + 1.0*I + sage: to_complex('(1.3231,-0.2)', CDF) + 1.3231 - 0.2*I + """ + real, imag = octave_string.strip('() ').split(',') + return R(float(real), float(imag)) + class OctaveElement(ExpectElement): - def _matrix_(self, R): + def _get_sage_ring(self): + r""" + TESTS:: + + sage: octave('1')._get_sage_ring() # optional - octave + Real Double Field + sage: octave('I')._get_sage_ring() # optional - octave + Complex Double Field + sage: octave('[]')._get_sage_ring() # optional - octave + Real Double Field + """ + if self.isinteger(): + import sage.rings.integer_ring + return sage.rings.integer_ring.ZZ + elif self.isreal(): + import sage.rings.real_double + return sage.rings.real_double.RDF + elif self.iscomplex(): + import sage.rings.complex_double + return sage.rings.complex_double.CDF + else: + raise TypeError("no Sage ring associated to this element.") + + def __nonzero__(self): + r""" + Test whether this element is nonzero. + + EXAMPLES:: + + sage: bool(octave('0')) # optional - octave + False + sage: bool(octave('[]')) # optional - octave + False + sage: bool(octave('[0,0]')) # optional - octave + False + sage: bool(octave('[0,0,0;0,0,0]')) # optional - octave + False + + sage: bool(octave('0.1')) # optional - octave + True + sage: bool(octave('[0,1,0]')) # optional - octave + True + sage: bool(octave('[0,0,-0.1;0,0,0]')) # optional - octave + True + """ + return str(self) != ' [](0x0)' and any(x != '0' for x in str(self).split()) + + def _matrix_(self, R=None): r""" Return Sage matrix from this octave element. EXAMPLES:: + sage: A = octave('[1,2;3,4.5]') # optional - octave + sage: matrix(A) # optional - octave + [1.0 2.0] + [3.0 4.5] + sage: _.base_ring() # optional - octave + Real Double Field + + sage: A = octave('[I,1;-1,0]') # optional - octave + sage: matrix(A) # optional - octave + [1.0*I 1.0] + [ -1.0 0.0] + sage: _.base_ring() # optional - octave + Complex Double Field + sage: A = octave('[1,2;3,4]') # optional - octave sage: matrix(ZZ, A) # optional - octave [1 2] [3 4] - sage: A = octave('[1,2;3,4.5]') # optional - octave - sage: matrix(RR, A) # optional - octave - [1.00000000000000 2.00000000000000] - [3.00000000000000 4.50000000000000] """ + oc = self.parent() + if not self.ismatrix(): + raise TypeError('not an octave matrix') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = [u.strip().split(' ') for u in s.split('\n')] + nrows = len(w) + ncols = len(w[0]) + + if self.iscomplex(): + w = [[to_complex(x,R) for x in row] for row in w] + from sage.matrix.all import MatrixSpace - s = str(self).strip() - v = s.split('\n ') - nrows = len(v) - if nrows == 0: - return MatrixSpace(R,0,0)(0) - ncols = len(v[0].split()) - M = MatrixSpace(R, nrows, ncols) - v = sum([[x for x in w.split()] for w in v], []) - return M(v) + return MatrixSpace(R, nrows, ncols)(w) + + def _vector_(self, R=None): + r""" + Return Sage vector from this octave element. + + EXAMPLES:: + + sage: A = octave('[1,2,3,4]') # optional - octave + sage: vector(ZZ, A) # optional - octave + (1, 2, 3, 4) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,I]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 1.0*I) + """ + oc = self.parent() + if not self.isvector(): + raise TypeError('not an octave vector') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = s.strip().split(' ') + nrows = len(w) + + if self.iscomplex(): + w = [to_complex(x, R) for x in w] + + from sage.modules.free_module import FreeModule + return FreeModule(R, nrows)(w) + + def _scalar_(self): + """ + Return Sage scalar from this octave element. + + EXAMPLES:: + + sage: A = octave('2833') # optional - octave + sage: As = A.sage(); As # optional - octave + 2833.0 + sage: As.parent() # optional - octave + Real Double Field + + sage: B = sqrt(A) # optional - octave + sage: Bs = B.sage(); Bs # optional - octave + 53.2259 + sage: Bs.parent() # optional - octave + Real Double Field + + sage: C = sqrt(-A) # optional - octave + sage: Cs = C.sage(); Cs # optional - octave + 53.2259*I + sage: Cs.parent() # optional - octave + Complex Double Field + """ + if not self.isscalar(): + raise TypeError("not an octave scalar") + + R = self._get_sage_ring() + if self.iscomplex(): + return to_complex(str(self), R) + else: + return R(str(self)) + + def _sage_(self): + """ + Try to parse the octave object and return a sage object. + + EXAMPLES:: + sage: A = octave('2833') # optional - octave + sage: A.sage() # optional - octave + 2833.0 + sage: B = sqrt(A) # optional - octave + sage: B.sage() # optional - octave + 53.2259 + sage: C = sqrt(-A) # optional - octave + sage: C.sage() # optional - octave + 53.2259*I + sage: A = octave('[1,2,3,4]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.0, 3.0, 4.0) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,2.3+I,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3 + 1.0*I, 4.5) + """ + if self.isscalar(): + return self._scalar_() + elif self.isvector(): + return self._vector_() + elif self.ismatrix(): + return self._matrix_() + else: + raise NotImplementedError('octave type is not recognized') # An instance octave = Octave() diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index f00881ecb2d..b0a2e15554b 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -93,10 +93,10 @@ For what values $k$ does a vertical line $x=k$ intersect the combined figure of the circle and ellipse exactly three times? :: - sage: F = qf.exactly_k(3, y, circle * ellipse == 0); F # optional - qepcad - (X3 y)[(x^2 + y^2 - 3) (3 x^2 + 2 x y + y^2 - x + y - 7) = 0] - sage: qepcad(F) # optional - qepcad - 8 x^2 - 8 x - 29 <= 0 /\ x^2 - 3 <= 0 /\ 8 x^4 - 26 x^2 - 4 x + 13 >= 0 /\ [ 8 x^4 - 26 x^2 - 4 x + 13 = 0 \/ x^2 - 3 = 0 \/ 8 x^2 - 8 x - 29 = 0 ] + sage: F = qf.exactly_k(3, y, circle * ellipse == 0); F + (X3 y)[(3 x^2 + 2 x y + y^2 - x + y - 7) (x^2 + y^2 - 3) = 0] + sage: qepcad(F) # not tested (random order) + x^2 - 3 <= 0 /\ 8 x^2 - 8 x - 29 <= 0 /\ 8 x^4 - 26 x^2 - 4 x + 13 >= 0 /\ [ 8 x^4 - 26 x^2 - 4 x + 13 = 0 \/ x^2 - 3 = 0 \/ 8 x^2 - 8 x - 29 = 0 ] Here we see that the solutions are among the eight ($4 + 2 + 2$) roots of the three polynomials inside the brackets, but not all of these @@ -116,7 +116,7 @@ geometrically; then QEPCAD will use the extended language to produce a solution formula without the selection polynomials. :: - sage: qepcad(F, solution='geometric') # optional - qepcad + sage: qepcad(F, solution='geometric') # not tested (random order) x = _root_1 8 x^2 - 8 x - 29 \/ 8 x^4 - 26 x^2 - 4 x + 13 = 0 @@ -131,9 +131,9 @@ Let us do some basic formula simplification and visualization. We will look at the region which is inside both the ellipse and the circle:: - sage: F = qf.and_(ellipse < 0, circle < 0); F # optional - qepcad + sage: F = qf.and_(ellipse < 0, circle < 0); F [3 x^2 + 2 x y + y^2 - x + y - 7 < 0 /\ x^2 + y^2 - 3 < 0] - sage: qepcad(F) # optional - qepcad + sage: qepcad(F) # not tested (random order) y^2 + 2 x y + y + 3 x^2 - x - 7 < 0 /\ y^2 + x^2 - 3 < 0 We get back the same formula we put in. This is not surprising (we @@ -142,7 +142,7 @@ an output that lets us understand something about the shape of the solution set. :: - sage: qepcad(F, solution='geometric') # optional - qepcad + sage: qepcad(F, solution='geometric') # not tested (random order) [ [ x = _root_-2 8 x^4 - 26 x^2 - 4 x + 13 @@ -171,9 +171,9 @@ formula for the projection of a particular semicircle onto the $x$ axis:: - sage: F = qf.exists(y, qf.and_(circle == 0, x + y > 0)); F # optional - qepcad + sage: F = qf.exists(y, qf.and_(circle == 0, x + y > 0)); F (E y)[x^2 + y^2 - 3 = 0 /\ x + y > 0] - sage: qepcad(F) # optional - qepcad + sage: qepcad(F) # not tested (random order) x^2 - 3 <= 0 /\ [ x > 0 \/ 2 x^2 - 3 < 0 ] Here, the formula $x > 0$ had to be introduced in order to get a @@ -183,7 +183,7 @@ it is always possible to construct a solution formula in the extended language without introducing new polynomials. :: - sage: qepcad(F, solution='extended') # optional - qepcad + sage: qepcad(F, solution='extended') # not tested (random order) x^2 - 3 <= 0 /\ x > _root_1 2 x^2 - 3 Up to this point, all the output we have seen has basically been in the @@ -209,7 +209,7 @@ with. We will start by finding a point on our ellipse. :: sage: p = qepcad(ellipse == 0, solution='any-point'); p # optional - qepcad - {'y': 0.9685019685029527?, 'x': -1.468501968502953?} + {'x': -1.468501968502953?, 'y': 0.9685019685029527?} (Note that despite the decimal printing and the question marks, these are really exact numbers.) @@ -218,14 +218,22 @@ a copy of ellipse as a polynomial over `\QQ` (instead of a symbolic expression). :: - sage: pellipse = QQ['x,y'](ellipse) # optional - qepcad + sage: pellipse = QQ['x,y'](ellipse) sage: pellipse(**p) == 0 # optional - qepcad True For cell-points, let us look at points *not* on the ellipse. :: sage: pts = qepcad(ellipse != 0, solution='cell-points'); pts # optional - qepcad - [{'y': 0, 'x': 4}, {'y': 1, 'x': 2.468501968502953?}, {'y': -9, 'x': 2.468501968502953?}, {'y': 9, 'x': 1/2}, {'y': -1, 'x': 1/2}, {'y': -5, 'x': 1/2}, {'y': 3, 'x': -1.468501968502953?}, {'y': -1, 'x': -1.468501968502953?}, {'y': 0, 'x': -3}] + [{'x': 4, 'y': 0}, + {'x': 2.468501968502953?, 'y': 1}, + {'x': 2.468501968502953?, 'y': -9}, + {'x': 1/2, 'y': 9}, + {'x': 1/2, 'y': -1}, + {'x': 1/2, 'y': -5}, + {'x': -1.468501968502953?, 'y': 3}, + {'x': -1.468501968502953?, 'y': -1}, + {'x': -3, 'y': 0}] For the points here which are in full-dimensional cells, QEPCAD has the freedom to choose rational sample points, and it does so. @@ -238,8 +246,8 @@ Finally, for all-points, let us look again at finding vertical lines that intersect the union of the circle and the ellipse exactly three times. :: - sage: F = qf.exactly_k(3, y, circle * ellipse == 0); F # optional - qepcad - (X3 y)[(x^2 + y^2 - 3) (3 x^2 + 2 x y + y^2 - x + y - 7) = 0] + sage: F = qf.exactly_k(3, y, circle * ellipse == 0); F + (X3 y)[(3 x^2 + 2 x y + y^2 - x + y - 7) (x^2 + y^2 - 3) = 0] sage: pts = qepcad(F, solution='all-points'); pts # optional - qepcad [{'x': 1.732050807568878?}, {'x': 1.731054913462534?}, {'x': 0.678911384208004?}, {'x': -0.9417727377417167?}, {'x': -1.468193559928821?}, {'x': -1.468501968502953?}] @@ -248,7 +256,7 @@ We can substitute one of these solutions into the original equation:: sage: pt = pts[0] # optional - qepcad - sage: pcombo = QQ['x,y'](circle * ellipse) # optional - qepcad + sage: pcombo = QQ['x,y'](circle * ellipse) sage: intersections = pcombo(y=polygen(AA, 'y'), **pt); intersections # optional - qepcad y^4 + 4.464101615137755?*y^3 + 0.2679491924311227?*y^2 @@ -411,7 +419,7 @@ sage: c.sample_point() # optional - qepcad (0, 1.732050807568878?) sage: c.sample_point_dict() # optional - qepcad - {'y': 1.732050807568878?, 'x': 0} + {'x': 0, 'y': 1.732050807568878?} We have seen that we can get cells using the :meth:`cell` method. There are several QEPCAD commands that print lists of cells; we can @@ -445,17 +453,17 @@ the circle twice, and also intersect the ellipse twice. The vertical lines that intersect the circle twice can be found by simplifying:: - sage: F = qf.exactly_k(2, y, circle == 0); F # optional - qepcad + sage: F = qf.exactly_k(2, y, circle == 0); F (X2 y)[x^2 + y^2 - 3 = 0] and the vertical lines that intersect the ellipse twice are expressed by:: - sage: G = qf.exactly_k(2, y, ellipse == 0); G # optional - qepcad + sage: G = qf.exactly_k(2, y, ellipse == 0); G (X2 y)[3 x^2 + 2 x y + y^2 - x + y - 7 = 0] and the lines that intersect both figures would be:: - sage: qf.and_(F, G) # optional - qepcad + sage: qf.and_(F, G) Traceback (most recent call last): ... ValueError: QEPCAD formulas must be in prenex (quantifiers outermost) form @@ -469,7 +477,7 @@ just need to use a formula that uses both polynomials.) :: sage: qe = qepcad(qf.and_(ellipse == 0, circle == 0), interact=True) # optional - qepcad - sage: qe.go(); qe.go(); qe.go() # optional - qepcad + sage: qe.go(); qe.go(); qe.go() # optional - qepcad QEPCAD object has moved to phase 'Before Projection (y)' QEPCAD object has moved to phase 'Before Choice' QEPCAD object has moved to phase 'Before Solution' @@ -480,7 +488,7 @@ Our input polynomials are 'level-2 projection factors', we see:: - sage: qe.d_proj_factors() # optional - qepcad + sage: qe.d_proj_factors() # optional - qepcad P_1,1 = fac(J_1,1) = fac(dis(A_2,1)) = 8 x^2 - 8 x - 29 P_1,2 = fac(J_1,2) = fac(dis(A_2,2)) @@ -496,12 +504,12 @@ sign of the corresponding projection factor is 0 in our cell. For instance, the cell (12,2) is on the ellipse:: - sage: qe.cell(12,2).signs()[1][0] # optional - qepcad + sage: qe.cell(12,2).signs()[1][0] # optional - qepcad 0 So we can update the truth values as desired like this:: - sage: for c in qe.cell(): # optional - qepcad + sage: for c in qe.cell(): # optional - qepcad ....: count_ellipse = 0 ....: count_circle = 0 ....: for c2 in c: @@ -513,7 +521,7 @@ ``'geometric'``, and gives solutions using the same rules as ``solution='geometric'`` described above.) :: - sage: qe.solution_extension('G') # optional - qepcad + sage: qe.solution_extension('G') # not tested (random order) 8 x^2 - 8 x - 29 < 0 /\ x^2 - 3 < 0 @@ -525,9 +533,66 @@ sage: open('%s/default.qepcadrc'%SAGE_LOCAL).readlines()[-1] 'SINGULAR .../local/bin\n' +Tests related to the not tested examples (nondeterministic order of atoms):: + + sage: from sage.interfaces.qepcad import _qepcad_atoms + sage: var('a,b,c,d,x,y,z') + (a, b, c, d, x, y, z) + sage: qf = qepcad_formula + sage: ellipse = 3*x^2 + 2*x*y + y^2 - x + y - 7 + sage: circle = x^2 + y^2 - 3 + + sage: F = qf.exactly_k(3, y, circle * ellipse == 0) + sage: _qepcad_atoms(qepcad(F)) # optional - qepcad + {'8 x^2 - 8 x - 29 <= 0', + '8 x^2 - 8 x - 29 = 0', + '8 x^4 - 26 x^2 - 4 x + 13 = 0', + '8 x^4 - 26 x^2 - 4 x + 13 >= 0', + 'x^2 - 3 <= 0', + 'x^2 - 3 = 0'} + sage: _qepcad_atoms(qepcad(F, solution='geometric')) # optional - qepcad + {'8 x^4 - 26 x^2 - 4 x + 13 = 0', + 'x = _root_-1 x^2 - 3', + 'x = _root_1 8 x^2 - 8 x - 29'} + + sage: F = qf.and_(ellipse < 0, circle < 0) + sage: _qepcad_atoms(qepcad(F)) # optional - qepcad + {'y^2 + 2 x y + y + 3 x^2 - x - 7 < 0', 'y^2 + x^2 - 3 < 0'} + sage: _qepcad_atoms(qepcad(F, solution='geometric')) # optional - qepcad + {'8 x^4 - 26 x^2 - 4 x + 13 < 0', + 'x < _root_-2 8 x^4 - 26 x^2 - 4 x + 13', + 'x = _root_-2 8 x^4 - 26 x^2 - 4 x + 13', + 'x = _root_2 8 x^4 - 26 x^2 - 4 x + 13', + 'x > _root_2 8 x^4 - 26 x^2 - 4 x + 13', + 'y^2 + 2 x y + y + 3 x^2 - x - 7 < 0', + 'y^2 + x^2 - 3 < 0'} + + sage: F = qf.exists(y, qf.and_(circle == 0, x + y > 0)) + sage: _qepcad_atoms(qepcad(F)) # optional - qepcad + {'2 x^2 - 3 < 0', 'x > 0', 'x^2 - 3 <= 0'} + sage: _qepcad_atoms(qepcad(F, solution='extended')) # optional - qepcad + {'x > _root_1 2 x^2 - 3', 'x^2 - 3 <= 0'} + + sage: qe = qepcad(qf.and_(ellipse == 0, circle == 0), interact=True) # optional - qepcad + sage: qe.go(); qe.go(); qe.go() # optional - qepcad + QEPCAD object has moved to phase 'Before Projection (y)' + QEPCAD object has moved to phase 'Before Choice' + QEPCAD object has moved to phase 'Before Solution' + sage: for c in qe.cell(): # optional - qepcad + ....: count_ellipse = 0 + ....: count_circle = 0 + ....: for c2 in c: + ....: count_ellipse += (c2.signs()[1][0] == 0) + ....: count_circle += (c2.signs()[1][1] == 0) + ....: c.set_truth(count_ellipse == 2 and count_circle == 2) + sage: _qepcad_atoms(qe.solution_extension('G')) # optional - qepcad + {'8 x^2 - 8 x - 29 < 0', 'x^2 - 3 < 0'} + + AUTHORS: - Carl Witty (2008-03): initial version +- Thierry Monteil (2015-07) repackaging + noncommutative doctests. """ #***************************************************************************** @@ -550,6 +615,25 @@ from expect import Expect, ExpectFunction, AsciiArtString +def _qepcad_atoms(formula): + r""" + Return the atoms of a qepcad quantifier-free formula, as a set of strings. + + INPUT:: + + - `formula` (string) - a quantifier-free formula. + + .. note:: this function is pis-aller used for doctesting, not a complete + parser, which should be written in a further ticket. + + EXAMPLES:: + + sage: from sage.interfaces.qepcad import _qepcad_atoms + sage: _qepcad_atoms('y^5 + 4 y + 8 >= 0 /\ y <= 0 /\ [ y = 0 \/ y^5 + 4 y + 8 = 0 ]') + {'y <= 0', 'y = 0', 'y^5 + 4 y + 8 = 0', 'y^5 + 4 y + 8 >= 0'} + """ + return set(i.strip() for i in flatten([i.split('\\/') for i in formula.replace('[','').replace(']','').split('/\\')])) + def _qepcad_cmd(memcells=None): r""" Construct a QEPCAD command line. @@ -856,9 +940,9 @@ def solution_extension(self, kind): QEPCAD object has moved to phase 'Before Projection (y)' QEPCAD object has moved to phase 'Before Choice' QEPCAD object has moved to phase 'Before Solution' - sage: qe.solution_extension('E') # optional - qepcad + sage: qe.solution_extension('E') # not tested (random order) x > _root_1 2 x^2 - 3 /\ y^2 + x^2 - 3 = 0 /\ [ 2 x^2 - 3 > 0 \/ y = _root_-1 y^2 + x^2 - 3 ] - sage: qe.solution_extension('G') # optional - qepcad + sage: qe.solution_extension('G') # not tested (random order) [ [ 2 x^2 - 3 < 0 @@ -876,8 +960,36 @@ def solution_extension(self, kind): /\ y^2 + x^2 - 3 = 0 ] - sage: qe.solution_extension('T') # optional - qepcad + sage: qe.solution_extension('T') # not tested (random order) y + x > 0 /\ y^2 + x^2 - 3 = 0 + + TESTS: + + Tests related to the not tested examples (nondeterministic order of atoms):: + + sage: from sage.interfaces.qepcad import _qepcad_atoms + sage: var('x,y') + (x, y) + sage: qf = qepcad_formula + sage: qe = qepcad(qf.and_(x^2 + y^2 - 3 == 0, x + y > 0), interact=True) # optional - qepcad + sage: qe.go(); qe.go(); qe.go() # optional - qepcad + QEPCAD object has moved to phase 'Before Projection (y)' + QEPCAD object has moved to phase 'Before Choice' + QEPCAD object has moved to phase 'Before Solution' + sage: _qepcad_atoms(qe.solution_extension('E')) # optional - qepcad + {'2 x^2 - 3 > 0', + 'x > _root_1 2 x^2 - 3', + 'y = _root_-1 y^2 + x^2 - 3', + 'y^2 + x^2 - 3 = 0'} + sage: _qepcad_atoms(qe.solution_extension('G')) # optional - qepcad + {'2 x^2 - 3 < 0', + 'x = _root_-1 2 x^2 - 3', + 'x > _root_-1 2 x^2 - 3', + 'x^2 - 3 <= 0', + 'y = _root_-1 y^2 + x^2 - 3', + 'y^2 + x^2 - 3 = 0'} + sage: _qepcad_atoms(qe.solution_extension('T')) # optional - qepcad + {'y + x > 0', 'y^2 + x^2 - 3 = 0'} """ if kind == 'I': raise ValueError("Interactive solution construction not " @@ -978,11 +1090,22 @@ def answer(self): EXAMPLES:: - sage: qe = qepcad(x^3 - x == 0, interact=True) # optional - qepcad - sage: qe.finish() # optional - qepcad + sage: qe = qepcad(x^3 - x == 0, interact=True) # optional - qepcad + sage: qe.finish() # not tested (random order) x - 1 <= 0 /\ x + 1 >= 0 /\ [ x = 0 \/ x - 1 = 0 \/ x + 1 = 0 ] - sage: qe.answer() # optional - qepcad + sage: qe.answer() # not tested (random order) + x - 1 <= 0 /\ x + 1 >= 0 /\ [ x = 0 \/ x - 1 = 0 \/ x + 1 = 0 ] + + TESTS: + + Tests related to the not tested examples (nondeterministic order of atoms):: + + sage: from sage.interfaces.qepcad import _qepcad_atoms + sage: qe = qepcad(x^3 - x == 0, interact=True) # optional - qepcad + sage: qe.finish() # random, optional - qepcad x - 1 <= 0 /\ x + 1 >= 0 /\ [ x = 0 \/ x - 1 = 0 \/ x + 1 = 0 ] + sage: _qepcad_atoms(qe.answer()) # optional - qepcad + {'x + 1 = 0', 'x + 1 >= 0', 'x - 1 <= 0', 'x - 1 = 0', 'x = 0'} """ return AsciiArtString(self._parse_answer_stats()[0]) @@ -1318,13 +1441,13 @@ def qepcad(formula, assume=None, interact=False, solution=None, (a, b, c, d, x, y, z, long_with_underscore_314159) sage: K. = QQ[] - sage: qepcad('(E x)[a x + b > 0]', vars='(a,b,x)') # optional - qepcad + sage: qepcad('(E x)[a x + b > 0]', vars='(a,b,x)') # not tested (random order) a /= 0 \/ b > 0 sage: qepcad(a > b) # optional - qepcad b - a < 0 - sage: qepcad(qf.exists(x, a*x^2 + b*x + c == 0)) # optional - qepcad + sage: qepcad(qf.exists(x, a*x^2 + b*x + c == 0)) # not tested (random order) 4 a c - b^2 <= 0 /\ [ c = 0 \/ a /= 0 \/ 4 a c - b^2 < 0 ] sage: qepcad(qf.exists(x, a*x^2 + b*x + c == 0), assume=(a != 0)) # optional - qepcad @@ -1333,17 +1456,17 @@ def qepcad(formula, assume=None, interact=False, solution=None, For which values of $a$, $b$, $c$ does $a x^2 + b x + c$ have 2 real zeroes? :: - sage: exact2 = qepcad(qf.exactly_k(2, x, a*x^2 + b*x + c == 0)); exact2 # optional - qepcad + sage: exact2 = qepcad(qf.exactly_k(2, x, a*x^2 + b*x + c == 0)); exact2 # not tested (random order) a /= 0 /\ 4 a c - b^2 < 0 one real zero? :: - sage: exact1 = qepcad(qf.exactly_k(1, x, a*x^2 + b*x + c == 0)); exact1 # optional - qepcad + sage: exact1 = qepcad(qf.exactly_k(1, x, a*x^2 + b*x + c == 0)); exact1 # not tested (random order) [ a > 0 /\ 4 a c - b^2 = 0 ] \/ [ a < 0 /\ 4 a c - b^2 = 0 ] \/ [ a = 0 /\ 4 a c - b^2 < 0 ] No real zeroes? :: - sage: exact0 = qepcad(qf.forall(x, a*x^2 + b*x + c != 0)); exact0 # optional - qepcad + sage: exact0 = qepcad(qf.forall(x, a*x^2 + b*x + c != 0)); exact0 # not tested (random order) 4 a c - b^2 >= 0 /\ c /= 0 /\ [ b = 0 \/ 4 a c - b^2 > 0 ] $3^{75}$ real zeroes? :: @@ -1353,61 +1476,61 @@ def qepcad(formula, assume=None, interact=False, solution=None, We can check that the results don't overlap:: - sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact1), vars='a,b,c') # optional - qepcad + sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact1), vars='a,b,c') # not tested (random order) FALSE - sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact2), vars='a,b,c') # optional - qepcad + sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact2), vars='a,b,c') # not tested (random order) FALSE - sage: qepcad(r'[[%s] /\ [%s]]' % (exact1, exact2), vars='a,b,c') # optional - qepcad + sage: qepcad(r'[[%s] /\ [%s]]' % (exact1, exact2), vars='a,b,c') # not tested (random order) FALSE and that the union of the results is as expected:: - sage: qepcad(r'[[%s] \/ [%s] \/ [%s]]' % (exact0, exact1, exact2), vars=(a,b,c)) # optional - qepcad + sage: qepcad(r'[[%s] \/ [%s] \/ [%s]]' % (exact0, exact1, exact2), vars=(a,b,c)) # not tested (random order) b /= 0 \/ a /= 0 \/ c /= 0 So we have finitely many zeroes if $a$, $b$, or $c$ is nonzero; which means we should have infinitely many zeroes if they are all zero. :: - sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c == 0)) # optional - qepcad + sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c == 0)) # not tested (random order) a = 0 /\ b = 0 /\ c = 0 The polynomial is nonzero almost everywhere iff it is not identically zero. :: - sage: qepcad(qf.all_but_finitely_many(x, a*x^2 + b*x + c != 0)) # optional - qepcad + sage: qepcad(qf.all_but_finitely_many(x, a*x^2 + b*x + c != 0)) # not tested (random order) b /= 0 \/ a /= 0 \/ c /= 0 The non-zeroes are continuous iff there are no zeroes or if the polynomial is zero. :: - sage: qepcad(qf.connected_subset(x, a*x^2 + b*x + c != 0)) # optional - qepcad + sage: qepcad(qf.connected_subset(x, a*x^2 + b*x + c != 0)) # not tested (random order) 4 a c - b^2 >= 0 /\ [ a = 0 \/ 4 a c - b^2 > 0 ] The zeroes are continuous iff there are no or one zeroes, or if the polynomial is zero:: - sage: qepcad(qf.connected_subset(x, a*x^2 + b*x + c == 0)) # optional - qepcad + sage: qepcad(qf.connected_subset(x, a*x^2 + b*x + c == 0)) # not tested (random order) a = 0 \/ 4 a c - b^2 >= 0 - sage: qepcad(r'[[%s] \/ [%s] \/ [a = 0 /\ b = 0 /\ c = 0]]' % (exact0, exact1), vars='a,b,c') # optional - qepcad + sage: qepcad(r'[[%s] \/ [%s] \/ [a = 0 /\ b = 0 /\ c = 0]]' % (exact0, exact1), vars='a,b,c') # not tested (random order) a = 0 \/ 4 a c - b^2 >= 0 Since polynomials are continuous and $y > 0$ is an open set, they are positive infinitely often iff they are positive at least once. :: - sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c > 0)) # optional - qepcad + sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c > 0)) # not tested (random order) c > 0 \/ a > 0 \/ 4 a c - b^2 < 0 - sage: qepcad(qf.exists(x, a*x^2 + b*x + c > 0)) # optional - qepcad + sage: qepcad(qf.exists(x, a*x^2 + b*x + c > 0)) # not tested (random order) c > 0 \/ a > 0 \/ 4 a c - b^2 < 0 However, since $y >= 0$ is not open, the equivalence does not hold if you replace 'positive' with 'nonnegative'. (We assume $a \neq 0$ to get simpler formulas.) :: - sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c >= 0), assume=(a != 0)) # optional - qepcad + sage: qepcad(qf.infinitely_many(x, a*x^2 + b*x + c >= 0), assume=(a != 0)) # not tested (random order) a > 0 \/ 4 a c - b^2 < 0 - sage: qepcad(qf.exists(x, a*x^2 + b*x + c >= 0), assume=(a != 0)) # optional - qepcad + sage: qepcad(qf.exists(x, a*x^2 + b*x + c >= 0), assume=(a != 0)) # not tested (random order) a > 0 \/ 4 a c - b^2 <= 0 TESTS: @@ -1417,6 +1540,65 @@ def qepcad(formula, assume=None, interact=False, solution=None, sage: qepcad(qf.exists(a, a*long_with_underscore_314159 == 1)) # optional - qepcad longwithunderscore314159 /= 0 + + Tests related to the not tested examples (nondeterministic order of atoms):: + + sage: from sage.interfaces.qepcad import _qepcad_atoms + sage: var('a,b,c,d,x,y,z,long_with_underscore_314159') + (a, b, c, d, x, y, z, long_with_underscore_314159) + sage: K. = QQ[] + + sage: _qepcad_atoms(qepcad('(E x)[a x + b > 0]', vars='(a,b,x)')) # optional - qepcad + {'a /= 0', 'b > 0'} + + sage: _qepcad_atoms(qepcad(qf.exists(x, a*x^2 + b*x + c == 0))) # optional - qepcad + {'4 a c - b^2 < 0', '4 a c - b^2 <= 0', 'a /= 0', 'c = 0'} + + sage: exact2 = qepcad(qf.exactly_k(2, x, a*x^2 + b*x + c == 0)); _qepcad_atoms(exact2) # optional - qepcad + {'4 a c - b^2 < 0', 'a /= 0'} + + sage: exact1 = qepcad(qf.exactly_k(1, x, a*x^2 + b*x + c == 0)); _qepcad_atoms(exact1) # optional - qepcad + {'4 a c - b^2 < 0', '4 a c - b^2 = 0', 'a < 0', 'a = 0', 'a > 0'} + + sage: exact0 = qepcad(qf.forall(x, a*x^2 + b*x + c != 0)); _qepcad_atoms(exact0) # optional - qepcad + {'4 a c - b^2 > 0', '4 a c - b^2 >= 0', 'b = 0', 'c /= 0'} + + sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact1), vars='a,b,c') # optional - qepcad + FALSE + sage: qepcad(r'[[%s] /\ [%s]]' % (exact0, exact2), vars='a,b,c') # optional - qepcad + FALSE + sage: qepcad(r'[[%s] /\ [%s]]' % (exact1, exact2), vars='a,b,c') # optional - qepcad + FALSE + + sage: _qepcad_atoms(qepcad(r'[[%s] \/ [%s] \/ [%s]]' % (exact0, exact1, exact2), vars=(a,b,c))) # optional - qepcad + {'a /= 0', 'b /= 0', 'c /= 0'} + + sage: _qepcad_atoms(qepcad(qf.infinitely_many(x, a*x^2 + b*x + c == 0))) # optional - qepcad + {'a = 0', 'b = 0', 'c = 0'} + + sage: _qepcad_atoms(qepcad(qf.all_but_finitely_many(x, a*x^2 + b*x + c != 0))) # optional - qepcad + {'a /= 0', 'b /= 0', 'c /= 0'} + + sage: _qepcad_atoms(qepcad(qf.connected_subset(x, a*x^2 + b*x + c != 0))) # optional - qepcad + {'4 a c - b^2 > 0', '4 a c - b^2 >= 0', 'a = 0'} + + sage: _qepcad_atoms(qepcad(qf.connected_subset(x, a*x^2 + b*x + c == 0))) # optional - qepcad + {'4 a c - b^2 >= 0', 'a = 0'} + + sage: _qepcad_atoms(qepcad(r'[[%s] \/ [%s] \/ [a = 0 /\ b = 0 /\ c = 0]]' % (exact0, exact1), vars='a,b,c')) # optional - qepcad + {'4 a c - b^2 >= 0', 'a = 0'} + + sage: _qepcad_atoms(qepcad(qf.infinitely_many(x, a*x^2 + b*x + c > 0))) # optional - qepcad + {'4 a c - b^2 < 0', 'a > 0', 'c > 0'} + + sage: _qepcad_atoms(qepcad(qf.exists(x, a*x^2 + b*x + c > 0))) # optional - qepcad + {'4 a c - b^2 < 0', 'a > 0', 'c > 0'} + + sage: _qepcad_atoms(qepcad(qf.infinitely_many(x, a*x^2 + b*x + c >= 0), assume=(a != 0))) # optional - qepcad + {'4 a c - b^2 < 0', 'a > 0'} + + sage: _qepcad_atoms(qepcad(qf.exists(x, a*x^2 + b*x + c >= 0), assume=(a != 0))) # optional - qepcad + {'4 a c - b^2 <= 0', 'a > 0'} """ use_witness = False if solution == 'any-point': @@ -1526,7 +1708,7 @@ def qepcad_version(): EXAMPLES:: sage: qepcad_version() # random, optional - qepcad - 'Version B 1.48, 25 Oct 2007' + 'Version B 1.69, 16 Mar 2012' TESTS:: @@ -2545,3 +2727,4 @@ def sample_point_dict(self): vars = self._parent._varlist return dict([(vars[i], points[i]) for i in range(len(points))]) + diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index 0795ca7eec7..1462030975e 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -402,6 +402,22 @@ def _rich_repr_(self, display_manager, **kwds): """ return None + def _repr_option(self, option): + """ + Disable repr option. + + This is necessary because otherwise our :meth:`__getattr__` + would be called. + + EXAMPLES:: + + sage: from sage.repl.rich_output import get_display_manager + sage: m = sage0(4) + sage: m._repr_option('ascii_art') + False + """ + return False + def __getattr__(self, attrname): """ EXAMPLES:: diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 5079f3f0e1b..c9743de9501 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -334,6 +334,8 @@ from sage.misc.misc import get_verbose from sage.misc.superseded import deprecation +from six import reraise as raise_ + class SingularError(RuntimeError): """ Raised if Singular printed an error message @@ -785,20 +787,33 @@ def __call__(self, x, type='def'): return SingularElement(self, type, x, False) - def has_coerce_map_from_impl(self, S): + def _coerce_map_from_(self, S): + """ + Return ``True`` if ``S`` admits a coercion map into the + Singular interface. + + EXAMPLES:: + + sage: singular._coerce_map_from_(ZZ) + True + sage: singular.coerce_map_from(ZZ) + Call morphism: + From: Integer Ring + To: Singular + sage: singular.coerce_map_from(float) + """ # we want to implement this without coercing, since singular has state. if hasattr(S, 'an_element'): if hasattr(S.an_element(), '_singular_'): return True try: self._coerce_(S.an_element()) + return True except TypeError: - return False - return True + pass elif S is int or S is long: return True - raise NotImplementedError - + return None def cputime(self, t=None): r""" @@ -1248,7 +1263,7 @@ def __init__(self, parent, type, value, is_name=False): # coercion to work properly. except SingularError as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/lfunctions/zero_sums.pyx b/src/sage/lfunctions/zero_sums.pyx index bccd267cb5a..6c4535c3d9c 100644 --- a/src/sage/lfunctions/zero_sums.pyx +++ b/src/sage/lfunctions/zero_sums.pyx @@ -56,7 +56,7 @@ cdef class LFunctionZeroSum_abstract(SageObject): """ cdef _pi # Pi to 64 bits cdef _euler_gamma # Euler-Mascheroni constant = 0.5772... - cdef _N # The level of the form attached to self + cdef _level # The level of the form attached to self cdef _k # The weight of the form attached to self cdef _C1 # = log(N)/2 - log(2*pi) cdef _C0 # = C1 - euler_gamma @@ -119,7 +119,7 @@ cdef class LFunctionZeroSum_abstract(SageObject): 389 """ - return self._N + return self._level def weight(self): r""" @@ -941,9 +941,9 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): self._k = ZZ(2) self._E = E if N is not None: - self._N = N + self._level = N else: - self._N = E.conductor() + self._level = E.conductor() # PARI minicurve for computing a_p coefficients self._e = E.pari_mincurve() @@ -951,7 +951,7 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): self._euler_gamma = RDF(euler_gamma) # These constants feature in most (all?) sums over the L-function's zeros - self._C1 = log(RDF(self._N))/2 - log(self._pi*2) + self._C1 = log(RDF(self._level))/2 - log(self._pi*2) self._C0 = self._C1 - self._euler_gamma # Number of CPUs to use for computations @@ -1059,7 +1059,7 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): p,e = n.perfect_power() ap = self._E.ap(p) logp = log(RDF(p)) - if p.divides(self._N): + if p.divides(self._level): return - ap**e*logp/n_float a,b = ap,2 # Coefficients for higher powers obey recursion relation @@ -1181,7 +1181,7 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): cdef int ap, aq cdef unsigned long n - cdef double N_double = self._N + cdef double N_double = self._level t = twopi*Delta expt = c_exp(t) @@ -1193,7 +1193,7 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): # Do bad primes first. Add correct contributions and subtract # incorrect contribution, since we'll add them back later on. if bad_primes is None: - bad_primes = self._N.prime_divisors() + bad_primes = self._level.prime_divisors() bad_primes = [prime for prime in bad_primes if prime 2.5: max_Delta = 2.5 verbose("Computed max Delta value too big; setting to 2.5") diff --git a/src/sage/libs/arb/acb.pxd b/src/sage/libs/arb/acb.pxd index 61336196ee3..c04b39d6d4d 100644 --- a/src/sage/libs/arb/acb.pxd +++ b/src/sage/libs/arb/acb.pxd @@ -1,26 +1,160 @@ -from sage.libs.arb.arb cimport arb_t, arb_struct +# distutils: libraries = arb + +from sage.libs.arb.types cimport * +from sage.libs.flint.types cimport fmpz_t, fmpq_t cdef extern from "acb.h": - ctypedef struct acb_struct: - arb_struct real - arb_struct imag - ctypedef acb_struct[1] acb_t + + arb_t acb_realref(acb_t x) + arb_t acb_imagref(acb_t x) void acb_init(acb_t x) void acb_clear(acb_t x) + acb_ptr _acb_vec_init(long n) + void _acb_vec_clear(acb_ptr v, long n) bint acb_is_zero(const acb_t z) + bint acb_is_one(const acb_t z) + bint acb_is_finite(const acb_t z) bint acb_is_exact(const acb_t z) + bint acb_is_int(const acb_t z) + void acb_zero(acb_t z) + void acb_one(acb_t z) + void acb_onei(acb_t z) void acb_set(acb_t z, const acb_t x) - void acb_set_ui(acb_t z, unsigned long c) + void acb_set_ui(acb_t z, long x) + void acb_set_si(acb_t z, long x) + void acb_set_fmpz(acb_t z, const fmpz_t x) + void acb_set_arb(acb_t z, const arb_t c) + void acb_set_fmpq(acb_t z, const fmpq_t x, long prec) + void acb_set_round(acb_t z, const acb_t x, long prec) + void acb_set_round_fmpz(acb_t z, const fmpz_t x, long prec) + void acb_set_round_arb(acb_t z, const arb_t x, long prec) + void acb_swap(acb_t z, acb_t x) + + void acb_print(const acb_t x) + void acb_printd(const acb_t z, long digits) + + # void acb_randtest(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_special(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_precise(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_param(acb_t z, flint_rand_t state, long prec, long mag_bits) bint acb_equal(const acb_t x, const acb_t y) + bint acb_eq(const acb_t x, const acb_t y) + bint acb_ne(const acb_t x, const acb_t y) bint acb_overlaps(const acb_t x, const acb_t y) + void acb_get_abs_ubound_arf(arf_t u, const acb_t z, long prec) + void acb_get_abs_lbound_arf(arf_t u, const acb_t z, long prec) + void acb_get_rad_ubound_arf(arf_t u, const acb_t z, long prec) + void acb_get_mag(mag_t u, const acb_t x) + void acb_get_mag_lower(mag_t u, const acb_t x) + bint acb_contains_fmpq(const acb_t x, const fmpq_t y) + bint acb_contains_fmpz(const acb_t x, const fmpz_t y) + bint acb_contains(const acb_t x, const acb_t y) + bint acb_contains_zero(const acb_t x) + long acb_rel_error_bits(const acb_t x) + long acb_rel_accuracy_bits(const acb_t x) + long acb_bits(const acb_t x) + void acb_indeterminate(acb_t x) + void acb_trim(acb_t y, const acb_t x) + bint acb_is_real(const acb_t x) + bint acb_get_unique_fmpz(fmpz_t z, const acb_t x) + void acb_arg(arb_t r, const acb_t z, long prec) + void acb_abs(arb_t r, const acb_t z, long prec) + + void acb_neg(acb_t z, const acb_t x) + void acb_conj(acb_t z, const acb_t x) + void acb_add_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_add_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_add_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_add(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_sub_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_sub_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_sub_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_sub(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_mul_onei(acb_t z, const acb_t x) + void acb_div_onei(acb_t z, const acb_t x) + void acb_mul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_mul_si(acb_t z, const acb_t x, long y, long prec) + void acb_mul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_mul_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_mul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_mul_2exp_si(acb_t z, const acb_t x, long e) + void acb_mul_2exp_fmpz(acb_t z, const acb_t x, const fmpz_t e) + void acb_cube(acb_t z, const acb_t x, long prec) + void acb_addmul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_addmul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_addmul_si(acb_t z, const acb_t x, long y, long prec) + void acb_addmul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_addmul_arb(acb_t z, const acb_t x, const arb_t y, long prec) + void acb_submul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_submul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_submul_si(acb_t z, const acb_t x, long y, long prec) + void acb_submul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_submul_arb(acb_t z, const acb_t x, const arb_t y, long prec) + void acb_inv(acb_t z, const acb_t x, long prec) + void acb_div_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_div_si(acb_t z, const acb_t x, long y, long prec) + void acb_div_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) void acb_div(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_const_pi(acb_t y, long prec) + + void acb_sqrt(acb_t r, const acb_t z, long prec) + void acb_rsqrt(acb_t r, const acb_t z, long prec) + void acb_pow_fmpz(acb_t y, const acb_t b, const fmpz_t e, long prec) + void acb_pow_ui(acb_t y, const acb_t b, unsigned long e, long prec) + void acb_pow_si(acb_t y, const acb_t b, long e, long prec) + void acb_pow_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_pow(acb_t z, const acb_t x, const acb_t y, long prec) + + void acb_exp(acb_t y, const acb_t z, long prec) + void acb_exp_pi_i(acb_t y, const acb_t z, long prec) + void acb_exp_invexp(acb_t s, acb_t t, const acb_t z, long prec) + void acb_log(acb_t y, const acb_t z, long prec) + void acb_log1p(acb_t z, const acb_t x, long prec) + + void acb_sin(acb_t s, const acb_t z, long prec) + void acb_cos(acb_t c, const acb_t z, long prec) + void acb_sin_cos(arb_t s, arb_t c, const acb_t z, long prec) + void acb_tan(acb_t s, const acb_t z, long prec) + void acb_cot(acb_t s, const acb_t z, long prec) + void acb_sin_pi(acb_t s, const acb_t z, long prec) + void acb_cos_pi(acb_t s, const acb_t z, long prec) + void acb_sin_cos_pi(acb_t s, acb_t c, const acb_t z, long prec) + void acb_tan_pi(acb_t s, const acb_t z, long prec) + void acb_cot_pi(acb_t s, const acb_t z, long prec) + + void acb_atan(acb_t s, const acb_t z, long prec) + + void acb_sinh(acb_t s, const acb_t z, long prec) + void acb_cosh(acb_t c, const acb_t z, long prec) + void acb_sinh_cosh(acb_t s, acb_t c, const acb_t z, long prec) + void acb_tanh(acb_t s, const acb_t z, long prec) + void acb_coth(acb_t s, const acb_t z, long prec) + + void acb_rising_ui_bs(acb_t z, const acb_t x, unsigned long n, long prec) + void acb_rising_ui_rs(acb_t z, const acb_t x, unsigned long n, unsigned long step, long prec) + void acb_rising_ui_rec(acb_t z, const acb_t x, unsigned long n, long prec) + void acb_rising_ui(acb_t z, const acb_t x, unsigned long n, long prec) + + void acb_gamma(acb_t y, const acb_t x, long prec) + void acb_rgamma(acb_t y, const acb_t x, long prec) + void acb_lgamma(acb_t y, const acb_t x, long prec) + void acb_digamma(acb_t y, const acb_t x, long prec) + void acb_log_sin_pi(acb_t res, const acb_t z, long prec) + void acb_polygamma(acb_t z, const acb_t s, const acb_t z, long prec) + void acb_barnes_g(acb_t res, const acb_t z, long prec) + void acb_log_barnes_g(acb_t res, const acb_t z, long prec) + + void acb_zeta(acb_t z, const acb_t s, long prec) + void acb_hurwitz_zeta(acb_t z, const acb_t s, const acb_t a, long prec) + + void acb_polylog(acb_t w, const acb_t s, const acb_t z, long prec) + void acb_polylog_si(acb_t w, long s, const acb_t z, long prec) + + void acb_agm1(acb_t m, const acb_t z, long prec) + void acb_agm1_cpx(acb_ptr m, const acb_t z, long len, long prec) diff --git a/src/sage/libs/arb/acb_hypgeom.pxd b/src/sage/libs/arb/acb_hypgeom.pxd new file mode 100644 index 00000000000..56864881561 --- /dev/null +++ b/src/sage/libs/arb/acb_hypgeom.pxd @@ -0,0 +1,62 @@ +# distutils: libraries = arb + +from sage.libs.arb.types cimport * + +cdef extern from "acb_hypgeom.h": + void acb_hypgeom_pfq_bound_factor(mag_t C, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, unsigned long n) + long acb_hypgeom_pfq_choose_n(acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long prec) + void acb_hypgeom_pfq_sum_forward(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_rs(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_bs(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_fme(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_bs_invz(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t w, long n, long prec) + void acb_hypgeom_pfq_sum_invz(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, const acb_t w, long n, long prec) + void acb_hypgeom_pfq_direct(acb_t res, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + # void acb_hypgeom_pfq_series_direct(acb_poly_t res, const acb_poly_struct * a, long p, const acb_poly_struct * b, long q, const acb_poly_t z, int regularized, long n, long len, long prec) + void acb_hypgeom_u_asymp(acb_t res, const acb_t a, const acb_t b, const acb_t z, long n, long prec) + bint acb_hypgeom_u_use_asymp(const acb_t z, long prec) + # void acb_hypgeom_u_1f1_series(acb_poly_t res, const acb_poly_t a, const acb_poly_t b, const acb_poly_t z, long len, long prec) + void acb_hypgeom_u_1f1(acb_t res, const acb_t a, const acb_t b, const acb_t z, long prec) + void acb_hypgeom_u(acb_t res, const acb_t a, const acb_t b, const acb_t z, long prec) + void acb_hypgeom_m_asymp(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_m_1f1(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_m(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_erf_1f1a(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erf_1f1b(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erf_asymp(acb_t res, const acb_t z, long prec, long prec2) + void acb_hypgeom_erf(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erfc(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erfi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_bessel_j_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_j_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_j(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_y(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_k_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + # void acb_hypgeom_bessel_k_0f1_series(acb_poly_t res, const acb_poly_t nu, const acb_poly_t z, long len, long prec) + void acb_hypgeom_bessel_k_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_k(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_gamma_upper_asymp(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_1f1a(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_1f1b(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_singular(acb_t res, long s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_expint(acb_t res, const acb_t s, const acb_t z, long prec) + void acb_hypgeom_ei_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ei_2f2(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ei(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si_1f2(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci_2f3(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci(acb_t res, const acb_t z, long prec) + void acb_hypgeom_shi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi_2f3(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_li(acb_t res, const acb_t z, int offset, long prec) + diff --git a/src/sage/libs/arb/arb.pxd b/src/sage/libs/arb/arb.pxd index 4068c68cdd7..0118d436dec 100644 --- a/src/sage/libs/arb/arb.pxd +++ b/src/sage/libs/arb/arb.pxd @@ -1,15 +1,11 @@ -from sage.libs.arb.arf cimport arf_t -from sage.libs.arb.mag cimport mag_t +# distutils: libraries = arb + +from sage.libs.arb.types cimport * from sage.libs.flint.types cimport fmpz_t, fmpq_t from sage.libs.mpfr cimport mpfr_t cdef extern from "arb.h": - ctypedef struct arb_struct: - pass - ctypedef arb_struct arb_t[1] - ctypedef arb_struct * arb_ptr - arf_t arb_midref(arb_t x) mag_t arb_radref(arb_t x) diff --git a/src/sage/libs/arb/arf.pxd b/src/sage/libs/arb/arf.pxd index ced559f8924..5ee6d00e0cb 100644 --- a/src/sage/libs/arb/arf.pxd +++ b/src/sage/libs/arb/arf.pxd @@ -1,16 +1,11 @@ -from sage.libs.arb.mag cimport mag_t +# distutils: libraries = arb + +from sage.libs.arb.types cimport * from sage.libs.gmp.types cimport mpz_t from sage.libs.flint.types cimport fmpz_t from sage.libs.mpfr cimport mpfr_t, mpfr_rnd_t cdef extern from "arf.h": - ctypedef struct arf_struct: - pass - ctypedef arf_struct arf_t[1] - ctypedef arf_struct * arf_ptr - ctypedef const arf_struct * arf_srcptr - ctypedef int arf_rnd_t - void arf_init(arf_t x) void arf_clear(arf_t x) void arf_zero(arf_t x) @@ -137,3 +132,5 @@ cdef extern from "arf.h": int arf_complex_mul(arf_t e, arf_t f, const arf_t a, const arf_t b, const arf_t c, const arf_t d, long prec, arf_rnd_t rnd) int arf_complex_mul_fallback(arf_t e, arf_t f, const arf_t a, const arf_t b, const arf_t c, const arf_t d, long prec, arf_rnd_t rnd) int arf_complex_sqr(arf_t e, arf_t f, const arf_t a, const arf_t b, long prec, arf_rnd_t rnd) + + long ARF_PREC_EXACT diff --git a/src/sage/libs/arb/mag.pxd b/src/sage/libs/arb/mag.pxd index b3264aa3e12..eb12d89e79a 100644 --- a/src/sage/libs/arb/mag.pxd +++ b/src/sage/libs/arb/mag.pxd @@ -1,14 +1,9 @@ +# distutils: libraries = arb + +from sage.libs.arb.types cimport * from sage.libs.flint.types cimport fmpz_t, fmpq_t cdef extern from "mag.h": - ctypedef struct mag_struct: - pass - ctypedef mag_struct mag_t[1] - ctypedef mag_struct * mag_ptr - ctypedef const mag_struct * mag_srcptr - - long MAG_BITS - void mag_init(mag_t x) void mag_clear(mag_t x) void mag_init_set(mag_t x, const mag_t y) diff --git a/src/sage/libs/arb/types.pxd b/src/sage/libs/arb/types.pxd new file mode 100644 index 00000000000..d1a79eb4bab --- /dev/null +++ b/src/sage/libs/arb/types.pxd @@ -0,0 +1,35 @@ +cdef extern from "mag.h": + ctypedef struct mag_struct: + pass + ctypedef mag_struct mag_t[1] + ctypedef mag_struct * mag_ptr + ctypedef const mag_struct * mag_srcptr + long MAG_BITS + +cdef extern from "arf.h": + ctypedef struct arf_struct: + pass + ctypedef arf_struct arf_t[1] + ctypedef arf_struct * arf_ptr + ctypedef const arf_struct * arf_srcptr + cdef enum arf_rnd_t: + ARF_RND_DOWN + ARF_RND_UP + ARF_RND_FLOOR + ARF_RND_CEIL + ARF_RND_NEAR + long ARF_PREC_EXACT + +cdef extern from "arb.h": + ctypedef struct arb_struct: + pass + ctypedef arb_struct arb_t[1] + ctypedef arb_struct * arb_ptr + +cdef extern from "acb.h": + ctypedef struct acb_struct: + pass + ctypedef acb_struct[1] acb_t + ctypedef acb_struct * acb_ptr + ctypedef const acb_struct * acb_srcptr + diff --git a/src/sage/libs/coxeter3/coxeter.pxd b/src/sage/libs/coxeter3/coxeter.pxd index e2747a12c0f..a234ab78e53 100644 --- a/src/sage/libs/coxeter3/coxeter.pxd +++ b/src/sage/libs/coxeter3/coxeter.pxd @@ -6,7 +6,7 @@ #***************************************************************************** from sage.structure.sage_object cimport SageObject -include "decl.pxi" +include "decl.pxd" cdef class String: cdef c_String x @@ -24,9 +24,9 @@ cdef class CoxGroup(SageObject): cdef class CoxGroupElement: cdef c_CoxWord word cdef c_CoxGroup* group - cdef CoxGroup _parent + cdef CoxGroup _parent_group cdef CoxGroupElement _new(self) - cpdef CoxGroup parent(self) + cpdef CoxGroup parent_group(self) cdef class CoxGraph: cdef c_CoxGraph x diff --git a/src/sage/libs/coxeter3/coxeter.pyx b/src/sage/libs/coxeter3/coxeter.pyx index 3fd37303b1e..8ff555a68b5 100644 --- a/src/sage/libs/coxeter3/coxeter.pyx +++ b/src/sage/libs/coxeter3/coxeter.pyx @@ -14,11 +14,13 @@ Low level part of the interface to Fokko Ducloux's Coxeter 3 library #***************************************************************************** include "sage/ext/interrupt.pxi" -include "decl.pxi" +from .decl cimport * initConstants() -from sage.rings.all import Integer, ZZ +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing cdef class String: def __cinit__(self, s=""): @@ -279,12 +281,12 @@ cdef class CoxGroup(SageObject): cdef Type t = Type(type) cdef c_CoxGroup* c_W = coxeterGroup(t.x, rank) self.x = c_W - self.out_ordering = dict(zip(range(1, rank+1), ordering)) - self.in_ordering = dict([(b,a) for a,b in self.out_ordering.items()]) + self.out_ordering = {i+1: o for i,o in enumerate(ordering)} + self.in_ordering = {self.out_ordering[a]: a for a in self.out_ordering} # Check that the Coxeter matrices match up. if self.coxeter_matrix() != coxeter_matrix(self.cartan_type): - print "Warning, differing Coxeter matrices" + print("Warning, differing Coxeter matrices") @classmethod def _ordering_from_cartan_type(cls, cartan_type): @@ -299,7 +301,6 @@ cdef class CoxGroup(SageObject): [1, 2, 3, 4, 5] """ from sage.misc.all import srange - from sage.rings.all import Integer t = cartan_type.type() r = cartan_type.rank() is_affine = cartan_type.is_affine() @@ -313,10 +314,10 @@ cdef class CoxGroup(SageObject): if is_affine: raise NotImplementedError else: - return map(Integer, [1, 2]) + return [Integer(1), Integer(2)] elif t in ['E']: if is_affine: - return srange(1, r) + [Integer(0)] + return srange(1, r) + [ZZ.zero()] else: return srange(1, r+1) else: @@ -537,7 +538,7 @@ cdef class CoxGroup(SageObject): """ Make all of the elements of a finite Coxeter group available. - Raises an error if W is not finite + Raises an error if ``self`` is not finite. EXAMPLES:: @@ -548,10 +549,10 @@ cdef class CoxGroup(SageObject): sage: W.full_context() # optional - coxeter3 Traceback (most recent call last): ... - TypeError: Group needs to be finite. + TypeError: group needs to be finite """ if not self.is_finite(): - raise TypeError, "Group needs to be finite." + raise TypeError("group needs to be finite") cdef c_FiniteCoxGroup* fcoxgroup = (self.x) if not fcoxgroup.isFullContext(): fcoxgroup.fullContext() @@ -572,7 +573,7 @@ cdef class CoxGroup(SageObject): sage: W.long_element() # optional - coxeter3 Traceback (most recent call last): ... - TypeError: Group needs to be finite. + TypeError: group needs to be finite """ self.full_context() cdef c_FiniteCoxGroup* fcoxgroup = (self.x) @@ -582,7 +583,7 @@ cdef class CoxGroup(SageObject): def __call__(self, w): """ - Return a reduced expression for `w`. + Return a reduced expression for ``w``. INPUT: @@ -678,10 +679,10 @@ cdef class CoxGroupElement: [4, 5, 4] """ self.group = (group).x - self._parent = group + self._parent_group = group self.word.reset() for i in w: - self.word.append_letter(self._parent.in_ordering[i]) + self.word.append_letter(self._parent_group.in_ordering[i]) if normal_form: self.group.normalForm(self.word) @@ -719,7 +720,7 @@ cdef class CoxGroupElement: sage: W = CoxGroup(['A', 4]) # optional - coxeter3 sage: w = CoxGroupElement(W, [1,2,3,2,3]) # optional - coxeter3 sage: w._coxnumber() # optional - coxeter3 - 7L + 7 """ return self.group.extendContext(self.word) @@ -732,7 +733,7 @@ cdef class CoxGroupElement: sage: w = W([1,2,3]) # optional - coxeter3 sage: TestSuite(w).run() # optional - coxeter3 """ - return (CoxGroupElement, (self._parent, list(self))) + return (CoxGroupElement, (self._parent_group, list(self))) def __invert__(self): """ @@ -747,11 +748,11 @@ cdef class CoxGroupElement: sage: ~w # optional - coxeter3 [3, 2, 1] """ - return CoxGroupElement(self._parent, reversed(self)) + return CoxGroupElement(self._parent_group, reversed(self)) inverse = __invert__ - cpdef CoxGroup parent(self): + cpdef CoxGroup parent_group(self): """ Return the parent Coxeter group for this element. @@ -760,11 +761,11 @@ cdef class CoxGroupElement: sage: from sage.libs.coxeter3.coxeter import * # optional - coxeter3 sage: W = CoxGroup(['A',5]) # optional - coxeter3 sage: w = W([1,2,3]) # optional - coxeter3 - sage: w.parent() # optional - coxeter3 + sage: w.parent_group() # optional - coxeter3 Coxeter group of type A and rank 5 """ - return self._parent + return self._parent_group def __getitem__(self, i): """ @@ -798,7 +799,7 @@ cdef class CoxGroupElement: if i >= len(self): raise IndexError, "The index (%d) is out of range."%i - return self._parent.out_ordering[self.word.get_index(i)] + return self._parent_group.out_ordering[self.word.get_index(i)] def __repr__(self): """ @@ -830,7 +831,7 @@ cdef class CoxGroupElement: sage: hash(w) == hash(v) # optional - coxeter3 False """ - return hash((self.__class__.__name__, self.parent(), tuple(self))) + return hash((self.__class__.__name__, self.parent_group(), tuple(self))) def __richcmp__(CoxGroupElement self, other, int op): """ @@ -858,8 +859,8 @@ cdef class CoxGroupElement: if type(other) != type(self): return False - s_p = self.parent() - o_p = other.parent() + s_p = self.parent_group() + o_p = other.parent_group() s_l = list(self) o_l = list(other) @@ -917,7 +918,7 @@ cdef class CoxGroupElement: sage: w.left_descents() # optional - coxeter3 [1, 2] """ - return LFlags_to_list(self._parent, self.group.ldescent(self.word)) + return LFlags_to_list(self._parent_group, self.group.ldescent(self.word)) def right_descents(self): """ @@ -931,7 +932,7 @@ cdef class CoxGroupElement: sage: w.right_descents() # optional - coxeter3 [1, 2] """ - return LFlags_to_list(self._parent, self.group.rdescent(self.word)) + return LFlags_to_list(self._parent_group, self.group.rdescent(self.word)) def bruhat_le(self, w): """ @@ -950,7 +951,7 @@ cdef class CoxGroupElement: sage: w.bruhat_le(v) # optional - coxeter3 False """ - cdef CoxGroupElement ww = CoxGroupElement(self._parent, w) + cdef CoxGroupElement ww = CoxGroupElement(self._parent_group, w) return self.group.inOrder_word(self.word, ww.word) def is_two_sided_descent(self, s): @@ -965,14 +966,14 @@ cdef class CoxGroupElement: sage: x.is_two_sided_descent(1) # optional - coxeter3 True """ - cdef Generator ss = self._parent.in_ordering[s] + cdef Generator ss = self._parent_group.in_ordering[s] return self.group.isDescent(self.word, s) cdef CoxGroupElement _new(self): """ Return a new copy of this element. """ - cdef CoxGroupElement res = CoxGroupElement(self.parent(), []) + cdef CoxGroupElement res = CoxGroupElement(self.parent_group(), []) res.word.set(self.word) return res @@ -1069,7 +1070,7 @@ cdef class CoxGroupElement: sage: W([1,2,1]).poincare_polynomial() # optional - coxeter3 t^3 + 2*t^2 + 2*t + 1 """ - cdef CoxGroup W = self.parent() + cdef CoxGroup W = self.parent_group() cdef c_List_CoxWord result = c_List_CoxWord_factory(0) cdef CoxGroupElement id = CoxGroupElement(W, []) cdef CoxGroupElement ww = CoxGroupElement(W, self) @@ -1084,7 +1085,7 @@ cdef class CoxGroupElement: def kazhdan_lusztig_polynomial(self, v): """ - Return the Kazhdan-Lusztig polynomial `P_{u,v}` where `u` is this element. + Return the Kazhdan-Lusztig polynomial `P_{u,v}` where `u` is ``self``. Currently this is a bit inefficient as it constructs the polynomial from its string representation. @@ -1101,21 +1102,24 @@ cdef class CoxGroupElement: from sage.all import ZZ cdef CoxGroupElement vv if not isinstance(v, CoxGroupElement): - vv = CoxGroupElement(self._parent, v) + vv = CoxGroupElement(self._parent_group, v) else: vv = v - ZZq = ZZ['q'] + ZZq = PolynomialRing(ZZ, 'q') if not self.group.inOrder_word(self.word, vv.word): - return ZZq(0) + return ZZq.zero() cdef CoxNbr x = self.group.extendContext(self.word) cdef CoxNbr y = self.group.extendContext(vv.word) cdef c_KLPol kl_poly = self.group.klPol(x, y) - - cdef String s = String() - klpoly_append(s.x, kl_poly, "q") - return ZZq(str(s)) + if kl_poly.isZero(): + return ZZq.zero() + cdef int i + cdef list l = [] + for 0 <= i <= kl_poly.deg(): + l.append(kl_poly[i]) + return ZZq(l) def mu_coefficient(self, v): r""" @@ -1135,7 +1139,7 @@ cdef class CoxGroupElement: 1 """ from sage.all import ZZ - cdef CoxGroupElement vv = CoxGroupElement(self._parent, v) + cdef CoxGroupElement vv = CoxGroupElement(self._parent_group, v) cdef CoxNbr x = self.group.extendContext(self.word) cdef CoxNbr y = self.group.extendContext(vv.word) return ZZ(self.group.mu(x,y)) diff --git a/src/sage/libs/coxeter3/coxeter_group.py b/src/sage/libs/coxeter3/coxeter_group.py index c67cd2a7b98..73d40a73b9d 100644 --- a/src/sage/libs/coxeter3/coxeter_group.py +++ b/src/sage/libs/coxeter3/coxeter_group.py @@ -7,16 +7,18 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.groups.group import Group + from sage.libs.coxeter3.coxeter import get_CoxGroup, CoxGroupElement -from sage.structure.element import MultiplicativeGroupElement from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper -from sage.categories.all import CoxeterGroups, FiniteCoxeterGroups +from sage.categories.all import CoxeterGroups from sage.structure.parent import Parent +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + class CoxeterGroup(UniqueRepresentation, Parent): @staticmethod def __classcall__(cls, cartan_type, *args, **options): @@ -41,7 +43,10 @@ def __init__(self, cartan_type): Coxeter group of type ['A', 2] implemented by Coxeter3 sage: TestSuite(CoxeterGroup(['A',2])).run() # optional - coxeter3 """ - Parent.__init__(self, category=(FiniteCoxeterGroups() if cartan_type.is_finite() else CoxeterGroups())) + category = CoxeterGroups() + if cartan_type.is_finite(): + category = category.Finite() + Parent.__init__(self, category=category) self._coxgroup = get_CoxGroup(cartan_type) self._cartan_type = cartan_type @@ -87,10 +92,10 @@ def index_set(self): sage: W = CoxeterGroup(['A', 3], implementation='coxeter3') # optional - coxeter3 sage: W.index_set() # optional - coxeter3 - [1, 2, 3] + (1, 2, 3) sage: C = CoxeterGroup(['A', 3,1], implementation='coxeter3') # optional - coxeter3 sage: C.index_set() # optional - coxeter3 - [0, 1, 2, 3] + (0, 1, 2, 3) """ return self.cartan_type().index_set() #return range(1, self.rank()+1) @@ -131,7 +136,7 @@ def one(self): [] """ - return self([]) + return self.element_class(self, []) def simple_reflections(self): """ @@ -145,7 +150,7 @@ def simple_reflections(self): [2, 1, 2] """ from sage.combinat.family import Family - return Family(self.index_set(), lambda i: self([i])) + return Family(self.index_set(), lambda i: self.element_class(self, [i])) gens = simple_reflections @@ -187,7 +192,7 @@ def length(self, x): 0 """ - return len(x.value) + return x.length() @cached_method def coxeter_matrix(self): @@ -234,7 +239,7 @@ def _an_element_(self): [] """ - return self([]) + return self.element_class(self, []) def m(self, i, j): """ @@ -307,22 +312,25 @@ def kazhdan_lusztig_polynomial(self, u, v, constant_term_one=True): TESTS: - We check that Coxeter3 and Sage's implementation give the same results:: + We check that Coxeter3 and Sage's implementation give the same results:: sage: C = CoxeterGroup(['B', 3], implementation='coxeter3') # optional - coxeter3 sage: W = WeylGroup("B3",prefix="s") - sage: [s1,s2,s3]=W.simple_reflections() - sage: R.=LaurentPolynomialRing(QQ) - sage: KL=KazhdanLusztigPolynomial(W,q) + sage: [s1,s2,s3] = W.simple_reflections() + sage: R. = LaurentPolynomialRing(QQ) + sage: KL = KazhdanLusztigPolynomial(W,q) sage: all(KL.P(1,w) == C.kazhdan_lusztig_polynomial([],w.reduced_word()) for w in W) # optional - coxeter3 # long (15s) True """ u, v = self(u), self(v) p = u.value.kazhdan_lusztig_polynomial(v.value) if constant_term_one: - return u.value.kazhdan_lusztig_polynomial(v.value) - q = p.parent().gen() - return q**(v.length()-u.length())*p.substitute(q=q**(-2)) + return p + ZZq = PolynomialRing(ZZ, 'q', sparse=True) + # This is the same as q**len_diff * p(q**(-2)) + len_diff = v.length()-u.length() + d = {-2*deg+len_diff: coeff for deg,coeff in enumerate(p) if coeff != 0} + return ZZq(d) def parabolic_kazhdan_lusztig_polynomial(self, u, v, J, constant_term_one=True): """ @@ -378,17 +386,19 @@ def parabolic_kazhdan_lusztig_polynomial(self, u, v, J, constant_term_one=True): sage: type(W.parabolic_kazhdan_lusztig_polynomial([2],[],[1])) # optional - coxeter3 """ - from sage.rings.all import ZZ u = self(u) v = self(v) if any(d in J for d in u.descents()) or any(d in J for d in v.descents()): raise ValueError("u and v have to be minimal coset representatives") - P = ZZ['q'] - q = P.gen() - subgroup = [ z for z in self.weak_order_ideal(lambda x: set(x.descents()).issubset(set(J))) if (u*z).bruhat_le(v) ] + J_set = set(J) + WOI = self.weak_order_ideal(lambda x: J_set.issuperset(x.descents())) if constant_term_one: - return P.sum((-1)**(z.length())*self.kazhdan_lusztig_polynomial(u*z,v) for z in subgroup) - return P.sum((-q)**(z.length())*self.kazhdan_lusztig_polynomial(u*z,v, constant_term_one=False) for z in subgroup) + P = PolynomialRing(ZZ, 'q') + return P.sum((-1)**(z.length()) * self.kazhdan_lusztig_polynomial(u*z,v) + for z in WOI if (u*z).bruhat_le(v)) + P = PolynomialRing(ZZ, 'q', sparse=True) + return P.sum((-1)**(z.length()) * self.kazhdan_lusztig_polynomial(u*z,v, constant_term_one=False).shift(z.length()) + for z in WOI if (u*z).bruhat_le(v)) class Element(ElementWrapper): @@ -406,16 +416,6 @@ def __init__(self, parent, x): x = CoxGroupElement(parent._coxgroup, x).reduced() ElementWrapper.__init__(self, parent, x) - def _repr_(self): - """ - TESTS:: - - sage: W = CoxeterGroup(['A', 3], implementation='coxeter3') # optional - coxeter3 - sage: W([1,2,1]) # indirect doctest # optional - coxeter3 - [1, 2, 1] - """ - return repr(self.value) - def __iter__(self): """ Return an iterator for the elements in the reduced word. @@ -485,7 +485,7 @@ def __invert__(self): sage: ~w # optional - coxeter3 [3, 2, 1] """ - return self.parent()(~self.value) + return self.__class__(self.parent(), ~self.value) inverse = __invert__ @@ -501,8 +501,7 @@ def __getitem__(self, i): 2 """ - if i >= len(self): - raise IndexError + # Allow the error message to be raised by the underlying element return self.value[i] def _mul_(self, y): @@ -516,8 +515,7 @@ def _mul_(self, y): sage: s[1]*s[2]*s[1] # optional - coxeter3 [1, 2, 1] """ - result = self.value * y.value - return self.parent()(result) + return self.__class__(self.parent(), self.value * y.value) def __eq__(self, y): """ @@ -544,11 +542,15 @@ def __len__(self): EXAMPLES:: sage: W = CoxeterGroup(['A', 3], implementation='coxeter3') # optional - coxeter3 - sage: len(W([1,2,1])) # optional - coxeter3 + sage: w = W([1,2,1]) # optional - coxeter3 + sage: w.length() # optional - coxeter3 + 3 + sage: len(w) # optional - coxeter3 3 """ - return self.parent().length(self) + return len(self.value) + length = __len__ def bruhat_le(self, v): """ @@ -671,7 +673,7 @@ def action_on_rational_function(self, f): raise ValueError("the number of generators for the polynomial ring must be the same as the rank of the root system") basis_elements = [alpha[i] for i in W.index_set()] - basis_to_order = dict([(s, i) for i, s in enumerate(W.index_set())]) + basis_to_order = {s: i for i, s in enumerate(W.index_set())} results = [] for poly in [f.numerator(), f.denominator()]: @@ -680,7 +682,7 @@ def action_on_rational_function(self, f): for exponent in exponents: #Construct something in the root lattice from the exponent vector - exponent = sum([e*b for e, b in zip(exponent, basis_elements)]) + exponent = sum(e*b for e, b in zip(exponent, basis_elements)) exponent = self.action(exponent) monomial = 1 @@ -693,3 +695,4 @@ def action_on_rational_function(self, f): numerator, denominator = results return numerator / denominator + diff --git a/src/sage/libs/coxeter3/decl.pxi b/src/sage/libs/coxeter3/decl.pxd similarity index 97% rename from src/sage/libs/coxeter3/decl.pxi rename to src/sage/libs/coxeter3/decl.pxd index 2fa90f8f689..526051c1cb3 100644 --- a/src/sage/libs/coxeter3/decl.pxi +++ b/src/sage/libs/coxeter3/decl.pxd @@ -91,8 +91,10 @@ cdef extern from "graph.h": # KLPol # ############### cdef extern from "kl.h": - ctypedef struct c_KLPol "kl::KLPol": - pass + cdef cppclass c_KLPol "kl::KLPol": + const unsigned short& operator[](int) + unsigned long deg() + int isZero() cdef extern from "polynomials.h": c_String klpoly_append "polynomials::append"(c_String str, c_KLPol, char* x) diff --git a/src/sage/libs/ecl.pyx b/src/sage/libs/ecl.pyx index e291c0c0b74..f41c233a0cc 100644 --- a/src/sage/libs/ecl.pyx +++ b/src/sage/libs/ecl.pyx @@ -14,13 +14,16 @@ Library interface to Embeddable Common Lisp (ECL) #rationals to SAGE types Integer and Rational. These parts could easily be #adapted to work with pure Python types. -include "sage/ext/signals.pxi" include "sage/ext/interrupt.pxi" include "sage/ext/cdefs.pxi" +from libc.stdlib cimport abort +from libc.signal cimport SIGINT, SIGBUS, SIGSEGV, SIGCHLD +from libc.signal cimport raise_ as signal_raise +from posix.signal cimport sigaction, sigaction_t + from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational -from sage.rings.rational import Rational #it would be preferrable to let bint_symbolp wrap an efficient macro #but the macro provided in object.h doesn't seem to work @@ -40,9 +43,9 @@ cdef bint bint_rationalp(cl_object obj): cdef extern from "eclsig.h": int ecl_sig_on() except 0 void ecl_sig_off() - cdef Sigaction ecl_sigint_handler - cdef Sigaction ecl_sigbus_handler - cdef Sigaction ecl_sigsegv_handler + cdef sigaction_t ecl_sigint_handler + cdef sigaction_t ecl_sigbus_handler + cdef sigaction_t ecl_sigsegv_handler cdef mpz_t ecl_mpz_from_bignum(cl_object obj) cdef cl_object ecl_bignum_from_mpz(mpz_t num) @@ -232,7 +235,7 @@ def init_ecl(): global read_from_string_clobj global ecl_has_booted cdef char *argv[1] - cdef Sigaction sage_action[32] + cdef sigaction_t sage_action[32] cdef int i if ecl_has_booted: @@ -262,7 +265,7 @@ def init_ecl(): sigaction(SIGSEGV, NULL, &ecl_sigsegv_handler) #verify that no SIGCHLD handler was installed - cdef Sigaction sig_test + cdef sigaction_t sig_test sigaction(SIGCHLD, NULL, &sig_test) assert sage_action[SIGCHLD].sa_handler == NULL # Sage does not set SIGCHLD handler assert sig_test.sa_handler == NULL # And ECL bootup did not set one diff --git a/src/sage/libs/flint/fmpq.pxd b/src/sage/libs/flint/fmpq.pxd index c2092583031..f1e8dcc2d8a 100644 --- a/src/sage/libs/flint/fmpq.pxd +++ b/src/sage/libs/flint/fmpq.pxd @@ -9,3 +9,11 @@ cdef extern from "flint/fmpq.h": # Conversion void fmpq_set_mpq(fmpq_t, const mpq_t) void fmpq_get_mpq(mpq_t, const fmpq_t) + + # Comparison + int fmpq_is_zero(const fmpq_t res) + int fmpq_is_one(const fmpq_t res) + int fmpq_equal(const fmpq_t x, const fmpq_t y) + int fmpq_sgn(const fmpq_t) + int fmpq_cmp(const fmpq_t x, const fmpq_t y) + diff --git a/src/sage/libs/flint/fmpq_poly.pxd b/src/sage/libs/flint/fmpq_poly.pxd index fb77f2e4a56..f3b75328a48 100644 --- a/src/sage/libs/flint/fmpq_poly.pxd +++ b/src/sage/libs/flint/fmpq_poly.pxd @@ -138,6 +138,12 @@ cdef extern from "flint/fmpq_poly.h": void fmpq_poly_resultant(fmpq_t, const fmpq_poly_t, const fmpq_poly_t) + # Power series division + void fmpq_poly_inv_series_newton(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_inv_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_div_series( + fmpq_poly_t, const fmpq_poly_t, const fmpq_poly_t, slong) + # Derivative and integral void fmpq_poly_derivative(fmpq_poly_t, const fmpq_poly_t) void fmpq_poly_integral(fmpq_poly_t, const fmpq_poly_t) @@ -163,6 +169,20 @@ cdef extern from "flint/fmpq_poly.h": int fmpq_poly_is_monic(const fmpq_poly_t) void fmpq_poly_make_monic(fmpq_poly_t, const fmpq_poly_t) + # Transcendental functions + void fmpq_poly_log_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_exp_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_atan_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_atanh_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_asin_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_asinh_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_tan_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_sin_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_cos_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_sinh_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_cosh_series(fmpq_poly_t, const fmpq_poly_t, slong) + void fmpq_poly_tanh_series(fmpq_poly_t, const fmpq_poly_t, slong) + # since the fmpq_poly header seems to be lacking this inline function cdef inline sage_fmpq_poly_max_limbs(const fmpq_poly_t poly): return _fmpz_vec_max_limbs(fmpq_poly_numref(poly), fmpq_poly_length(poly)) diff --git a/src/sage/libs/flint/fmpz.pxd b/src/sage/libs/flint/fmpz.pxd index dc497331ce0..99e748be1a4 100644 --- a/src/sage/libs/flint/fmpz.pxd +++ b/src/sage/libs/flint/fmpz.pxd @@ -1,3 +1,5 @@ +# distutils: libraries = flint + from libc.stdio cimport FILE from sage.libs.gmp.types cimport mpz_t from sage.libs.flint.types cimport * @@ -45,6 +47,8 @@ cdef extern from "flint/fmpz.h": int fmpz_fits_si(fmpz_t f) void fmpz_zero(fmpz_t f) void fmpz_one(fmpz_t f) + void fmpz_setbit(fmpz_t f, ulong i) + int fmpz_tstbit(fmpz_t f, ulong i) # Input and output int fmpz_read(fmpz_t) diff --git a/src/sage/libs/flint/fmpz_mat.pxd b/src/sage/libs/flint/fmpz_mat.pxd index f3b7b30c03d..c0c6de326e8 100644 --- a/src/sage/libs/flint/fmpz_mat.pxd +++ b/src/sage/libs/flint/fmpz_mat.pxd @@ -30,3 +30,5 @@ cdef extern from "flint/fmpz_mat.h": long fmpz_mat_rank(const fmpz_mat_t A) int fmpz_mat_solve(fmpz_mat_t X, fmpz_t den, const fmpz_mat_t A, const fmpz_mat_t B) long fmpz_mat_nullspace(fmpz_mat_t B, const fmpz_mat_t A) + void fmpz_mat_hnf(fmpz_mat_t H , const fmpz_mat_t A) + void fmpz_mat_hnf_transform(fmpz_mat_t H, fmpz_mat_t U, const fmpz_mat_t A) diff --git a/src/sage/libs/fplll/fplll.pxd b/src/sage/libs/fplll/fplll.pxd index 362158aa17f..772b4a1f01e 100644 --- a/src/sage/libs/fplll/fplll.pxd +++ b/src/sage/libs/fplll/fplll.pxd @@ -1,3 +1,7 @@ +# distutils: language = c++ +# distutils: libraries = gmp mpfr fplll +# distutils: extra_compile_args = -DFPLLL_V3_COMPAT + from sage.libs.gmp.types cimport mpz_t # diff --git a/src/sage/libs/gap/gap_functions.py b/src/sage/libs/gap/gap_functions.py index bda8c8ae03d..0c8004b6c2d 100644 --- a/src/sage/libs/gap/gap_functions.py +++ b/src/sage/libs/gap/gap_functions.py @@ -38,6 +38,7 @@ 'Apply', 'AsGroup', 'Assert', + 'AtlasGroup', 'AutomorphismGroup', 'BaseOfGroup', 'Basis', @@ -200,6 +201,7 @@ 'FreeProduct', 'FreeSemigroup', 'FrobeniusAutomorphism', + 'FullRowSpace', 'GF', 'GL', 'GQuotients', diff --git a/src/sage/libs/giac.py b/src/sage/libs/giac.py new file mode 100644 index 00000000000..bb394b4a8d5 --- /dev/null +++ b/src/sage/libs/giac.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +""" +Wrappers for Giac functions + +We provide a python function to compute and convert to sage a groebner +basis using the giacpy module. + +AUTHORS: + +- Martin Albrecht (2015-07-01): initial version +- Han Frederic (2015-07-01): initial version + +EXAMPLES:: + + sage: from sage.libs.giac import groebner_basis as gb_giac # optional - giacpy + sage: P = PolynomialRing(QQ, 6, 'x') + sage: I = sage.rings.ideal.Cyclic(P) + sage: B = gb_giac(I.gens()) # optional - giacpy, random + sage: B # optional - giacpy + Polynomial Sequence with 45 Polynomials in 6 Variables +""" + +# ***************************************************************************** +# Copyright (C) 2013 Frederic Han +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.structure.proof.all import polynomial as proof_polynomial +from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence + +# Remarks for doctests: +# 1) The first time that the c++ library giac is loaded a message appears. +# This message is version and arch dependant. +# 2) When proba_epsilon is too bad (>1e-6?) setting it to a better value +# will give an additional message like the following one: +# Restoring proba epsilon to 1e-6 from 1e-12 +# (it looks like in internal giac changes this also to not work with a too bad probability) + + +class GiacSettingsDefaultContext: + """ + Context preserve libgiac settings. + """ + + def __enter__(self): + """ + EXAMPLE:: + + sage: from sage.libs.giac import GiacSettingsDefaultContext # optional - giacpy + sage: from giacpy import giacsettings # optional - giacpy + sage: giacsettings.proba_epsilon = 1e-16 # optional - giacpy + sage: with GiacSettingsDefaultContext(): giacsettings.proba_epsilon = 1e-12 # optional - giacpy + sage: giacsettings.proba_epsilon < 1e-14 # optional - giacpy + True + + """ + try: + from giacpy import giacsettings, libgiac + except ImportError: + raise ImportError("""One of the optional packages giac or giacpy is missing""") + + self.proba_epsilon = giacsettings.proba_epsilon + self.threads = giacsettings.threads + # Change the debug level at the end to not have messages at each modification + self.debuginfolevel = libgiac('debug_infolevel()') + + def __exit__(self, typ, value, tb): + """ + EXAMPLE:: + + sage: from sage.libs.giac import GiacSettingsDefaultContext # optional - giacpy + sage: from giacpy import giacsettings # optional - giacpy + sage: giacsettings.proba_epsilon = 1e-16 # optional - giacpy + sage: with GiacSettingsDefaultContext(): giacsettings.proba_epsilon = 1e-30 # optional - giacpy + sage: giacsettings.proba_epsilon < 1e-20 # optional - giacpy + False + + """ + try: + from giacpy import giacsettings, libgiac + except ImportError: + raise ImportError("""One of the optional packages giac or giacpy is missing""") + + # Restore the debug level first to not have messages at each modification + libgiac('debug_infolevel')(self.debuginfolevel) + # NB: giacsettings.epsilon has a different meaning that giacsettings.proba_epsilon. + giacsettings.proba_epsilon = self.proba_epsilon + giacsettings.threads = self.threads + +def local_giacsettings(func): + """ + Decorator to preserve Giac's proba_epsilon and threads settings. + + EXAMPLE:: + + sage: def testf(a,b): # optional - giacpy + ....: giacsettings.proba_epsilon = a/100 + ....: giacsettings.threads = b+2 + ....: return (giacsettings.proba_epsilon, giacsettings.threads) + + sage: from giacpy import giacsettings # optional - giacpy + sage: from sage.libs.giac import local_giacsettings # optional - giacpy + sage: gporig, gtorig = (giacsettings.proba_epsilon,giacsettings.threads) # optional - giacpy + sage: gp, gt = local_giacsettings(testf)(giacsettings.proba_epsilon,giacsettings.threads) # optional - giacpy + sage: gporig == giacsettings.proba_epsilon # optional - giacpy + True + sage: gtorig == giacsettings.threads # optional - giacpy + True + sage: gp0): + raise ValueError("Variables names %s conflict in giac. Change them or purge them from in giac with libgiac.purge(\'%s\')" + %(problematicnames, problematicnames[0])) + + if K.is_prime_field() and p == 0: + F = libgiac(gens) + elif K.is_prime_field() and p < 2**31: + F = (libgiac(gens) % p) + else: + raise NotImplementedError("Only prime fields of cardinal < 2^31 are implemented in Giac for Groebner bases.") + + if P.term_order() != "degrevlex": + raise NotImplementedError("Only degrevlex term orderings are supported in Giac Groebner bases.") + + # proof or probabilistic reconstruction + if proba_epsilon is None: + if proof_polynomial(): + giacsettings.proba_epsilon = 0 + else: + giacsettings.proba_epsilon = 1e-15 + else: + giacsettings.proba_epsilon = proba_epsilon + + # prot + if prot: + libgiac('debug_infolevel(2)') + + # threads + if threads is not None: + giacsettings.threads = threads + + # compute de groebner basis with giac + gb_giac = F.gbasis([P.gens()], "revlex") + + return PolynomialSequence(gb_giac, P, immutable=True) diff --git a/src/sage/ext/random.pxi b/src/sage/libs/gmp/randomize.pxd similarity index 71% rename from src/sage/ext/random.pxi rename to src/sage/libs/gmp/randomize.pxd index 44b579098ab..49f32881667 100644 --- a/src/sage/ext/random.pxi +++ b/src/sage/libs/gmp/randomize.pxd @@ -1,18 +1,14 @@ -########################################################## -# Setup the c-library and GMP random number generators. -# seed it when module is loaded. +""" +Generate random rationals in Sage +""" -# The c_random() method on randstate objects gives a value -# 0 <= n <= SAGE_RAND_MAX -cdef int SAGE_RAND_MAX = 2147483647 # 2^31 - 1 - - -from sage.libs.gmp.all cimport * -from sage.misc.randstate cimport randstate, current_randstate +from sage.libs.gmp.mpz cimport * +from sage.libs.gmp.mpq cimport * +from sage.misc.randstate cimport randstate, current_randstate, SAGE_RAND_MAX ########################### -cdef void mpq_randomize_entry(mpq_t x, mpz_t num_bound, mpz_t den_bound): +cdef inline void mpq_randomize_entry(mpq_t x, mpz_t num_bound, mpz_t den_bound): cdef randstate rstate = current_randstate() mpz_urandomm(mpq_numref(x), rstate.gmp_state, num_bound) mpz_urandomm(mpq_denref(x), rstate.gmp_state, den_bound) @@ -22,7 +18,7 @@ cdef void mpq_randomize_entry(mpq_t x, mpz_t num_bound, mpz_t den_bound): mpz_mul_si(mpq_numref(x), mpq_numref(x), -1) mpq_canonicalize(x) -cdef void mpq_randomize_entry_as_int(mpq_t x, mpz_t bound): +cdef inline void mpq_randomize_entry_as_int(mpq_t x, mpz_t bound): cdef randstate rstate = current_randstate() mpz_urandomm(mpq_numref(x), rstate.gmp_state, bound) mpz_set_si(mpq_denref(x), 1) diff --git a/src/sage/libs/gmpxx.pxd b/src/sage/libs/gmpxx.pxd new file mode 100644 index 00000000000..c519083e940 --- /dev/null +++ b/src/sage/libs/gmpxx.pxd @@ -0,0 +1,17 @@ +# distutils: libraries = gmpxx gmp + +from sage.libs.gmp.types cimport mpz_t, mpq_t + +cdef extern from 'gmpxx.h': + cdef cppclass mpz_class: + mpz_class() + mpz_class(int i) + mpz_class(mpz_t z) + mpz_class(mpz_class) + mpz_t get_mpz_t() + mpz_class operator%(mpz_class, mpz_class) + + cdef cppclass mpq_class: + mpq_class() + mpz_t get_num_mpz_t() + mpz_t get_den_mpz_t() diff --git a/src/sage/libs/lcalc/lcalc_Lfunction.pyx b/src/sage/libs/lcalc/lcalc_Lfunction.pyx index 69314603e86..8f2a81aabcd 100644 --- a/src/sage/libs/lcalc/lcalc_Lfunction.pyx +++ b/src/sage/libs/lcalc/lcalc_Lfunction.pyx @@ -10,25 +10,21 @@ AUTHORS: - Yann Laigle-Chapuy (2009): refactored - Rishikesh (2009): initial version """ + #***************************************************************************** # Copyright (C) 2009 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** include "sage/ext/interrupt.pxi" include "sage/ext/cdefs.pxi" -include "sage/libs/mpfr.pxd" +from sage.libs.mpfr cimport * from sage.rings.integer cimport Integer from sage.rings.complex_number cimport ComplexNumber @@ -139,14 +135,14 @@ cdef class Lfunction: sage: chi=DirichletGroup(5)[2] #This is a quadratic character sage: from sage.libs.lcalc.lcalc_Lfunction import * sage: L=Lfunction_from_character(chi, type="int") - sage: L.value(.5) - 0.231750947504... + 5.75329642226...e-18*I + sage: L.value(.5) # abs tol 3e-15 + 0.231750947504016 + 5.75329642226136e-18*I sage: L.value(.2+.4*I) 0.102558603193... + 0.190840777924...*I sage: L=Lfunction_from_character(chi, type="double") - sage: L.value(.6) - 0.274633355856... + 6.59869267328...e-18*I + sage: L.value(.6) # abs tol 3e-15 + 0.274633355856345 + 6.59869267328199e-18*I sage: L.value(.6+I) 0.362258705721... + 0.433888250620...*I diff --git a/src/sage/libs/libecm.pyx b/src/sage/libs/libecm.pyx index caba9f4bf4c..fd99f57ed26 100644 --- a/src/sage/libs/libecm.pyx +++ b/src/sage/libs/libecm.pyx @@ -11,18 +11,21 @@ AUTHORS: - Robert L Miller (2008-01-21): library interface (clone of ecmfactor.c) - Jeroen Demeyer (2012-03-29): signal handling, documentation +- Paul Zimmermann (2011-05-22) -- added input/output of sigma EXAMPLES:: sage: from sage.libs.libecm import ecmfactor sage: result = ecmfactor(999, 0.00) - sage: result in [(True, 27), (True, 37), (True, 999)] + sage: result[0] and (result[1] in [27, 37, 999]) True sage: result = ecmfactor(999, 0.00, verbose=True) Performing one curve with B1=0 Found factor in step 1: ... - sage: result in [(True, 27), (True, 37), (True, 999)] + sage: result[0] and (result[1] in [27, 37, 999]) True + sage: ecmfactor(2^128+1,1000,sigma=227140902) + (True, 5704689200685129054721, 227140902) """ #***************************************************************************** # Copyright (C) 2008 Robert Miller @@ -41,12 +44,16 @@ from sage.rings.integer cimport Integer cdef extern from "ecm.h": ctypedef struct __ecm_param_struct: - pass + int method + mpz_t x + mpz_t sigma ctypedef __ecm_param_struct ecm_params[1] int ecm_factor (mpz_t, mpz_t, double, ecm_params) + void ecm_init (ecm_params) + void ecm_clear (ecm_params) int ECM_NO_FACTOR_FOUND -def ecmfactor(number, double B1, verbose=False): +def ecmfactor(number, double B1, verbose=False, sigma=0): """ Try to find a factor of a positive integer using ECM (Elliptic Curve Method). This function tries one elliptic curve. @@ -74,7 +81,14 @@ def ecmfactor(number, double B1, verbose=False): sage: factor(N) 2349023 * 79638304766856507377778616296087448490695649 sage: ecmfactor(N, 2e5) - (True, 2349023) + (True, 2349023, ...) + + If a factor was found, we can reproduce the factorization with the same + sigma value:: + + sage: N = 2^167 - 1 + sage: ecmfactor(N, 2e5, sigma=1473308225) + (True, 2349023, 1473308225) With a smaller B1 bound, we may or may not succeed:: @@ -98,7 +112,7 @@ def ecmfactor(number, double B1, verbose=False): sage: factor(N) 359 * 1433 * 1489459109360039866456940197095433721664951999121 sage: ecmfactor(N, 1e3) # random - (True, 514447) + (True, 514447, 3475102204) We can ask for verbose output:: @@ -108,7 +122,7 @@ def ecmfactor(number, double B1, verbose=False): sage: ecmfactor(N, 100, verbose=True) Performing one curve with B1=100 Found factor in step 1: 11 - (True, 11) + (True, 11, ...) sage: ecmfactor(N/11, 100, verbose=True) Performing one curve with B1=100 Found no factor. @@ -127,7 +141,7 @@ def ecmfactor(number, double B1, verbose=False): Some special cases:: sage: ecmfactor(1, 100) - (True, 1) + (True, 1, ...) sage: ecmfactor(0, 100) Traceback (most recent call last): ... @@ -135,10 +149,12 @@ def ecmfactor(number, double B1, verbose=False): """ cdef mpz_t n, f cdef int res - cdef Integer sage_int_f, sage_int_number + cdef Integer sage_int_f, sage_int_number, sage_int_sigma + cdef ecm_params q sage_int_f = Integer(0) sage_int_number = Integer(number) + sage_int_sigma = Integer(sigma) if number <= 0: raise ValueError("Input number (%s) must be positive"%number) @@ -150,20 +166,24 @@ def ecmfactor(number, double B1, verbose=False): mpz_init(n) mpz_set(n, sage_int_number.value) mpz_init(f) # For potential factor + ecm_init(q) + mpz_set(q.sigma,sage_int_sigma.value) - res = ecm_factor(f, n, B1, NULL) + res = ecm_factor(f, n, B1, q) if res > 0: mpz_set(sage_int_f.value, f) + mpz_set(sage_int_sigma.value, q.sigma) mpz_clear(f) mpz_clear(n) + ecm_clear(q) sig_off() if res > 0: if verbose: print "Found factor in step %d: %d"%(res,sage_int_f) - return (True, sage_int_f) + return (True, sage_int_f, sage_int_sigma) elif res == ECM_NO_FACTOR_FOUND: if verbose: print "Found no factor." diff --git a/src/sage/libs/lrcalc/lrcalc.pxd b/src/sage/libs/lrcalc/lrcalc.pxd index e04d3413c5c..073347894ae 100644 --- a/src/sage/libs/lrcalc/lrcalc.pxd +++ b/src/sage/libs/lrcalc/lrcalc.pxd @@ -40,6 +40,12 @@ cdef extern from "lrcalc/vector.h": vector* vp_first(vecpair* vp) vector* vp_second(vecpair* vp) +cdef extern from "lrcalc/list.h": + cdef struct _list: + void **array + size_t allocated + size_t length + void l_free(_list *lst) cdef extern from "lrcalc/symfcn.h": long long lrcoef_c "lrcoef"(vector* outer, vector* inner1, vector* inner2) @@ -47,6 +53,7 @@ cdef extern from "lrcalc/symfcn.h": hashtab* skew_c "skew"(vector *outer, vector *inner, int maxrows) hashtab* coprod_c "coprod"(vector *part, int all) void fusion_reduce_c "fusion_reduce"(hashtab* ht, int rows, int cols, int opt_zero) + _list *quantum_reduce_c "quantum_reduce"(hashtab* ht, int rows, int col) ctypedef struct skewtab: vector *outer diff --git a/src/sage/libs/lrcalc/lrcalc.pyx b/src/sage/libs/lrcalc/lrcalc.pyx index 1d8f3f31ab3..7399300d0a9 100644 --- a/src/sage/libs/lrcalc/lrcalc.pyx +++ b/src/sage/libs/lrcalc/lrcalc.pyx @@ -185,8 +185,11 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.all import Integer -from sage.combinat.all import Partition, Permutation, SkewTableau +from sage.rings.integer cimport Integer +from sage.structure.parent cimport Parent +from sage.combinat.partition import _Partitions +from sage.combinat.permutation import Permutation +from sage.combinat.skew_tableau import SkewTableau cdef vector* iterable_to_vector(it): """ @@ -199,14 +202,15 @@ cdef vector* iterable_to_vector(it): [3, 2, 1] """ cdef vector* v - it = list(it) - cdef int n = len(it) + cdef list itr = list(it) + cdef int n = len(itr) + cdef int i v = v_new(n) for i from 0 <= i < n: - v.array[i] = int(it[i]) + v.array[i] = int(itr[i]) return v -cdef vector_to_list(vector *v): +cdef list vector_to_list(vector *v): """ Converts a lrcalc vector to Python list. @@ -218,7 +222,7 @@ cdef vector_to_list(vector *v): """ cdef int i, n n = v_length(v) - result = [None]*n + cdef list result = [None]*n for i from 0 <= i < n: result[i] = Integer(v_elem(v, i)) return result @@ -252,12 +256,15 @@ cdef skewtab_to_SkewTableau(skewtab *st): """ inner = vector_to_list(st.inner) outer = vector_to_list(st.outer) - return SkewTableau(expr=[[ inner[y] for y in range(len(outer))], - [[ st.matrix[x + y * st.cols]+1 for x in range(inner[y], outer[y]) ] for y in range(len(outer)-1,-1,-1) ]]) + return SkewTableau(expr=[[inner[y] for y in range(len(outer))], + [[st.matrix[x + y * st.cols] + 1 + for x in range(inner[y], outer[y])] + for y in range(len(outer) - 1, -1, -1)]]) def test_skewtab_to_SkewTableau(outer, inner): """ - A wrapper function for the cdef function ``skewtab_to_SkewTableau`` for testing purposes. + A wrapper function for the cdef function ``skewtab_to_SkewTableau`` + for testing purposes. It constructs the first LR skew tableau of shape ``outer/inner`` as an ``lrcalc`` ``skewtab``, and converts it to a @@ -279,7 +286,7 @@ def test_skewtab_to_SkewTableau(outer, inner): cdef skewtab* st = st_new(o, i, NULL, 0) return skewtab_to_SkewTableau(st) -cdef sf_hashtab_to_dict(hashtab *ht): +cdef dict sf_hashtab_to_dict(hashtab *ht): """ Return a dictionary representing a Schur function. The keys are partitions and the values are integers . @@ -292,15 +299,16 @@ cdef sf_hashtab_to_dict(hashtab *ht): sage: assert isinstance(mult([1],[1]),dict)#indirect doctest """ cdef hash_itr itr - result = {} + cdef dict result = {} + cdef list p hash_first(ht, itr) while hash_good(itr): p = vector_to_list( hash_key(itr)) - result[Partition(p)] = Integer(hash_intvalue(itr)) + result[_Partitions(p)] = Integer(hash_intvalue(itr)) hash_next(itr) return result -cdef schubert_hashtab_to_dict(hashtab *ht): +cdef dict schubert_hashtab_to_dict(hashtab *ht): """ Return a dictionary corresponding to a Schubert polynomial whose keys are permutations and whose values are integers . @@ -312,7 +320,7 @@ cdef schubert_hashtab_to_dict(hashtab *ht): {[3, 2, 1]: 1} """ cdef hash_itr itr - result = {} + cdef dict result = {} hash_first(ht, itr) while hash_good(itr): p = vector_to_list( hash_key(itr)) @@ -321,7 +329,7 @@ cdef schubert_hashtab_to_dict(hashtab *ht): return result -cdef vp_hashtab_to_dict(hashtab *ht): +cdef dict vp_hashtab_to_dict(hashtab *ht): """ Return a dictionary corresponding to the coproduct of a Schur function whose keys are pairs of partitions and whose values are integers . @@ -334,17 +342,16 @@ cdef vp_hashtab_to_dict(hashtab *ht): """ cdef hash_itr itr cdef vecpair* vp - result = {} + cdef dict result = {} hash_first(ht, itr) while hash_good(itr): vp = hash_key(itr) - p1 = Partition(vector_to_list(vp_first(vp))) - p2 = Partition(vector_to_list(vp_second(vp))) + p1 = _Partitions(vector_to_list(vp_first(vp))) + p2 = _Partitions(vector_to_list(vp_second(vp))) result[(p1, p2)] = Integer(hash_intvalue(itr)) hash_next(itr) return result - def lrcoef_unsafe(outer, inner1, inner2): r""" Compute a single Littlewood-Richardson coefficient. @@ -418,9 +425,9 @@ def lrcoef(outer, inner1, inner2): 0 """ - return lrcoef_unsafe(Partition(outer), Partition(inner1), Partition(inner2)) + return lrcoef_unsafe(_Partitions(outer), _Partitions(inner1), _Partitions(inner2)) -def mult(part1, part2, maxrows=None, level=None): +def mult(part1, part2, maxrows=None, level=None, quantum=None): r""" Compute a product of two Schur functions. @@ -429,20 +436,26 @@ def mult(part1, part2, maxrows=None, level=None): INPUT: - - ``part1`` -- a partition. + - ``part1`` -- a partition - - ``part2`` -- a partition. + - ``part2`` -- a partition - - ``maxrows`` -- an integer or None. + - ``maxrows`` -- (optional) an integer - - ``level`` -- an integer or None. + - ``level`` -- (optional) an integer + + - ``quantum`` -- (optional) an element of a ring If ``maxrows`` is specified, then only partitions with at most - this number of rows is included in the result. + this number of rows are included in the result. If both ``maxrows`` and ``level`` are specified, then the function - calculates the fusion product for `sl(\mathrm{maxrows})` of the - given level. + calculates the fusion product for `\mathfrak{sl}(\mathrm{maxrows})` + of the given level. + + If ``quantum`` is set, then this returns the product in the quantum + cohomology ring of the Grassmannian. In particular, both ``maxrows`` + and ``level`` need to be specified. EXAMPLES:: @@ -464,18 +477,59 @@ def mult(part1, part2, maxrows=None, level=None): ... ValueError: maxrows needs to be specified if you specify the level + The quantum product:: + + sage: q = polygen(QQ, 'q') + sage: sorted(mult([1],[2,1], 2, 2, quantum=q).items()) + [([], q), ([2, 2], 1)] + sage: sorted(mult([2,1],[2,1], 2, 2, quantum=q).items()) + [([1, 1], q), ([2], q)] + + sage: mult([2,1],[2,1], quantum=q) + Traceback (most recent call last): + ... + ValueError: missing parameters maxrows or level """ - if maxrows is None and not(level is None): - raise ValueError, 'maxrows needs to be specified if you specify the level' + if maxrows is None and level is not None: + raise ValueError('maxrows needs to be specified if you specify' + ' the level') + if quantum is not None and (level is None or maxrows is None): + raise ValueError('missing parameters maxrows or level') + cdef vector* v1 = iterable_to_vector(part1) cdef vector* v2 = iterable_to_vector(part2) if maxrows is None: maxrows = 0 cdef hashtab* ht = mult_c(v1, v2, int(maxrows)) - if not(level is None): - fusion_reduce_c(ht, int(maxrows), int(level), int(0)) - result = sf_hashtab_to_dict(ht) - v_free(v1); v_free(v2); hash_free(ht) + cdef hashtab* tab + cdef dict result + + if quantum is None: + if level is not None: + fusion_reduce_c(ht, int(maxrows), int(level), int(0)) + result = sf_hashtab_to_dict(ht) + v_free(v1) + v_free(v2) + hash_free(ht) + return result + + # Otherwise do quantum multiplication + cdef _list *qlist + cdef dict temp + qlist = quantum_reduce_c(ht, int(maxrows), int(level)) + # The above call frees the memory associated with ht + v_free(v1) + v_free(v2) + + cdef Parent P = quantum.parent() + result = {} + for i in range(qlist.length): + tab = (qlist.array[i]) + temp = sf_hashtab_to_dict(tab) + for k in temp: + result[k] = result.get(k, P.zero()) + quantum**i * temp[k] + hash_free(tab) + l_free(qlist) return result def skew(outer, inner, maxrows=0): @@ -492,7 +546,7 @@ def skew(outer, inner, maxrows=0): - ``inner`` -- a partition. - - ``maxrows`` -- an integer or None. + - ``maxrows`` -- an integer or ``None``. If ``maxrows`` is specified, then only partitions with at most this number of rows are included in the result. @@ -637,5 +691,6 @@ def lrskew(outer, inner, weight=None, maxrows=0): #yield skewtab_to_SkewTableau(st) st_free(st) if weight is not None: - result = [r for r in result if r.weight() == Partition(weight) ] + result = [r for r in result if r.weight() == _Partitions(weight) ] return result # todo: remove + diff --git a/src/sage/libs/ntl/ntl_GF2E.pyx b/src/sage/libs/ntl/ntl_GF2E.pyx index 30211dec4a7..190984b8b8c 100644 --- a/src/sage/libs/ntl/ntl_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_GF2E.pyx @@ -15,7 +15,6 @@ #***************************************************************************** include "sage/ext/interrupt.pxi" -include "sage/ext/random.pxi" include 'misc.pxi' include 'decl.pxi' @@ -24,8 +23,9 @@ from ntl_GF2 cimport ntl_GF2 from ntl_GF2X cimport ntl_GF2X from ntl_GF2EContext cimport ntl_GF2EContext_class from ntl_GF2EContext import ntl_GF2EContext - from sage.libs.ntl.ntl_ZZ import unpickle_class_args +from sage.misc.randstate cimport randstate, current_randstate + ############################################################################## # diff --git a/src/sage/libs/ntl/ntl_ZZ.pyx b/src/sage/libs/ntl/ntl_ZZ.pyx index c63ace8a54a..f38a13e3b1a 100644 --- a/src/sage/libs/ntl/ntl_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_ZZ.pyx @@ -16,13 +16,13 @@ include "sage/ext/interrupt.pxi" include "sage/ext/stdsage.pxi" include "sage/ext/cdefs.pxi" -include "sage/ext/random.pxi" include 'misc.pxi' include 'decl.pxi' from sage.rings.integer_ring import IntegerRing from sage.rings.integer cimport Integer from sage.libs.ntl.convert cimport PyLong_to_ZZ +from sage.misc.randstate cimport randstate, current_randstate ZZ_sage = IntegerRing() diff --git a/src/sage/libs/ntl/ntl_ZZ_p.pyx b/src/sage/libs/ntl/ntl_ZZ_p.pyx index 9d302270eac..530fb82923b 100644 --- a/src/sage/libs/ntl/ntl_ZZ_p.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_p.pyx @@ -15,7 +15,6 @@ include "sage/ext/interrupt.pxi" include "sage/ext/cdefs.pxi" -include "sage/ext/random.pxi" include 'misc.pxi' include 'decl.pxi' @@ -32,6 +31,9 @@ from sage.libs.ntl.convert cimport PyLong_to_ZZ from sage.libs.ntl.ntl_ZZ_pContext cimport ntl_ZZ_pContext_class from sage.libs.ntl.ntl_ZZ_pContext import ntl_ZZ_pContext +from sage.misc.randstate cimport randstate, current_randstate + + ZZ_sage = IntegerRing() def ntl_ZZ_p_random_element(v): diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index b889f808ce1..c30393d5e5f 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -15,7 +15,6 @@ include "sage/ext/interrupt.pxi" include "sage/ext/stdsage.pxi" -include "sage/ext/random.pxi" include 'misc.pxi' include 'decl.pxi' @@ -24,8 +23,9 @@ from sage.libs.ntl.ntl_ZZ cimport ntl_ZZ from sage.libs.ntl.ntl_ZZ_p cimport ntl_ZZ_p from sage.libs.ntl.ntl_ZZ_pContext cimport ntl_ZZ_pContext_class from sage.libs.ntl.ntl_ZZ_pContext import ntl_ZZ_pContext - from sage.libs.ntl.ntl_ZZ import unpickle_class_args +from sage.misc.randstate cimport randstate, current_randstate +from sage.libs.gmp.mpz cimport * cdef inline make_ZZ_p(ZZ_p_c* x, ntl_ZZ_pContext_class ctx): cdef ntl_ZZ_p y diff --git a/src/sage/libs/ntl/ntl_mat_GF2E.pyx b/src/sage/libs/ntl/ntl_mat_GF2E.pyx index 61c300b0c1f..a184137188e 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2E.pyx @@ -24,7 +24,6 @@ ############################################################################## include "sage/ext/interrupt.pxi" -include 'sage/ext/random.pxi' include 'misc.pxi' include 'decl.pxi' @@ -32,6 +31,7 @@ from ntl_GF2E cimport ntl_GF2E from ntl_GF2EContext import ntl_GF2EContext from ntl_GF2EContext cimport ntl_GF2EContext_class from sage.rings.integer cimport Integer +from sage.misc.randstate cimport randstate, current_randstate from sage.libs.ntl.ntl_ZZ import unpickle_class_args diff --git a/src/sage/libs/ntl/ntlwrap.cpp b/src/sage/libs/ntl/ntlwrap.cpp index 203f52806f3..94540474be6 100644 --- a/src/sage/libs/ntl/ntlwrap.cpp +++ b/src/sage/libs/ntl/ntlwrap.cpp @@ -676,7 +676,7 @@ static void ZZ_pX_conv_modulus(ZZ_pX &fout, const ZZ_pX &fin, const ZZ_pContext fout.normalize(); } -void ZZ_pEX_conv_modulus(ZZ_pEX &fout, const ZZ_pEX &fin, const ZZ_pContext &modout) +static void ZZ_pEX_conv_modulus(ZZ_pEX &fout, const ZZ_pEX &fin, const ZZ_pContext &modout) { // Changes the modulus of fin to modout, and puts the result in fout. long i, n, j, m; diff --git a/src/sage/libs/pari/gen.pyx b/src/sage/libs/pari/gen.pyx index 25a7222e7a0..dfbe916e806 100644 --- a/src/sage/libs/pari/gen.pyx +++ b/src/sage/libs/pari/gen.pyx @@ -8074,7 +8074,7 @@ cdef class gen(gen_auto): sage: x = polygen(QQ) sage: K. = NumberField(x^2 - 1/8) sage: pari(x^2 - 2).factornf(K.pari_polynomial("a")) - [x + Mod(-4*a, 8*a^2 - 1), 1; x + Mod(4*a, 8*a^2 - 1), 1] + [x + Mod(-a, a^2 - 2), 1; x + Mod(a, a^2 - 2), 1] """ cdef gen t0 = objtogen(t) pari_catch_sig_on() diff --git a/src/sage/libs/pari/pari_instance.pxd b/src/sage/libs/pari/pari_instance.pxd index 950e2bfda23..86257d1e630 100644 --- a/src/sage/libs/pari/pari_instance.pxd +++ b/src/sage/libs/pari/pari_instance.pxd @@ -15,6 +15,8 @@ cdef class PariInstance_auto(ParentWithBase): cdef class PariInstance(PariInstance_auto): cdef long _real_precision cdef gen PARI_ZERO, PARI_ONE, PARI_TWO + cpdef gen zero(self) + cpdef gen one(self) cdef inline gen new_gen(self, GEN x) cdef inline gen new_gen_noclear(self, GEN x) cdef gen new_gen_from_mpz_t(self, mpz_t value) diff --git a/src/sage/libs/pari/pari_instance.pyx b/src/sage/libs/pari/pari_instance.pyx index 39e5a6b936a..82f589c4b62 100644 --- a/src/sage/libs/pari/pari_instance.pyx +++ b/src/sage/libs/pari/pari_instance.pyx @@ -531,7 +531,22 @@ cdef class PariInstance(PariInstance_auto): def __hash__(self): return 907629390 # hash('pari') - cdef has_coerce_map_from_c_impl(self, x): + cpdef _coerce_map_from_(self, x): + """ + Return ``True`` if ``x`` admits a coercion map into the + PARI interface. + + This currently always returns ``True``. + + EXAMPLES:: + + sage: pari._coerce_map_from_(ZZ) + True + sage: pari.coerce_map_from(ZZ) + Call morphism: + From: Integer Ring + To: Interface to the PARI C library + """ return True def __richcmp__(left, right, int op): @@ -985,6 +1000,24 @@ cdef class PariInstance(PariInstance_auto): cdef _an_element_c_impl(self): # override this in Cython return self.PARI_ZERO + cpdef gen zero(self): + """ + EXAMPLES:: + + sage: pari.zero() + 0 + """ + return self.PARI_ZERO + + cpdef gen one(self): + """ + EXAMPLES:: + + sage: pari.one() + 1 + """ + return self.PARI_ONE + def new_with_bits_prec(self, s, long precision): r""" pari.new_with_bits_prec(self, s, precision) creates s as a PARI diff --git a/src/sage/libs/pari/paridecl.pxd b/src/sage/libs/pari/paridecl.pxd index fac8f363ccd..8e039a6084a 100644 --- a/src/sage/libs/pari/paridecl.pxd +++ b/src/sage/libs/pari/paridecl.pxd @@ -1435,6 +1435,7 @@ cdef extern from "sage/libs/pari/parisage.h": long fetch_var_higher() GEN fetch_var_value(long vx, GEN t) GEN gp_read_str(char *t) + GEN gp_read_str_multiline(char *t) entree* install(void *f, char *name, char *code) entree* is_entry(char *s) void kill0(char *e) @@ -2203,7 +2204,7 @@ cdef extern from "sage/libs/pari/parisage.h": # classpoly.c - GEN polclass(GEN D, long xvar) + GEN polclass(GEN D, long inv, long xvar) # compile.c @@ -2216,8 +2217,8 @@ cdef extern from "sage/libs/pari/parisage.h": # concat.c - GEN concat(GEN x, GEN y) - GEN concat1(GEN x) + GEN gconcat(GEN x, GEN y) + GEN gconcat1(GEN x) GEN matconcat(GEN v) GEN shallowconcat(GEN x, GEN y) GEN shallowconcat1(GEN x) @@ -3181,7 +3182,101 @@ cdef extern from "sage/libs/pari/parisage.h": GEN rnfkummer(GEN bnr, GEN subgroup, long all, long prec) + # lfun.c + + long is_linit(GEN data) + GEN ldata_get_an(GEN ldata) + long ldata_get_selfdual(GEN ldata) + long ldata_isreal(GEN ldata) + GEN ldata_get_gammavec(GEN ldata) + long ldata_get_degree(GEN ldata) + long ldata_get_k(GEN ldata) + GEN ldata_get_conductor(GEN ldata) + GEN ldata_get_rootno(GEN ldata) + GEN ldata_get_residue(GEN ldata) + GEN ldata_vecan(GEN ldata, long L, long prec) + long ldata_get_type(GEN ldata) + long linit_get_type(GEN linit) + GEN linit_get_ldata(GEN linit) + GEN linit_get_tech(GEN linit) + GEN lfun_get_domain(GEN tech) + GEN lfun_get_dom(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun_get_factgammavec(GEN tech) + GEN lfun_get_step(GEN tech) + GEN lfun_get_pol(GEN tech) + GEN lfun_get_Residue(GEN tech) + GEN lfun_get_k2(GEN tech) + GEN lfun_get_w2(GEN tech) + GEN lfun_get_expot(GEN tech) + long lfun_get_der(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun(GEN ldata, GEN s, long prec) + GEN lfun_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfun0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfun0(GEN ldata, GEN s, long der, long prec) + long lfuncheckfeq(GEN data, GEN t0, long prec) + long lfuncheckfeq_bitprec(GEN data, GEN t0, long bitprec) + GEN lfunconductor(GEN data, GEN maxcond, long flag, long prec) + GEN lfuncreate(GEN obj) + GEN lfunan(GEN ldata, long L, long prec) + GEN lfunhardy(GEN ldata, GEN t, long prec) + GEN lfunhardy_bitprec(GEN ldata, GEN t, long bitprec) + GEN lfuninit(GEN ldata, GEN dom, long der, long prec) + GEN lfuninit_bitprec(GEN ldata, GEN dom, long der, long bitprec) + GEN lfuninit_make(long t, GEN ldata, GEN molin, GEN domain) + long lfunisvgaell(GEN Vga, long flag) + GEN lfunlambda(GEN ldata, GEN s, long prec) + GEN lfunlambda_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfunlambda0(GEN ldata, GEN s, long der, long prec) + GEN lfunlambda0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfunmisc_to_ldata(GEN ldata) + GEN lfunmisc_to_ldata_shallow(GEN ldata) + long lfunorderzero(GEN ldata, long prec) + long lfunorderzero_bitprec(GEN ldata, long bitprec) + GEN lfunprod_get_fact(GEN tech) + GEN lfunrootno(GEN data, long prec) + GEN lfunrootno_bitprec(GEN data, long bitprec) + GEN lfunrootres(GEN data, long prec) + GEN lfunrootres_bitprec(GEN data, long bitprec) + GEN lfunrtopoles(GEN r) + GEN lfuntheta(GEN data, GEN t, long m, long prec) + GEN lfuntheta_bitprec(GEN data, GEN t, long m, long bitprec) + GEN lfunthetainit(GEN ldata, GEN tinf, long m, long prec) + GEN lfunthetainit_bitprec(GEN ldata, GEN tdom, long m, long bitprec) + GEN lfunthetacheckinit(GEN data, GEN tinf, long m, long *ptbitprec, long fl) + GEN lfunzeros(GEN ldata, GEN lim, long divz, long prec) + GEN lfunzeros_bitprec(GEN ldata, GEN lim, long divz, long bitprec) + int sdomain_isincl(GEN dom, GEN dom0) + GEN theta_get_an(GEN tdata) + GEN theta_get_K(GEN tdata) + GEN theta_get_R(GEN tdata) + long theta_get_bitprec(GEN tdata) + long theta_get_m(GEN tdata) + GEN theta_get_tdom(GEN tdata) + GEN theta_get_sqrtN(GEN tdata) + + # lfunutils.c + + GEN dirzetak(GEN nf, GEN b) + GEN ellmoddegree(GEN e, long prec) + GEN lfunabelianrelinit(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long prec) + GEN lfunabelianrelinit_bitprec(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long bitprec) + GEN lfunconvol(GEN a1, GEN a2) + GEN lfundiv(GEN ldata1, GEN ldata2, long prec) + GEN lfunzetakinit(GEN pol, GEN dom, long der, long flag, long prec) + GEN lfunzetakinit_bitprec(GEN pol, GEN dom, long der, long flag, long bitprec) + GEN lfunetaquo(GEN ldata) + GEN lfunmfspec(GEN ldata, long prec) + GEN lfunmfpeters(GEN ldata, long prec) + GEN lfunmfpeters_bitprec(GEN ldata, long bitprec) + GEN lfunmul(GEN ldata1, GEN ldata2, long prec) + GEN lfunqf(GEN ldata) + GEN lfunsymsq(GEN ldata, GEN known, long prec) + GEN lfunsymsqspec(GEN ldata, long prec) + # lll.c + GEN ZM_lll_norms(GEN x, double D, long flag, GEN *B) GEN kerint(GEN x) GEN lll(GEN x) diff --git a/src/sage/libs/polybori/decl.pxd b/src/sage/libs/polybori/decl.pxd index b8fcc6e3a15..6ec4bce21c9 100644 --- a/src/sage/libs/polybori/decl.pxd +++ b/src/sage/libs/polybori/decl.pxd @@ -10,8 +10,8 @@ cdef extern from "sage/libs/polybori/pb_wrap.h": pbdp_asc "CTypes::dp_asc" pbblock_dlex "CTypes::block_dlex" pbblock_dp_asc "CTypes::block_dp_asc" - pbdp "17" - pbblock_dp "19" + pbdp "static_cast(17)" + pbblock_dp "static_cast(19)" cdef enum comparecodes "CCompareEnums::comparecodes": less_than "CTypes::less_than" diff --git a/src/sage/libs/ppl.pyx b/src/sage/libs/ppl.pyx index b344257dee1..88266aa7f17 100644 --- a/src/sage/libs/ppl.pyx +++ b/src/sage/libs/ppl.pyx @@ -1,3 +1,5 @@ +# distutils: language = c++ +# distutils: libraries = ppl m r""" Cython wrapper for the Parma Polyhedra Library (PPL) @@ -149,6 +151,7 @@ AUTHORS: from sage.structure.sage_object cimport SageObject from sage.libs.gmp.mpz cimport * +from sage.libs.gmpxx cimport mpz_class from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational @@ -163,14 +166,6 @@ from libcpp cimport bool as cppbool # These can only be triggered by methods in the Polyhedron class # they need to be wrapped in sig_on() / sig_off() #################################################### -cdef extern from "gmpxx.h": - cdef cppclass mpz_class: - mpz_class() - mpz_class(int i) - mpz_class(mpz_t z) - mpz_class(mpz_class) - mpz_t get_mpz_t() - #################################################### # PPL can use floating-point arithmetic to compute integers @@ -3010,6 +3005,38 @@ cdef class Polyhedron(_mutable_or_immutable): return result + def __hash__(self): + r""" + Hash value for polyhedra. + + TESTS:: + + sage: from sage.libs.ppl import Constraint_System, Variable, C_Polyhedron + sage: x = Variable(0) + sage: p = C_Polyhedron( 5*x >= 3 ) + sage: p.set_immutable() + sage: hash(p) + 1 + + sage: y = Variable(1) + sage: cs = Constraint_System() + sage: cs.insert( x >= 0 ) + sage: cs.insert( y >= 0 ) + sage: p = C_Polyhedron(cs) + sage: p.set_immutable() + sage: hash(p) + 2 + + sage: hash(C_Polyhedron(x >= 0)) + Traceback (most recent call last): + ... + TypeError: mutable polyhedra are unhashable + """ + if self.is_mutable(): + raise TypeError("mutable polyhedra are unhashable") + # TODO: the hash code from PPL looks like being the dimension! + return self.thisptr[0].hash_code() + def __richcmp__(Polyhedron lhs, Polyhedron rhs, op): r""" Comparison for polyhedra. diff --git a/src/sage/libs/singular/groebner_strategy.pyx b/src/sage/libs/singular/groebner_strategy.pyx index 231cce0ed21..b4c2be95922 100644 --- a/src/sage/libs/singular/groebner_strategy.pyx +++ b/src/sage/libs/singular/groebner_strategy.pyx @@ -312,7 +312,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) - sage: NCGroebnerStrategy(I) + sage: NCGroebnerStrategy(I) #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} TESTS:: @@ -412,7 +412,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) sage: strat = NCGroebnerStrategy(I) - sage: strat # indirect doctest + sage: strat # indirect doctest #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} """ return "Groebner Strategy for ideal generated by %d elements over %s"%(self._ideal.ngens(),self._parent) diff --git a/src/sage/libs/singular/ring.pyx b/src/sage/libs/singular/ring.pyx index a535bfc638e..5a920139ea0 100644 --- a/src/sage/libs/singular/ring.pyx +++ b/src/sage/libs/singular/ring.pyx @@ -446,12 +446,14 @@ cdef ring *singular_ring_reference(ring *existing_ring) except NULL: INPUT: - - ``existing_ring`` -- an existing Singular ring. + - ``existing_ring`` -- a Singular ring. OUTPUT: - The same ring with its refcount increased. After calling this - function `n` times, you need to call :func:`singular_ring_delete` + The same ring with its refcount increased. If ``existing_ring`` + has not been refcounted yet, it will be after calling this function. + If initially ``existing_ring`` was refcounted once, then after + calling this function `n` times, you need to call :func:`singular_ring_delete` `n+1` times to actually deallocate the ring. EXAMPLE:: @@ -487,8 +489,7 @@ cdef ring *singular_ring_reference(ring *existing_ring) except NULL: if existing_ring==NULL: raise ValueError('singular_ring_reference(ring*) called with NULL pointer.') cdef object r = wrap_ring(existing_ring) - refcount = ring_refcount_dict.pop(r) - ring_refcount_dict[r] = refcount+1 + ring_refcount_dict[r] = ring_refcount_dict.get(r,0)+1 return existing_ring diff --git a/src/sage/matrix/action.pyx b/src/sage/matrix/action.pyx index 23ea977ba36..e9df2451601 100644 --- a/src/sage/matrix/action.pyx +++ b/src/sage/matrix/action.pyx @@ -53,8 +53,10 @@ AUTHOR: #***************************************************************************** # Copyright (C) 2007 Robert Bradshaw # -# Distributed under the terms of the GNU General Public License (GPL) -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -63,6 +65,7 @@ import operator from matrix_space import MatrixSpace, is_MatrixSpace from sage.modules.free_module import FreeModule, is_FreeModule +from sage.structure.element cimport coercion_model cdef class MatrixMulAction(Action): @@ -70,8 +73,7 @@ cdef class MatrixMulAction(Action): if not is_MatrixSpace(G): raise TypeError, "Not a matrix space: %s" % G if G.base_ring() is not S.base_ring(): - from sage.structure.element import get_coercion_model - base = get_coercion_model().common_parent(G.base_ring(), S.base_ring()) + base = coercion_model.common_parent(G.base_ring(), S.base_ring()) else: base = G.base_ring() Action.__init__(self, G, S, is_left, operator.mul) diff --git a/src/sage/matrix/constructor.py b/src/sage/matrix/constructor.py index e9357bf611b..3f108e1beed 100644 --- a/src/sage/matrix/constructor.py +++ b/src/sage/matrix/constructor.py @@ -856,6 +856,7 @@ def nrows_from_dict(d): Here the answer is 301 not 300, since there is a 0-th row. :: + sage: sage.matrix.constructor.nrows_from_dict({(300,4):10}) 301 """ @@ -4158,6 +4159,3 @@ def ith_to_zero_rotation_matrix(v, i, ring=None): entries[(k, k)] = 1 entries.update({(j,j):aa, (j,i):bb, (i,j):-bb, (i,i):aa}) return matrix(entries, nrows=dim, ring=ring) - - - diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 14285591696..c726afee5eb 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -4056,6 +4056,8 @@ cdef class Matrix(sage.structure.element.Matrix): """ return self.is_square() and self.determinant().is_unit() + is_unit = is_invertible + def is_singular(self): r""" Returns ``True`` if ``self`` is singular. @@ -4760,7 +4762,7 @@ cdef class Matrix(sage.structure.element.Matrix): MS = self.matrix_space(n, m) return MS(X).transpose() - cpdef ModuleElement _add_(self, ModuleElement right): + cpdef ModuleElement _add_(self, ModuleElement _right): """ Add two matrices with the same parent. @@ -4776,13 +4778,14 @@ cdef class Matrix(sage.structure.element.Matrix): """ cdef Py_ssize_t i, j cdef Matrix A + cdef Matrix right = _right A = self.new_matrix() - for i from 0 <= i < self._nrows: - for j from 0 <= j < self._ncols: - A.set_unsafe(i,j, self.get_unsafe(i,j) + (right).get_unsafe(i,j)) + for i in range(self._nrows): + for j in range(self._ncols): + A.set_unsafe(i,j,self.get_unsafe(i,j)._add_(right.get_unsafe(i,j))) return A - cpdef ModuleElement _sub_(self, ModuleElement right): + cpdef ModuleElement _sub_(self, ModuleElement _right): """ Subtract two matrices with the same parent. @@ -4798,22 +4801,22 @@ cdef class Matrix(sage.structure.element.Matrix): """ cdef Py_ssize_t i, j cdef Matrix A + cdef Matrix right = _right A = self.new_matrix() - for i from 0 <= i < self._nrows: - for j from 0 <= j < self._ncols: - A.set_unsafe(i,j, self.get_unsafe(i,j) - (right).get_unsafe(i,j)) + for i in range(self._nrows): + for j in range(self._ncols): + A.set_unsafe(i,j,self.get_unsafe(i,j)._sub_(right.get_unsafe(i,j))) return A - def __mod__(self, p): r""" Return matrix mod `p`, returning again a matrix over the same base ring. - .. note:: + .. NOTE:: - Use ``A.Mod(p)`` to obtain a matrix over the residue class - ring modulo `p`. + Use :meth:`mod` to obtain a matrix over the residue class ring + modulo `p`. EXAMPLES:: @@ -4824,11 +4827,13 @@ cdef class Matrix(sage.structure.element.Matrix): sage: parent(M % 7) Full MatrixSpace of 2 by 2 dense matrices over Integer Ring """ - cdef Py_ssize_t i - v = self.list() - for i from 0 <= i < len(v): - v[i] = v[i] % p - return self.new_matrix(entries = v, copy=False, coerce=True) + cdef Py_ssize_t i, j + cdef Matrix s = self + cdef Matrix A = s.new_matrix() + for i in range(A._nrows): + for j in range(A._ncols): + A[i,j] = s.get_unsafe(i,j) % p + return A def mod(self, p): """ diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index 3e2c044feee..d65eba6305d 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -9,18 +9,20 @@ TESTS:: sage: TestSuite(A).run() """ -################################################################################ +#***************************************************************************** # Copyright (C) 2005, 2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL). -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -################################################################################ +#***************************************************************************** include "sage/ext/python.pxi" import sage.modules.free_module +from sage.structure.element cimport coercion_model cdef class Matrix(matrix0.Matrix): @@ -169,20 +171,20 @@ cdef class Matrix(matrix0.Matrix): EXAMPLES:: - sage: M = matrix(ZZ,2,range(4)) # optional - giac + sage: M = matrix(ZZ,2,range(4)) sage: giac(M) # optional - giac [[0,1],[2,3]] :: - sage: M = matrix(QQ,3,[1,2,3,4/3,5/3,6/4,7,8,9]) # optional - giac + sage: M = matrix(QQ,3,[1,2,3,4/3,5/3,6/4,7,8,9]) sage: giac(M) # optional - giac [[1,2,3],[4/3,5/3,3/2],[7,8,9]] :: - sage: P. = ZZ[] # optional - giac - sage: M = matrix(P, 2, [-9*x^2-2*x+2, x-1, x^2+8*x, -3*x^2+5]) # optional - giac + sage: P. = ZZ[] + sage: M = matrix(P, 2, [-9*x^2-2*x+2, x-1, x^2+8*x, -3*x^2+5]) sage: giac(M) # optional - giac [[-9*x^2-2*x+2,x-1],[x^2+8*x,-3*x^2+5]] """ @@ -363,7 +365,7 @@ cdef class Matrix(matrix0.Matrix): EXAMPLES:: sage: m = matrix(ZZ, [[1,2],[3,4]]) - sage: macaulay2(m) #optional (indirect doctest) + sage: macaulay2(m) #optional - macaulay2 (indirect doctest) | 1 2 | | 3 4 | @@ -386,11 +388,11 @@ cdef class Matrix(matrix0.Matrix): EXAMPLES: - sage: a = matrix([[1,2,3],[4,5,6],[7,8,9]]); a # optional - scilab + sage: a = matrix([[1,2,3],[4,5,6],[7,8,9]]); a [1 2 3] [4 5 6] [7 8 9] - sage: a._scilab_init_() # optional - scilab + sage: a._scilab_init_() '[1,2,3;4,5,6;7,8,9]' AUTHORS: @@ -415,7 +417,7 @@ cdef class Matrix(matrix0.Matrix): EXAMPLES: - sage: a = matrix([[1,2,3],[4,5,6],[7,8,9]]); a # optional - scilab + sage: a = matrix([[1,2,3],[4,5,6],[7,8,9]]); a [1 2 3] [4 5 6] [7 8 9] @@ -1363,8 +1365,6 @@ cdef class Matrix(matrix0.Matrix): top_ring = self._base_ring bottom_ring = other._base_ring if top_ring is not bottom_ring: - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() R = coercion_model.common_parent(top_ring, bottom_ring) if top_ring is not R: self = self.change_ring(R) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 50e162809b8..e973cfd5bf0 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -73,8 +73,13 @@ cdef class Matrix(matrix1.Matrix): """ return self.solve_right(B) - def subs(self, in_dict=None, **kwds): + def subs(self, *args, **kwds): """ + Substitute values to the variables in that matrix. + + All the arguments are transmitted unchanged to the method ``subs`` of + the coefficients. + EXAMPLES:: sage: var('a,b,d,e') @@ -86,9 +91,37 @@ cdef class Matrix(matrix1.Matrix): sage: m.subs(a=b, b=d) [b d] [d e] + sage: m.subs({a: 3, b:2, d:1, e:-1}) + [ 3 2] + [ 1 -1] + + The parent of the newly created matrix might be different from the + initial one. It depends on what the method ``.subs`` does on + coefficients (see :trac:`19045`):: + + sage: x = polygen(ZZ) + sage: m = matrix([[x]]) + sage: m2 = m.subs(x=2) + sage: m2.parent() + Full MatrixSpace of 1 by 1 dense matrices over Integer Ring + sage: m1 = m.subs(x=RDF(1)) + sage: m1.parent() + Full MatrixSpace of 1 by 1 dense matrices over Real Double Field + + However, sparse matrices remain sparse:: + + sage: m = matrix({(3,2): -x, (59,38): x^2+2}, nrows=1000, ncols=1000) + sage: m1 = m.subs(x=1) + sage: m1.is_sparse() + True """ - v = [a.subs(in_dict, **kwds) for a in self.list()] - return self.new_matrix(self.nrows(), self.ncols(), v) + from sage.matrix.constructor import matrix + if self.is_sparse(): + return matrix({ij: self[ij].subs(*args, **kwds) for ij in self.nonzero_positions()}, + nrows=self._nrows, ncols=self._ncols, sparse=True) + else: + return matrix([a.subs(*args, **kwds) for a in self.list()], + nrows=self._nrows, ncols=self._ncols, sparse=False) def solve_left(self, B, check=True): """ @@ -1006,7 +1039,7 @@ cdef class Matrix(matrix1.Matrix): These numbers are the coefficients of a modified Laguerre polynomial:: - sage: x = polygen(ZZ) + sage: x = polygen(QQ) sage: factorial(8) * laguerre(8,-x) x^8 + 64*x^7 + 1568*x^6 + 18816*x^5 + 117600*x^4 + 376320*x^3 + 564480*x^2 + 322560*x + 40320 diff --git a/src/sage/matrix/matrix_complex_ball_dense.pyx b/src/sage/matrix/matrix_complex_ball_dense.pyx index db0311b7077..3a78c4a29d9 100644 --- a/src/sage/matrix/matrix_complex_ball_dense.pyx +++ b/src/sage/matrix/matrix_complex_ball_dense.pyx @@ -5,11 +5,10 @@ AUTHORS: - Clemens Heuberger (2014-10-25): Initial version. -This is a rudimentary binding to the optional `Arb library +This is a rudimentary binding to the `Arb library `_; it may be useful to refer to its documentation for more details. -You may have to run ``sage -i arb`` to use the arb library. """ #***************************************************************************** # Copyright (C) 2014 Clemens Heuberger @@ -29,7 +28,7 @@ from sage.matrix.constructor import matrix from sage.matrix.matrix_generic_sparse cimport Matrix_generic_sparse from sage.rings.complex_interval_field import ComplexIntervalField_class, ComplexIntervalField from sage.rings.complex_interval cimport ComplexIntervalFieldElement -from sage.rings.complex_ball_acb cimport ( +from sage.rings.complex_arb cimport ( ComplexBall, ComplexIntervalFieldElement_to_acb, acb_to_ComplexIntervalFieldElement) @@ -103,17 +102,11 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - doctest:...: FutureWarning: This class/method/function is marked as - experimental. It, its functionality or its interface might change - without a formal deprecation. - See http://trac.sagemath.org/17218 for details. - sage: MatrixSpace(CBF, 3)(2) # optional - arb + sage: MatrixSpace(CBF, 3)(2) [2.000000000000000 0 0] [ 0 2.000000000000000 0] [ 0 0 2.000000000000000] - sage: matrix(CBF, 1, 3, [1, 2, -3]) # optional - arb + sage: matrix(CBF, 1, 3, [1, 2, -3]) [ 1.000000000000000 2.000000000000000 -3.000000000000000] """ ################################################################# @@ -141,12 +134,10 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: from sage.matrix.matrix_complex_ball_dense import Matrix_complex_ball_dense # optional - arb - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = Matrix_complex_ball_dense.__new__( # optional - arb; indirect doctest + sage: from sage.matrix.matrix_complex_ball_dense import Matrix_complex_ball_dense + sage: a = Matrix_complex_ball_dense.__new__( # indirect doctest ....: Matrix_complex_ball_dense, Mat(CBF, 2), 0, 0, 0) - sage: type(a) # optional - arb + sage: type(a) """ self._parent = parent @@ -163,10 +154,8 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = Matrix(CBF, 2, [1, 2, 3, 4]) # optional - arb; indirect doctest - sage: del a # optional - arb + sage: a = Matrix(CBF, 2, [1, 2, 3, 4]) # indirect doctest + sage: del a """ acb_mat_clear(self.value) @@ -176,7 +165,7 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): entries, copy, coerce): - """ + r""" Initialize a dense matrix over the complex ball field. INPUT: @@ -197,20 +186,15 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES: - The __init__ function is called implicitly in each of the + The ``__init__`` function is called implicitly in each of the examples below to actually fill in the values of the matrix. - :: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - We create a `2 \times 2` and a `1\times 4` matrix:: - sage: matrix(CBF, 2, 2, range(4)) # optional - arb + sage: matrix(CBF, 2, 2, range(4)) [ 0 1.000000000000000] [2.000000000000000 3.000000000000000] - sage: Matrix(CBF, 1, 4, range(4)) # optional - arb + sage: Matrix(CBF, 1, 4, range(4)) [ 0 1.000000000000000 2.000000000000000 3.000000000000000] If the number of columns isn't given, it is determined from the @@ -218,10 +202,10 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): :: - sage: matrix(CBF, 2, range(4)) # optional - arb + sage: matrix(CBF, 2, range(4)) [ 0 1.000000000000000] [2.000000000000000 3.000000000000000] - sage: matrix(CBF, 2, range(6)) # optional - arb + sage: matrix(CBF, 2, range(6)) [ 0 1.000000000000000 2.000000000000000] [3.000000000000000 4.000000000000000 5.000000000000000] @@ -230,27 +214,27 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): :: - sage: A = Mat(CBF, 2); A # optional - arb + sage: A = Mat(CBF, 2); A Full MatrixSpace of 2 by 2 dense matrices over Complex ball field with 53 bits precision - sage: A(range(4)) # optional - arb + sage: A(range(4)) [ 0 1.000000000000000] [2.000000000000000 3.000000000000000] Actually it is only necessary that the input can be converted to a list, so the following also works:: - sage: v = reversed(range(4)); type(v) # optional - arb + sage: v = reversed(range(4)); type(v) - sage: A(v) # optional - arb + sage: A(v) [3.000000000000000 2.000000000000000] [1.000000000000000 0] Matrices can have many rows or columns (in fact, on a 64-bit - machine they could have up to `2^64-1` rows or columns):: + machine they could have up to `2^{64}-1` rows or columns):: - sage: v = matrix(CBF, 1, 10^5, range(10^5)) # optional - arb - sage: v.parent() # optional - arb + sage: v = matrix(CBF, 1, 10^5, range(10^5)) + sage: v.parent() Full MatrixSpace of 1 by 100000 dense matrices over Complex ball field with 53 bits precision """ @@ -320,13 +304,11 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = matrix(CBF, 2, 3, range(6)); a # optional - arb + sage: a = matrix(CBF, 2, 3, range(6)); a [ 0 1.000000000000000 2.000000000000000] [3.000000000000000 4.000000000000000 5.000000000000000] - sage: a[0, 0] = 10 # optional - arb - sage: a # optional - arb + sage: a[0, 0] = 10 + sage: a [10.00000000000000 1.000000000000000 2.000000000000000] [3.000000000000000 4.000000000000000 5.000000000000000] """ @@ -343,19 +325,17 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = MatrixSpace(CBF, 3)(range(9)); a # optional - arb + sage: a = MatrixSpace(CBF, 3)(range(9)); a [ 0 1.000000000000000 2.000000000000000] [3.000000000000000 4.000000000000000 5.000000000000000] [6.000000000000000 7.000000000000000 8.000000000000000] - sage: a[1, 2] # optional - arb + sage: a[1, 2] 5.000000000000000 - sage: a[4, 7] # optional - arb + sage: a[4, 7] Traceback (most recent call last): ... IndexError: matrix index out of range - sage: a[-1, 0] # optional - arb + sage: a[-1, 0] 6.000000000000000 """ cdef ComplexBall z = ComplexBall.__new__(ComplexBall) @@ -367,12 +347,10 @@ cdef class Matrix_complex_ball_dense(matrix_dense.Matrix_dense): """ EXAMPLE:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1) # optional - arb - sage: m1 = MatrixSpace(CBF, 1)([a]) # optional - arb - sage: m2 = MatrixSpace(CBF, 1)([a]) # optional - arb - sage: m1 == m2 # indirect doctest; optional - arb + sage: a = CBF(1) + sage: m1 = MatrixSpace(CBF, 1)([a]) + sage: m2 = MatrixSpace(CBF, 1)([a]) + sage: m1 == m2 # indirect doctest True """ return self._richcmp(right, op) diff --git a/src/sage/matrix/matrix_complex_double_dense.pyx b/src/sage/matrix/matrix_complex_double_dense.pyx index ce3c584064f..595e8a5651f 100644 --- a/src/sage/matrix/matrix_complex_double_dense.pyx +++ b/src/sage/matrix/matrix_complex_double_dense.pyx @@ -92,7 +92,6 @@ cdef class Matrix_complex_double_dense(matrix_double_dense.Matrix_double_dense): # * __init__ # * set_unsafe # * get_unsafe - # * __richcmp__ -- always the same # * __hash__ -- always simple ######################################################################## def __cinit__(self, parent, entries, copy, coerce): diff --git a/src/sage/matrix/matrix_cyclo_dense.pyx b/src/sage/matrix/matrix_cyclo_dense.pyx index 268d64ffc0c..b92fc8eca7c 100644 --- a/src/sage/matrix/matrix_cyclo_dense.pyx +++ b/src/sage/matrix/matrix_cyclo_dense.pyx @@ -27,20 +27,24 @@ AUTHORS: * Craig Citro """ -###################################################################### -# Copyright (C) 2008 William Stein -# Distributed under the terms of the GNU General Public License (GPL) -# The full text of the GPL is available at: +#***************************************************************************** +# Copyright (C) 2008 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/ -###################################################################### +#***************************************************************************** + include "sage/ext/interrupt.pxi" include "sage/ext/cdefs.pxi" -include "sage/ext/random.pxi" include "sage/libs/ntl/decl.pxi" from sage.structure.element cimport ModuleElement, RingElement, Element, Vector from sage.misc.randstate cimport randstate, current_randstate +from sage.libs.gmp.randomize cimport * from constructor import matrix from matrix_space import MatrixSpace @@ -658,25 +662,6 @@ cdef class Matrix_cyclo_dense(matrix_dense.Matrix_dense): C._matrix = M return C - def __richcmp__(Matrix self, right, int op): - """ - Compare a matrix with something else. This immediately calls - a base class _richcmp. - - EXAMPLES:: - - sage: W. = CyclotomicField(5) - sage: A = matrix(W, 2, 2, [1,z,-z,1+z/2]) - - These implicitly call richcmp:: - - sage: A == 5 - False - sage: A < 100 - True - """ - return self._richcmp(right, op) - cdef long _hash(self) except -1: """ Return hash of this matrix. @@ -731,11 +716,22 @@ cdef class Matrix_cyclo_dense(matrix_dense.Matrix_dense): identical parents. INPUT: - self, right -- matrices with same parent - OUTPUT: - int; either -1, 0, or 1 - EXAMPLES: + - ``self``, ``right`` -- matrices with same parent + + OUTPUT: either -1, 0, or 1 + + EXAMPLES:: + + sage: W. = CyclotomicField(5) + sage: A = matrix(W, 2, 2, [1,z,-z,1+z/2]) + + These implicitly call richcmp:: + + sage: A == 5 + False + sage: A < 100 + True This function is called implicitly when comparisons with matrices are done or the cmp function is used.:: diff --git a/src/sage/matrix/matrix_dense.pyx b/src/sage/matrix/matrix_dense.pyx index cdf0ec6dfcc..85e2739a236 100644 --- a/src/sage/matrix/matrix_dense.pyx +++ b/src/sage/matrix/matrix_dense.pyx @@ -234,7 +234,7 @@ cdef class Matrix_dense(matrix.Matrix): raised. This routine is meant to be called from the - meth:`~sage.matrix.matrix2.Matrix.elementwise_product` + :meth:`~sage.matrix.matrix2.Matrix.elementwise_product` method, which will ensure that this routine receives proper input. More thorough documentation is provided there. diff --git a/src/sage/matrix/matrix_double_dense.pyx b/src/sage/matrix/matrix_double_dense.pyx index f9b391ace0a..28798a53970 100644 --- a/src/sage/matrix/matrix_double_dense.pyx +++ b/src/sage/matrix/matrix_double_dense.pyx @@ -96,7 +96,6 @@ cdef class Matrix_double_dense(matrix_dense.Matrix_dense): # * __init__ # * set_unsafe # * get_unsafe - # * __richcmp__ -- always the same # * __hash__ -- always simple ######################################################################## def __cinit__(self, parent, entries, copy, coerce): @@ -130,9 +129,6 @@ cdef class Matrix_double_dense(matrix_dense.Matrix_dense): """ Deallocate any memory that was initialized.""" return - def __richcmp__(Matrix self, right, int op): # always need for mysterious reasons. - return self._richcmp(right, op) - def __hash__(self): """ Hash this matrix if it's immutable. @@ -303,11 +299,14 @@ cdef class Matrix_double_dense(matrix_dense.Matrix_dense): """ cdef Matrix_double_dense m - if nrows == -1: + if nrows == -1 and ncols == -1: nrows = self._nrows - if ncols == -1: ncols = self._ncols - parent = self.matrix_space(nrows, ncols) + parent = self._parent + else: + if nrows == -1: nrows = self._nrows + if ncols == -1: ncols = self._ncols + parent = self.matrix_space(nrows, ncols) m = self.__class__.__new__(self.__class__,parent,None,None,None) return m diff --git a/src/sage/matrix/matrix_generic_dense.pxd b/src/sage/matrix/matrix_generic_dense.pxd index 8e8bbb318e0..77ce1e55d17 100644 --- a/src/sage/matrix/matrix_generic_dense.pxd +++ b/src/sage/matrix/matrix_generic_dense.pxd @@ -1,5 +1,5 @@ cimport matrix_dense cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): - cdef object _entries - + cdef list _entries + cdef Matrix_generic_dense _new(self, Py_ssize_t nrows, Py_ssize_t ncols) diff --git a/src/sage/matrix/matrix_generic_dense.pyx b/src/sage/matrix/matrix_generic_dense.pyx index 14b0d8b8d03..69d1859379b 100644 --- a/src/sage/matrix/matrix_generic_dense.pyx +++ b/src/sage/matrix/matrix_generic_dense.pyx @@ -1,23 +1,8 @@ """ Dense Matrices over a general ring """ - -def _convert_dense_entries_to_list(entries): - """ - Create list of entries that define a matrix from a list of vectors. - - EXAMPLES: - sage: entries = [vector([1,2,3]), vector([4,5,6])] - sage: sage.matrix.matrix_generic_dense._convert_dense_entries_to_list(entries) - [1, 2, 3, 4, 5, 6] - """ - e = [] - for v in entries: - e = e+ v.list() - copy = False - return e - include "sage/ext/interrupt.pxi" +cimport cython from cpython.list cimport * from cpython.number cimport * from cpython.ref cimport * @@ -27,6 +12,8 @@ import matrix_dense cimport matrix +from sage.structure.element cimport parent_c + cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): r""" The ``Matrix_generic_dense`` class derives from @@ -43,6 +30,16 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): sage: type(A) sage: TestSuite(A).run() + + Test comparisons:: + + sage: A = random_matrix(Integers(25)['x'],2) + sage: cmp(A,A) + 0 + sage: cmp(A,A+1) + -1 + sage: cmp(A+1,A) + 1 """ ######################################################################## # LEVEL 1 functionality @@ -60,7 +57,7 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): TESTS: - We check that the problem related to Trac #9049 is not an issue any + We check that the problem related to :trac:`9049` is not an issue any more:: sage: S.=PolynomialRing(QQ) @@ -73,61 +70,80 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): """ matrix.Matrix.__init__(self, parent) - cdef Py_ssize_t i, n + cdef Py_ssize_t i,j + cdef bint is_list - if entries is None: - entries = 0 + R = parent.base_ring() + zero = R.zero() - if not isinstance(entries, (list, tuple)): + # determine if entries is a list or a scalar + if entries is None: + entries = zero + is_list = False + elif parent_c(entries) is R: + is_list = False + elif type(entries) is list: + # here we do a strong type checking as we potentially want to + # assign entries to self._entries without copying it + self._entries = entries + is_list = True + elif isinstance(entries, (list,tuple)): + # it is needed to check for list here as for example Sequence + # inherits from it but fails the strong type checking above + self._entries = list(entries) + is_list = True + copy = False + else: + # not sure what entries is at this point... try scalar first try: - x = parent.base_ring()(entries) - is_list = 0 + entries = R(entries) + is_list = False except TypeError: try: - entries = list(entries) - is_list = 1 + self._entries = list(entries) + is_list = True + copy = False except TypeError: - raise TypeError, "entries must be coercible to a list or the base ring" - - else: - is_list = 1 + raise TypeError("entries must be coercible to a list or the base ring") + # now set self._entries if is_list: - - if len(entries) != self._nrows * self._ncols: - raise TypeError, "entries has the wrong length" - - if not (coerce or copy): - self._entries = entries - else: - self._entries = [None]*(self._nrows*self._ncols) - n = len(entries) - if coerce: - R = parent.base_ring() - for i from 0 <= i < n: - self._entries[i] = R(entries[i]) - else: - for i from 0 <= i < n: - self._entries[i] = entries[i] - + if len(self._entries) != self._nrows * self._ncols: + raise TypeError("entries has the wrong length") + if coerce: + self._entries = [R(x) for x in self._entries] + elif copy: + self._entries = self._entries[:] + elif self._nrows == self._ncols: + self._entries = [zero]*(self._nrows*self._nrows) + for i in range(self._nrows): + self._entries[i+self._ncols*i]=entries + elif entries == zero: + self._entries = [zero]*(self._nrows*self._ncols) else: + raise TypeError("nonzero scalar matrix must be square") - zero = parent.base_ring()(0) - self._entries = [zero]*(self._nrows*self._ncols) + cdef Matrix_generic_dense _new(self, Py_ssize_t nrows, Py_ssize_t ncols): + r""" + Return a new dense matrix with no entries set. + """ + cdef Matrix_generic_dense res + res = self.__class__.__new__(self.__class__, 0, 0, 0) - if x != zero: - if self._nrows != self._ncols: - raise TypeError, "nonzero scalar matrix must be square" - for i from 0 <= i < self._nrows: - self._entries[i*self._ncols + i] = x + if nrows == self._nrows and ncols == self._ncols: + res._parent = self._parent + else: + res._parent = self.matrix_space(nrows, ncols) + res._ncols = ncols + res._nrows = nrows + res._base_ring = self._base_ring + return res cdef set_unsafe(self, Py_ssize_t i, Py_ssize_t j, value): - Py_DECREF(PyList_GET_ITEM(self._entries, i*self._ncols + j)) - Py_INCREF(value) - PyList_SET_ITEM(self._entries, i*self._ncols + j, value) + self._entries[i*self._ncols + j] = value cdef get_unsafe(self, Py_ssize_t i, Py_ssize_t j): - return PyList_GET_ITEM(self._entries, i*self._ncols + j) + return self._entries[i*self._ncols + j] def _pickle(self): """ @@ -152,19 +168,6 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): else: raise RuntimeError, "unknown matrix version" - def __richcmp__(matrix.Matrix self, right, int op): # always need for mysterious reasons. - """ - EXAMPLES: - sage: A = random_matrix(Integers(25)['x'],2) - sage: cmp(A,A) - 0 - sage: cmp(A,A+1) - -1 - sage: cmp(A+1,A) - 1 - """ - return self._richcmp(right, op) - def __hash__(self): """ EXAMPLES: @@ -243,17 +246,25 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): ... IndexError: polynomials are immutable """ - A = self.__class__(self._parent, self._entries, copy = True, coerce=False) + cdef Matrix_generic_dense A + A = self._new(self._nrows, self._ncols) + A._entries = self._entries[:] if self._subdivisions is not None: A.subdivide(*self.subdivisions()) return A + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.overflowcheck(False) def _multiply_classical(left, matrix.Matrix _right): """ Multiply the matrices left and right using the classical `O(n^3)` algorithm. - EXAMPLES: We multiply two matrices over a fairly general ring:: + EXAMPLES: + + We multiply two matrices over a fairly general ring:: + sage: R. = Integers(8)['x,y'] sage: a = matrix(R,2,[x,y,x^2,y^2]); a [ x y] @@ -290,39 +301,29 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): [0 0 0 0] """ cdef Py_ssize_t i, j, k, m, nr, nc, snc, p - cdef object v - cdef Matrix_generic_dense A, right - right = _right + cdef Matrix_generic_dense right = _right if left._ncols != right._nrows: - raise IndexError, "Number of columns of left must equal number of rows of other." + raise IndexError("Number of columns of left must equal number of rows of other.") nr = left._nrows nc = right._ncols snc = left._ncols R = left.base_ring() - P = left.matrix_space(nr, nc) - v = PyList_New(left._nrows * right._ncols) - zero = R(0) + cdef list v = [None] * (left._nrows * right._ncols) + zero = R.zero() p = 0 - cdef PyObject *l - cdef PyObject *r - for i from 0 <= i < nr: - for j from 0 <= j < nc: + for i in range(nr): + for j in range(nc): z = zero m = i*snc - for k from 0 <= k < snc: - # The following is really: - # z = z + left._entries[m + k] * right._entries[k*right._ncols + j] - l = PyList_GET_ITEM(left._entries, m+k) - r = PyList_GET_ITEM(right._entries, k*nc + j) - z = z + PyNumber_Multiply(l, r) - Py_INCREF(z); PyList_SET_ITEM(v, p, z) # Basically this is "v.append(z)" - p = p + 1 - - A = left.__class__.__new__(left.__class__, 0, 0 ,0) - matrix.Matrix.__init__(A, P) + for k in range(snc): + z += left._entries[m+k]._mul_(right._entries[k*nc+j]) + v[p] = z + p += 1 + + cdef Matrix_generic_dense A = left._new(nr, nc) A._entries = v return A @@ -331,7 +332,8 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): Return reference to list of entries of self. For internal use only, since this circumvents immutability. - EXAMPLES: + EXAMPLES:: + sage: A = random_matrix(Integers(25)['x'],2); A.set_immutable() sage: A._list()[0] = 0 sage: A._list()[0] @@ -342,26 +344,9 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): ######################################################################## # LEVEL 3 functionality (Optional) # * cdef _sub_ - # x * __deepcopy__ + # * __deepcopy__ # * __invert__ # * _multiply_classical # * Matrix windows -- only if you need strassen for that base # * Other functions (list them here): ######################################################################## - - def __deepcopy__(self): - """ - EXAMPLES: - sage: R. = QQ[] - sage: A = matrix(R, 2, [1,2,x,x^2]) - sage: B = A.__deepcopy__() - sage: A[0,0]._unsafe_mutate(1,2/3) - sage: A - [2/3*x + 1 2] - [ x x^2] - sage: B - [ 1 2] - [ x x^2] - """ - import copy - return self.__class__(self._parent, copy.deepcopy(self._entries), copy = False, coerce=False) diff --git a/src/sage/matrix/matrix_generic_sparse.pyx b/src/sage/matrix/matrix_generic_sparse.pyx index f406656082b..7994d4726a3 100644 --- a/src/sage/matrix/matrix_generic_sparse.pyx +++ b/src/sage/matrix/matrix_generic_sparse.pyx @@ -313,9 +313,6 @@ cdef class Matrix_generic_sparse(matrix_sparse.Matrix_sparse): else: raise RuntimeError("unknown matrix version (=%s)"%version) - def __richcmp__(matrix.Matrix self, right, int op): # always need for mysterious reasons. - return self._richcmp(right, op) - def __hash__(self): return self._hash() diff --git a/src/sage/matrix/matrix_gf2e_dense.pxd b/src/sage/matrix/matrix_gf2e_dense.pxd new file mode 100644 index 00000000000..3689f093829 --- /dev/null +++ b/src/sage/matrix/matrix_gf2e_dense.pxd @@ -0,0 +1,12 @@ +from sage.libs.m4rie cimport mzed_t + +cimport matrix_dense + +cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): + cdef mzed_t *_entries + cdef object _one + cdef object _zero + + cpdef Matrix_gf2e_dense _multiply_newton_john(Matrix_gf2e_dense self, Matrix_gf2e_dense right) + cpdef Matrix_gf2e_dense _multiply_karatsuba(Matrix_gf2e_dense self, Matrix_gf2e_dense right) + cpdef Matrix_gf2e_dense _multiply_strassen(Matrix_gf2e_dense self, Matrix_gf2e_dense right, cutoff=*) diff --git a/src/sage/matrix/matrix_mod2e_dense.pyx b/src/sage/matrix/matrix_gf2e_dense.pyx similarity index 91% rename from src/sage/matrix/matrix_mod2e_dense.pyx rename to src/sage/matrix/matrix_gf2e_dense.pyx index 01e49acb727..c7dd89a2a6b 100644 --- a/src/sage/matrix/matrix_mod2e_dense.pyx +++ b/src/sage/matrix/matrix_gf2e_dense.pyx @@ -10,7 +10,7 @@ The M4RIE library offers two matrix representations: words such that each element is filled with zeroes until the next power of two. Thus, for example, elements of `\GF{2^3}` are represented as ``[0xxx|0xxx|0xxx|0xxx|...]``. This representation is - wrapped as :class:`Matrix_mod2e_dense` in Sage. + wrapped as :class:`Matrix_gf2e_dense` in Sage. Multiplication and elimination both use "Newton-John" tables. These tables are simply all possible multiples of a given row in a matrix @@ -50,7 +50,7 @@ AUTHOR: TESTS:: - sage: TestSuite(sage.matrix.matrix_mod2e_dense.Matrix_mod2e_dense).run(verbose=True) + sage: TestSuite(sage.matrix.matrix_gf2e_dense.Matrix_gf2e_dense).run(verbose=True) running ._test_pickling() . . . pass TODO: @@ -66,6 +66,16 @@ REFERENCES: http://arxiv.org/abs/0901.1413 """ +#***************************************************************************** +# Copyright (C) 2010 Martin Albrecht +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + include "sage/ext/interrupt.pxi" cimport matrix_dense @@ -100,9 +110,9 @@ cdef class M4RIE_finite_field: """ EXAMPLE:: - sage: from sage.matrix.matrix_mod2e_dense import M4RIE_finite_field + sage: from sage.matrix.matrix_gf2e_dense import M4RIE_finite_field sage: K = M4RIE_finite_field(); K - + """ pass @@ -110,7 +120,7 @@ cdef class M4RIE_finite_field: """ EXAMPLE:: - sage: from sage.matrix.matrix_mod2e_dense import M4RIE_finite_field + sage: from sage.matrix.matrix_gf2e_dense import M4RIE_finite_field sage: K = M4RIE_finite_field() sage: del K """ @@ -123,7 +133,7 @@ cdef m4ri_word poly_to_word(f): cdef object word_to_poly(w, F): return F.fetch_int(w) -cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): +cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): ######################################################################## # LEVEL 1 functionality ######################################################################## @@ -336,11 +346,11 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): [a^3 + a^2 + 1 a^3 + a^2 + a a^3 + a^2 + a a^3 + 1] [a^3 + a^2 + 1 a^3 + a^2 a^3 + a^2 a^2] """ - cdef Matrix_mod2e_dense A - A = Matrix_mod2e_dense.__new__(Matrix_mod2e_dense, self._parent, 0, 0, 0, alloc=False) + cdef Matrix_gf2e_dense A + A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0, alloc=False) if self._nrows == 0 or self._ncols == 0: return A - A._entries = mzed_add(NULL, self._entries, (right)._entries) + A._entries = mzed_add(NULL, self._entries, (right)._entries) return A @@ -348,7 +358,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): """ EXAMPLE:: - sage: from sage.matrix.matrix_mod2e_dense import Matrix_mod2e_dense + sage: from sage.matrix.matrix_gf2e_dense import Matrix_gf2e_dense sage: K. = GF(2^4) sage: m,n = 3, 4 sage: MS = MatrixSpace(K,m,n) @@ -406,13 +416,13 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if self._ncols != right._nrows: raise ArithmeticError("left ncols must match right nrows") - cdef Matrix_mod2e_dense ans + cdef Matrix_gf2e_dense ans ans = self.new_matrix(nrows = self.nrows(), ncols = right.ncols()) if self._nrows == 0 or self._ncols == 0 or right._ncols == 0: return ans sig_on() - ans._entries = mzed_mul_naive(ans._entries, self._entries, (right)._entries) + ans._entries = mzed_mul_naive(ans._entries, self._entries, (right)._entries) sig_off() return ans @@ -449,17 +459,17 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if self._ncols != right._nrows: raise ArithmeticError("left ncols must match right nrows") - cdef Matrix_mod2e_dense ans + cdef Matrix_gf2e_dense ans ans = self.new_matrix(nrows = self.nrows(), ncols = right.ncols()) if self._nrows == 0 or self._ncols == 0 or right._ncols == 0: return ans sig_on() - ans._entries = mzed_mul(ans._entries, self._entries, (right)._entries) + ans._entries = mzed_mul(ans._entries, self._entries, (right)._entries) sig_off() return ans - cpdef Matrix_mod2e_dense _multiply_newton_john(Matrix_mod2e_dense self, Matrix_mod2e_dense right): + cpdef Matrix_gf2e_dense _multiply_newton_john(Matrix_gf2e_dense self, Matrix_gf2e_dense right): """ Return A*B using Newton-John tables. @@ -510,18 +520,18 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if self._ncols != right._nrows: raise ArithmeticError("left ncols must match right nrows") - cdef Matrix_mod2e_dense ans + cdef Matrix_gf2e_dense ans ans = self.new_matrix(nrows = self.nrows(), ncols = right.ncols()) if self._nrows == 0 or self._ncols == 0 or right._ncols == 0: return ans sig_on() - ans._entries = mzed_mul_newton_john(ans._entries, self._entries, (right)._entries) + ans._entries = mzed_mul_newton_john(ans._entries, self._entries, (right)._entries) sig_off() return ans - cpdef Matrix_mod2e_dense _multiply_karatsuba(Matrix_mod2e_dense self, Matrix_mod2e_dense right): + cpdef Matrix_gf2e_dense _multiply_karatsuba(Matrix_gf2e_dense self, Matrix_gf2e_dense right): """ Matrix multiplication using Karatsuba over polynomials with matrix coefficients over GF(2). @@ -558,18 +568,18 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if self._ncols != right._nrows: raise ArithmeticError("left ncols must match right nrows") - cdef Matrix_mod2e_dense ans + cdef Matrix_gf2e_dense ans ans = self.new_matrix(nrows = self.nrows(), ncols = right.ncols()) if self._nrows == 0 or self._ncols == 0 or right._ncols == 0: return ans sig_on() - ans._entries = mzed_mul_karatsuba(ans._entries, self._entries, (right)._entries) + ans._entries = mzed_mul_karatsuba(ans._entries, self._entries, (right)._entries) sig_off() return ans - cpdef Matrix_mod2e_dense _multiply_strassen(Matrix_mod2e_dense self, Matrix_mod2e_dense right, cutoff=0): + cpdef Matrix_gf2e_dense _multiply_strassen(Matrix_gf2e_dense self, Matrix_gf2e_dense right, cutoff=0): """ Winograd-Strassen matrix multiplication with Newton-John multiplication as base case. @@ -609,17 +619,17 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if self._ncols != right._nrows: raise ArithmeticError("left ncols must match right nrows") - cdef Matrix_mod2e_dense ans + cdef Matrix_gf2e_dense ans ans = self.new_matrix(nrows = self.nrows(), ncols = right.ncols()) if self._nrows == 0 or self._ncols == 0 or right._ncols == 0: return ans if cutoff == 0: - cutoff = _mzed_strassen_cutoff(ans._entries, self._entries, (right)._entries) + cutoff = _mzed_strassen_cutoff(ans._entries, self._entries, (right)._entries) sig_on() - ans._entries = mzed_mul_strassen(ans._entries, self._entries, (right)._entries, cutoff) + ans._entries = mzed_mul_strassen(ans._entries, self._entries, (right)._entries, cutoff) sig_off() return ans @@ -660,7 +670,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): [ a 1 0 a a + 1 0 0 a + 1 1 a + 1] """ cdef m4ri_word a = poly_to_word(right) - cdef Matrix_mod2e_dense C = Matrix_mod2e_dense.__new__(Matrix_mod2e_dense, self._parent, 0, 0, 0) + cdef Matrix_gf2e_dense C = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) mzed_mul_scalar(C._entries, a, self._entries) return C @@ -681,7 +691,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): """ return self.__copy__() - def __richcmp__(Matrix self, right, int op): # always need for mysterious reasons. + cpdef int _cmp_(self, Element right) except -2: """ EXAMPLE:: @@ -694,12 +704,9 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): sage: A == B False """ - return self._richcmp(right, op) - - cpdef int _cmp_(self, Element right) except -2: if self._nrows == 0 or self._ncols == 0: return 0 - return mzed_cmp(self._entries, (right)._entries) + return mzed_cmp(self._entries, (right)._entries) def __copy__(self): """ @@ -721,8 +728,8 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): sage: A2[0,0] a^2 """ - cdef Matrix_mod2e_dense A - A = Matrix_mod2e_dense.__new__(Matrix_mod2e_dense, self._parent, 0, 0, 0) + cdef Matrix_gf2e_dense A + A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) if self._nrows and self._ncols: mzed_copy(A._entries, self._entries) @@ -1000,8 +1007,8 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): [0 1 0] [0 0 1] """ - cdef Matrix_mod2e_dense A - A = Matrix_mod2e_dense.__new__(Matrix_mod2e_dense, self._parent, 0, 0, 0) + cdef Matrix_gf2e_dense A + A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) if self._nrows and self._nrows == self._ncols: mzed_invert_newton_john(A._entries, self._entries) @@ -1135,7 +1142,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): """ mzed_col_swap(self._entries, col1, col2) - def augment(self, Matrix_mod2e_dense right): + def augment(self, Matrix_gf2e_dense right): """ Augments ``self`` with ``right``. @@ -1193,10 +1200,10 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): sage: M.augment(N) [] """ - cdef Matrix_mod2e_dense A + cdef Matrix_gf2e_dense A if self._nrows != right._nrows: - raise TypeError, "Both numbers of rows must match." + raise TypeError("Both numbers of rows must match.") if self._ncols == 0: return right.__copy__() @@ -1209,7 +1216,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): A._entries = mzed_concat(A._entries, self._entries, right._entries) return A - def stack(self, Matrix_mod2e_dense other): + def stack(self, Matrix_gf2e_dense other): """ Stack ``self`` on top of ``other``. @@ -1265,14 +1272,14 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): [] """ if self._ncols != other._ncols: - raise TypeError, "Both numbers of columns must match." + raise TypeError("Both numbers of columns must match.") if self._nrows == 0: return other.__copy__() if other._nrows == 0: return self.__copy__() - cdef Matrix_mod2e_dense A + cdef Matrix_gf2e_dense A A = self.new_matrix(nrows = self._nrows + other._nrows) if self._ncols == 0: return A @@ -1338,7 +1345,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): if highr > self._entries.nrows: raise TypeError("Expected highr <= self.nrows(), but got %d > %d instead."%(highr, self._entries.nrows)) - cdef Matrix_mod2e_dense A = self.new_matrix(nrows = nrows, ncols = ncols) + cdef Matrix_gf2e_dense A = self.new_matrix(nrows = nrows, ncols = ncols) if ncols == 0 or nrows == 0: return A A._entries = mzed_submatrix(A._entries, self._entries, row, col, highr, highc) @@ -1391,8 +1398,8 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^8) sage: A = random_matrix(K,70,70) sage: f, s= A.__reduce__() - sage: from sage.matrix.matrix_mod2e_dense import unpickle_matrix_mod2e_dense_v0 - sage: f == unpickle_matrix_mod2e_dense_v0 + sage: from sage.matrix.matrix_gf2e_dense import unpickle_matrix_gf2e_dense_v0 + sage: f == unpickle_matrix_gf2e_dense_v0 True sage: f(*s) == A True @@ -1403,7 +1410,7 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): MS = MatrixSpace(GF(2), self._entries.x.nrows, self._entries.x.ncols) A = Matrix_mod2_dense.__new__(Matrix_mod2_dense, MS, 0, 0, 0, alloc = False) A._entries = mzd_copy( NULL, self._entries.x) - return unpickle_matrix_mod2e_dense_v0, (A, self.base_ring(), self.nrows(), self.ncols()) + return unpickle_matrix_gf2e_dense_v0, (A, self.base_ring(), self.nrows(), self.ncols()) def slice(self): r""" @@ -1590,22 +1597,29 @@ cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): mzed_cling(self._entries, v) mzd_slice_free(v) -def unpickle_matrix_mod2e_dense_v0(Matrix_mod2_dense a, base_ring, nrows, ncols): - """ +def unpickle_matrix_gf2e_dense_v0(Matrix_mod2_dense a, base_ring, nrows, ncols): + r""" EXAMPLE:: sage: K. = GF(2^2) sage: A = random_matrix(K,10,10) sage: f, s= A.__reduce__() - sage: from sage.matrix.matrix_mod2e_dense import unpickle_matrix_mod2e_dense_v0 - sage: f == unpickle_matrix_mod2e_dense_v0 + sage: from sage.matrix.matrix_gf2e_dense import unpickle_matrix_gf2e_dense_v0 + sage: f == unpickle_matrix_gf2e_dense_v0 True sage: f(*s) == A True + + We can still unpickle pickles from before :trac:`19240`:: + + sage: old_pickle = 'x\x9c\x85RKo\xd3@\x10\xae\xdd$$\xdb&\xe5U\x1e-\x8f\xc2\xc9\x12RD#$\xce\xa0\xb4\x80\x07\xa2\xca\xc2\x07\x0e\xd5\xe2:\x1b\xdb\x8acg\x1c\xa7J\x85*!\xa4\x90\xe6\x07p\xe0\xc4\x01q\xe5\xc4\x19\xf5\xd0?\xc1\x81\xdf\x80\xb8q\x0b\xb3\x8eMS\xa1\x82V;;\xb3\xdf\xce\xf7\xcd\x8e\xe6\xb5j\xf7,GT;V\x1cy\x83\xf4\xe0\x9d\xb0Y\x13\xbc)\x82\x9e`\xfd\xa0\xeb\xd9m_\xf0\xbf1\xbe{\x97\xa1\xa2\x9d\xc6\xf0\x0f\x82,\x7f\x9d\xa1\xaa\x81\n\xb9m\x9c\xd7\xf4\xf1d2\x81-h\xc0#(\x03\x83\x15\xdas\xc9*\xc3\x13x\x0cu0\xd28\x97\x9e*(0\x9f\xfa\x1b\xd0\xd2\x7fH\x82\xb5\xf4\xa2@TO\xe19\x01I\xac\x136\x991\x9f\xa4\xf9&\xcd\x07i\xbe\xcb\xd4ib\t\xba\xa4\xf6\x02zIT\xd1\x8f2(u\x15\xfd\x9d<\xee@\x05V\xd3\x94E*\xb0\x0e\x0fH\xad\xa8\xbf\x97\xa0\r\x03\xfd\xf0\xb8\x1aU\xff\x92\x90\xe8?\xa5\xd6\x814_\xa5\xf9(\xcd\xafc\xe99\xe2\xd9\xa0\x06\xd4\xf5\xcf\xf2\xf2!\xbc\xd4\xdf\x90#\xc0\x8f\r\xccM\x1b\xdd\x8b\xa3\xbe\x1d\xf7#QmYv\x1cF{\xcc\x11\x81\x88<\x9b\xa71\xcf:\xce0\xaf\x9d\x96\xe3\x87a\xbb\xdf\xe5\x8e\x1f\xeeX>\xc3\x82\xb9\xb0\xe9\x05^,6=\xe17\xf1\xcc\xd0\xc0"u\xb0d\xe6wDl\xdd\x1fa)e\x8a\xbc\xc0\xe9U\xbd \x16\x8e\x88X\xc7j\x0b\x9e\x05\xc8L\xe5\x1e%.\x98\x8a5\xc4\xc5\xd9\xf7\xdd\xd0\xdf\x0b\xc2\x8eg\xf93.wZ\xb5\xc1\x94B\xf8\xa2#\x82\x98a\xf9\xffY\x12\xe3v\x18L\xff\x14Fl\xeb\x0ff\x10\xc4\xb0\xa2\xb9y\xcd-\xba%\xcd\xa5\x8ajT\xd1\x92\xa9\x0c\x86x\xb6a\xe6h\xf8\x02right)._matrix) @@ -915,7 +912,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse """ cdef Integer x = Integer(right) cdef fmpz_t z - cdef Matrix_integer_dense M = self._new_uninitialized_matrix(self._nrows, self._ncols) + cdef Matrix_integer_dense M = self._new(self._nrows, self._ncols) sig_on() fmpz_init_set_readonly(z, x.value) @@ -942,7 +939,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [ 9 11 13] [ 9 11 13] """ - cdef Matrix_integer_dense M = self._new_uninitialized_matrix(self._nrows,self._ncols) + cdef Matrix_integer_dense M = self._new(self._nrows,self._ncols) sig_on() fmpz_mat_add(M._matrix,self._matrix,( right)._matrix) @@ -962,7 +959,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [-2 0 2] [ 4 6 8] """ - cdef Matrix_integer_dense M = self._new_uninitialized_matrix(self._nrows,self._ncols) + cdef Matrix_integer_dense M = self._new(self._nrows,self._ncols) sig_on() fmpz_mat_sub(M._matrix,self._matrix,( right)._matrix) @@ -1056,7 +1053,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse if e == 1: return self - cdef Matrix_integer_dense M = self._new_uninitialized_matrix(self._nrows, self._ncols) + cdef Matrix_integer_dense M = self._new(self._nrows, self._ncols) sig_on() fmpz_mat_pow(M._matrix, self._matrix, e) sig_off() @@ -1076,7 +1073,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [ 0 -1] [-2 -3] """ - cdef Matrix_integer_dense M = self._new_uninitialized_matrix(self._nrows, self._ncols) + cdef Matrix_integer_dense M = self._new(self._nrows, self._ncols) sig_on() fmpz_mat_neg(M._matrix, self._matrix) sig_off() @@ -1643,9 +1640,14 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse - ``algorithm`` -- String. The algorithm to use. Valid options are: - - ``'default'`` -- Let Sage pick an algorithm (default). Up - to 10 rows or columns: pari with flag 0; Up to 75 rows or - columns: pari with flag 1; Larger: use padic algorithm. + - ``'default'`` -- Let Sage pick an algorithm (default). + Up to 75 rows or columns with no transformation matrix, + use pari with flag 0; otherwise, use flint. + + - ``'flint'`` - use flint + + - ``'ntl'`` - use NTL (only works for square matrices of + full rank!) - ``'padic'`` - an asymptotically fast p-adic modular algorithm, If your matrix has large coefficients and is @@ -1655,10 +1657,9 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse - ``'pari0'`` - use PARI with flag 0 - - ``'pari4'`` - use PARI with flag 4 (use heuristic LLL) + - ``'pari1'`` - use PARI with flag 1 - - ``'ntl'`` - use NTL (only works for square matrices of - full rank!) + - ``'pari4'`` - use PARI with flag 4 (use heuristic LLL) - ``proof`` - (default: True); if proof=False certain determinants are computed using a randomized hybrid p-adic @@ -1670,7 +1671,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse don't include zero rows - ``transformation`` - if given, also compute - transformation matrix; only valid for padic algorithm + transformation matrix; only valid for flint and padic algorithm - ``D`` - (default: None) if given and the algorithm is 'ntl', then D must be a multiple of the determinant and this @@ -1678,7 +1679,8 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse OUTPUT: - The Hermite normal form (=echelon form over `\ZZ`) of self. + The Hermite normal form (=echelon form over `\ZZ`) of self as + an immutable matrix. EXAMPLES:: @@ -1791,7 +1793,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse TESTS: - This example illustrated trac 2398:: + This example illustrated :trac:`2398`:: sage: a = matrix([(0, 0, 3), (0, -2, 2), (0, 1, 2), (0, -2, 5)]) sage: a.hermite_form() @@ -1800,7 +1802,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [0 0 0] [0 0 0] - Check that #12280 is fixed:: + Check that :trac:`12280` is fixed:: sage: m = matrix([(-2, 1, 9, 2, -8, 1, -3, -1, -4, -1), ... (5, -2, 0, 1, 0, 4, -1, 1, -2, 0), @@ -1830,44 +1832,84 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [ 0 0 0 0 0 0 0 200 0 0] [ 0 0 0 0 0 0 0 0 200 0] [ 0 0 0 0 0 0 0 0 0 200] + + Check that the output is correct in corner cases, see :trac:`18613`:: + + sage: m = matrix(2, 0) + sage: m.parent() + Full MatrixSpace of 2 by 0 dense matrices over Integer Ring + sage: H, U = m.echelon_form(transformation=True) + sage: H.parent() + Full MatrixSpace of 2 by 0 dense matrices over Integer Ring + sage: H.is_immutable() + True + sage: U + [1 0] + [0 1] + sage: H == U * m + True + sage: H, U = m.echelon_form(transformation=True, + ....: include_zero_rows=False) + sage: H.parent() + Full MatrixSpace of 0 by 0 dense matrices over Integer Ring + sage: U.parent() + Full MatrixSpace of 0 by 2 dense matrices over Integer Ring + sage: H == U * m + True + sage: m = random_matrix(ZZ, 100, 100, x=-1000, y=1000, density=.1) + sage: m.parent() + Full MatrixSpace of 100 by 100 dense matrices over Integer Ring + sage: H, U = m.hermite_form(algorithm="flint", transformation=True) + sage: H == U*m + True """ - if self._nrows == 0 or self._ncols == 0: - self.cache('pivots', ()) - self.cache('rank', 0) - if transformation: - return self, self - return self - key = 'hnf-%s-%s'%(include_zero_rows,transformation) ans = self.fetch(key) if ans is not None: return ans - cdef Matrix_integer_dense H_m,w + cdef Matrix_integer_dense H_m,w,U cdef Py_ssize_t nr, nc, n, i, j nr = self._nrows nc = self._ncols n = nr if nr >= nc else nc if algorithm == 'default': - if transformation: algorithm = 'padic' + if transformation: algorithm = 'flint' else: - if n <= 10: algorithm = 'pari0' - elif n <= 75: algorithm = 'pari' - else: algorithm = 'padic' - - cdef bint pari_big = 0 - if algorithm.startswith('pari'): - if self.height().ndigits() > 10000 or n >= 50: - pari_big = 1 - + if n < 75: algorithm = 'pari0' + else: algorithm = 'flint' proof = get_proof_flag(proof, "linear_algebra") pivots = None - rank = None - if algorithm == "padic": + if nr == 0 or nc == 0: + pivots = () + if include_zero_rows: + H_m = self.new_matrix() + U = self.matrix_space(nr, nr).one() + else: + H_m = self.new_matrix(0, nc) + U = self.new_matrix(0, nr) + elif algorithm == "flint": + H_m = self._new(nr, nc) + + if transformation: + U = self._new(nr, nr) + sig_on() + fmpz_mat_hnf_transform(H_m._matrix, U._matrix, self._matrix) + sig_off() + else: + sig_on() + fmpz_mat_hnf(H_m._matrix, self._matrix) + sig_off() + if not include_zero_rows: + r = H_m.rank() + H_m = H_m[:r] + if transformation: + U = U[:r] + elif algorithm == "padic": import matrix_integer_dense_hnf self._init_mpz() if transformation: - H_m, U, pivots = matrix_integer_dense_hnf.hnf_with_transformation(self, proof=proof) + H_m, U = matrix_integer_dense_hnf.hnf_with_transformation(self, proof=proof) if not include_zero_rows: r = H_m.rank() H_m = H_m[:r] @@ -1875,38 +1917,15 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse else: H_m, pivots = matrix_integer_dense_hnf.hnf(self, include_zero_rows=include_zero_rows, proof=proof) - self.cache('pivots', tuple(pivots)) - self.cache('rank', len(pivots)) - - - elif algorithm == 'pari0': - if transformation: - raise ValueError("transformation matrix only available with p-adic algorithm") - if pari_big: - H_m = self._hnf_pari_big(0, include_zero_rows=include_zero_rows) - else: - H_m = self._hnf_pari(0, include_zero_rows=include_zero_rows) - - elif algorithm == 'pari': - if transformation: - raise ValueError("transformation matrix only available with p-adic algorithm") - if pari_big: - H_m = self._hnf_pari_big(1, include_zero_rows=include_zero_rows) - else: - H_m = self._hnf_pari(1, include_zero_rows=include_zero_rows) - - elif algorithm == 'pari4': - if transformation: - raise ValueError("transformation matrix only available with p-adic algorithm") - if pari_big: - H_m = self._hnf_pari_big(4, include_zero_rows=include_zero_rows) + elif transformation: + raise ValueError("transformation matrix only available with p-adic algorithm") + elif algorithm in ["pari", "pari0", "pari1", "pari4"]: + flag = int(algorithm[-1]) if algorithm != "pari" else 1 + if self.height().ndigits() > 10000 or n >= 50: + H_m = self._hnf_pari_big(flag, include_zero_rows=include_zero_rows) else: - H_m = self._hnf_pari(4, include_zero_rows=include_zero_rows) - + H_m = self._hnf_pari(flag, include_zero_rows=include_zero_rows) elif algorithm == 'ntl': - if transformation: - raise ValueError("transformation matrix only available with p-adic algorithm") - if nr != nc: raise ValueError("ntl only computes HNF for square matrices of full rank.") @@ -1921,8 +1940,6 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse except RuntimeError: # HNF may fail if a nxm matrix has rank < m raise ValueError("ntl only computes HNF for square matrices of full rank.") - rank = w1.nrows() - if include_zero_rows: H_m = self.new_matrix() else: @@ -1935,28 +1952,21 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse for j from 0 <= j < w1.ncols(): H_m[i,j] = w1[nr-i-1,nc-j-1] - elif algorithm == 'flint': - raise NotImplementedError('not yet implemented') else: raise ValueError("algorithm %r not understood" % algorithm) H_m.set_immutable() if pivots is None: from matrix_integer_dense_hnf import pivots_of_hnf_matrix - pivots = tuple(pivots_of_hnf_matrix(H_m)) - rank = len(pivots) - else: - pivots = tuple(pivots) - + pivots = pivots_of_hnf_matrix(H_m) + pivots = tuple(pivots) + rank = len(pivots) H_m.cache('pivots', pivots) self.cache('pivots', pivots) - H_m.cache('rank', rank) self.cache('rank',rank) - H_m.cache('in_echelon_form', True) - if transformation: U.set_immutable() ans = H_m, U @@ -3225,7 +3235,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse TEST: - Check that trac:`9345` is fixed:: + Check that :trac:`9345` is fixed:: sage: A = random_matrix(ZZ, 3, 3) sage: A.rational_reconstruction(0) @@ -3640,7 +3650,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse sig_off() # Now read the answer as a matrix. cdef Matrix_integer_dense M - M = self._new_uninitialized_matrix(self._ncols,dim) + M = self._new(self._ncols,dim) k = 0 for i from 0 <= i < self._ncols: for j from 0 <= j < dim: @@ -3674,7 +3684,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse sig_off() # Now read the answer as a matrix. cdef Matrix_integer_dense M - M = self._new_uninitialized_matrix(self._ncols,dim) + M = self._new(self._ncols,dim) for i from 0 <= i < M._nrows: for j from 0 <= j < M._ncols: fmpz_set(fmpz_mat_entry(M._matrix,i,j),fmpz_mat_entry(M0,i,j)) @@ -3791,7 +3801,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse cdef Integer den = Integer(0) cdef fmpz_t fden fmpz_init(fden) - M = self._new_uninitialized_matrix(self._nrows,self._ncols) + M = self._new(self._nrows,self._ncols) verbose('computing inverse of %s x %s matrix using FLINT'%(self._nrows, self._ncols)) sig_on() res = fmpz_mat_inv(M._matrix,fden,self._matrix) @@ -4165,7 +4175,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse sig_on() nonsingSolvLlhsMM(solu_pos, n, m, self._entries, B._entries, mp_N, mp_D) sig_off() - M = self._new_uninitialized_matrix(P.nrows(),P.ncols()) + M = self._new(P.nrows(),P.ncols()) k = 0 for i from 0 <= i < M._nrows: for j from 0 <= j < M._ncols: @@ -4288,7 +4298,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse if m == 0 or n == 0: return self.new_matrix(nrows = n, ncols = m), Integer(1) - M = self._new_uninitialized_matrix(self._ncols,B._ncols) + M = self._new(self._ncols,B._ncols) sig_on() fmpz_mat_solve(M._matrix,tmp,self._matrix,B._matrix) fmpz_get_mpz(den.value,tmp) @@ -4726,7 +4736,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse - ``matrix`` - the Hermite normal form of self. """ t = verbose('hermite mod %s'%D, caller_name='matrix_integer_dense') - cdef Matrix_integer_dense res = self._new_uninitialized_matrix(self._nrows,self._ncols) + cdef Matrix_integer_dense res = self._new(self._nrows,self._ncols) self._hnf_modn(res, D) verbose('finished hnf mod', t, caller_name='matrix_integer_dense') return res @@ -5048,7 +5058,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [ 6 7 8] [ 1 5 -10] """ - cdef Matrix_integer_dense res = self._new_uninitialized_matrix(self._nrows + 1, self._ncols) + cdef Matrix_integer_dense res = self._new(self._nrows + 1, self._ncols) cdef Py_ssize_t j cdef Integer z cdef fmpz_t zflint @@ -5294,7 +5304,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [2 5] """ cdef Matrix_integer_dense A - A = self._new_uninitialized_matrix(self._ncols,self._nrows) + A = self._new(self._ncols,self._nrows) sig_on() fmpz_mat_transpose(A._matrix,self._matrix) sig_off() @@ -5333,7 +5343,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse nr , nc = (self._nrows, self._ncols) cdef Matrix_integer_dense A - A = self._new_uninitialized_matrix(nc,nr) + A = self._new(nc,nr) cdef Py_ssize_t i,j cdef Py_ssize_t ri,rj # reversed i and j sig_on() diff --git a/src/sage/matrix/matrix_integer_dense_hnf.py b/src/sage/matrix/matrix_integer_dense_hnf.py index 190d9e6b137..498c6fda4a1 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): def hnf_with_transformation(A, proof=True): """ Compute the HNF H of A along with a transformation matrix U - such that U*A = H. Also return the pivots of H. + such that U*A = H. INPUT: @@ -1095,7 +1095,6 @@ def hnf_with_transformation(A, proof=True): - matrix -- the Hermite normal form H of A - U -- a unimodular matrix such that U * A = H - - pivots -- the pivot column positions of A EXAMPLES:: @@ -1103,7 +1102,7 @@ def hnf_with_transformation(A, proof=True): sage: A = matrix(ZZ, 2, [1, -5, -10, 1, 3, 197]); A [ 1 -5 -10] [ 1 3 197] - sage: H, U, pivots = matrix_integer_dense_hnf.hnf_with_transformation(A) + sage: H, U = matrix_integer_dense_hnf.hnf_with_transformation(A) sage: H [ 1 3 197] [ 0 8 207] @@ -1116,10 +1115,10 @@ def hnf_with_transformation(A, proof=True): """ # All we do is augment the input matrix with the identity matrix of the appropriate rank on the right. C = A.augment(identity_matrix(ZZ, A.nrows())) - H, pivots = hnf(C, include_zero_rows=True, proof=proof) + H, _ = hnf(C, include_zero_rows=True, proof=proof) U = H.matrix_from_columns(range(A.ncols(), H.ncols())) H2 = H.matrix_from_columns(range(A.ncols())) - return H2, U, pivots + return H2, U def hnf_with_transformation_tests(n=10, m=5, trials=10): """ @@ -1136,11 +1135,11 @@ def hnf_with_transformation_tests(n=10, m=5, trials=10): for i in range(trials): print i, sys.stdout.flush() - a = random_matrix(ZZ, n, m) - w = hnf_with_transformation(a) - assert w[0] == w[1]*a - w = hnf_with_transformation(a, proof=False) - assert w[0] == w[1]*a + A = random_matrix(ZZ, n, m) + H, U = hnf_with_transformation(A) + assert H == U * A + H, U = hnf_with_transformation(A, proof=False) + assert H == U * A ################################################################################################# diff --git a/src/sage/matrix/matrix_integer_sparse.pyx b/src/sage/matrix/matrix_integer_sparse.pyx index 3037abeeded..06245809fe4 100644 --- a/src/sage/matrix/matrix_integer_sparse.pyx +++ b/src/sage/matrix/matrix_integer_sparse.pyx @@ -52,7 +52,6 @@ cdef class Matrix_integer_sparse(matrix_sparse.Matrix_sparse): # * __init__ # * set_unsafe # * get_unsafe - # * __richcmp__ -- always the same # * __hash__ -- always simple ######################################################################## def __cinit__(self, parent, entries, copy, coerce): @@ -155,13 +154,9 @@ cdef class Matrix_integer_sparse(matrix_sparse.Matrix_sparse): mpz_vector_get_entry(x.value, &self._matrix[i], j) return x - def __richcmp__(Matrix self, right, int op): # always need for mysterious reasons. - return self._richcmp(right, op) - def __hash__(self): return self._hash() - ######################################################################## # LEVEL 2 functionality # * def _pickle diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 05bb8a8e288..6e1b3014f6f 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -105,8 +105,9 @@ include 'sage/ext/stdsage.pxi' cimport matrix_dense from libc.stdio cimport * -from sage.structure.element cimport Matrix, Vector -from sage.structure.element cimport ModuleElement, Element +from sage.structure.element cimport (Matrix, Vector, parent_c, + ModuleElement, Element) +from sage.modules.free_module_element cimport FreeModuleElement from sage.libs.gmp.random cimport * from sage.misc.functional import log from sage.misc.randstate cimport randstate, current_randstate @@ -269,39 +270,6 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse mzd_write_bit(self._entries,i,j, R(entries[k])) k = k + 1 - def __richcmp__(Matrix self, right, int op): - """ - Compares ``self`` with ``right``. While equality and - inequality are clearly defined, ``<`` and ``>`` are not. For - those first the matrix dimensions of ``self`` and ``right`` - are compared. If these match then ``<`` means that there is a - position smallest (i,j) in ``self`` where ``self[i,j]`` is - zero but ``right[i,j]`` is one. This (i,j) is smaller than the - (i,j) if ``self`` and ``right`` are exchanged for the - comparison. - - INPUT: - - - ``right`` - a matrix - - ``op`` - comparison operation - - EXAMPLE:: - - sage: A = MatrixSpace(GF(2),3,3).one() - sage: B = copy(MatrixSpace(GF(2),3,3).one()) - sage: B[0,1] = 1 - sage: A < B - True - - TESTS:: - - sage: A = matrix(GF(2),2,0) - sage: B = matrix(GF(2),2,0) - sage: A < B - False - """ - return self._richcmp(right, op) - def __hash__(self): r""" The has of a matrix is computed as `\oplus i*a_i` where the @@ -657,6 +625,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: r1 = A*v1 sage: r0.column(0) == r1 True + + TESTS: + + Check that :trac:`19378` is fixed:: + + sage: m = matrix(GF(2), 11, 0) + sage: v = vector(GF(2), 0) + sage: m * v + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) """ cdef mzd_t *tmp if not isinstance(v, Vector_mod2_dense): @@ -666,6 +643,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse raise ArithmeticError("number of columns of matrix must equal degree of vector") VS = VectorSpace(self._base_ring, self._nrows) + # If the vector is 0-dimensional, the result will be the 0-vector + if not self.ncols(): + return VS.zero() cdef Vector_mod2_dense c = Vector_mod2_dense.__new__(Vector_mod2_dense) c._init(self._nrows, VS) c._entries = mzd_init(1, self._nrows) @@ -1496,6 +1476,31 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse return A cpdef int _cmp_(self, Element right) except -2: + """ + Compares ``self`` with ``right``. While equality and + inequality are clearly defined, ``<`` and ``>`` are not. For + those first the matrix dimensions of ``self`` and ``right`` + are compared. If these match then ``<`` means that there is a + position smallest (i,j) in ``self`` where ``self[i,j]`` is + zero but ``right[i,j]`` is one. This (i,j) is smaller than the + (i,j) if ``self`` and ``right`` are exchanged for the + comparison. + + EXAMPLE:: + + sage: A = MatrixSpace(GF(2),3,3).one() + sage: B = copy(MatrixSpace(GF(2),3,3).one()) + sage: B[0,1] = 1 + sage: A < B + True + + TESTS:: + + sage: A = matrix(GF(2),2,0) + sage: B = matrix(GF(2),2,0) + sage: A < B + False + """ if self._nrows == 0 or self._ncols == 0: return 0 return mzd_cmp(self._entries, (right)._entries) @@ -1571,11 +1576,36 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: N = Matrix(GF(2), 0, 1, 0) sage: M.augment(N) [] + + Check that :trac:`19165` is solved:: + + sage: m = matrix(GF(2), 2, range(4)) + sage: m.augment(matrix(GF(2), 2, range(4), sparse=True)) + [0 1 0 1] + [0 1 0 1] + + sage: m.augment(1) + Traceback (most recent call last): + ... + TypeError: right must either be a matrix or a vector. Not + """ - if hasattr(right, '_vector_'): + cdef Matrix_mod2_dense other + + if isinstance(right, FreeModuleElement): right = right.column() - cdef Matrix_mod2_dense other = right + if isinstance(right, Matrix_mod2_dense): + other = right + elif isinstance(right, Matrix): + from sage.matrix.constructor import matrix + other = matrix(self.base_ring(), + right.nrows(), + right.ncols(), + right.list(), + sparse=False) + else: + raise TypeError("right must either be a matrix or a vector. Not {}".format(type(right))) if self._nrows != other._nrows: raise TypeError("Both numbers of rows must match.") diff --git a/src/sage/matrix/matrix_mod2e_dense.pxd b/src/sage/matrix/matrix_mod2e_dense.pxd deleted file mode 100644 index 92414cea218..00000000000 --- a/src/sage/matrix/matrix_mod2e_dense.pxd +++ /dev/null @@ -1,14 +0,0 @@ -# choose: dense or sparse - -from sage.libs.m4rie cimport mzed_t - -cimport matrix_dense - -cdef class Matrix_mod2e_dense(matrix_dense.Matrix_dense): - cdef mzed_t *_entries - cdef object _one - cdef object _zero - - cpdef Matrix_mod2e_dense _multiply_newton_john(Matrix_mod2e_dense self, Matrix_mod2e_dense right) - cpdef Matrix_mod2e_dense _multiply_karatsuba(Matrix_mod2e_dense self, Matrix_mod2e_dense right) - cpdef Matrix_mod2e_dense _multiply_strassen(Matrix_mod2e_dense self, Matrix_mod2e_dense right, cutoff=*) diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index bf064cb18fe..e02d0d9b56d 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -522,22 +522,6 @@ cdef class Matrix_modn_dense_template(matrix_dense.Matrix_dense): v[j] = (entries[k]) k = k + 1 - def __richcmp__(Matrix_modn_dense_template self, right, int op): # always need for mysterious reasons. - """ - EXAMPLES:: - - sage: A = matrix(ZZ, 10, 10, range(1000, 1100)) - sage: A.change_ring(GF(17)) == A.change_ring(GF(17)) - True - sage: A.change_ring(GF(17)) == A.change_ring(GF(19)) - False - sage: A.change_ring(GF(17)) == A.change_ring(Integers(2000)) - False - sage: A.change_ring(GF(17)) == A.change_ring(Integers(2000)) - False - """ - return self._richcmp(right, op) - def __hash__(self): """ EXAMPLE:: @@ -976,6 +960,18 @@ cdef class Matrix_modn_dense_template(matrix_dense.Matrix_dense): False sage: B + 3 == A True + + :: + + sage: A = matrix(ZZ, 10, 10, range(1000, 1100)) + sage: A.change_ring(GF(17)) == A.change_ring(GF(17)) + True + sage: A.change_ring(GF(17)) == A.change_ring(GF(19)) + False + sage: A.change_ring(GF(17)) == A.change_ring(Integers(2000)) + False + sage: A.change_ring(GF(17)) == A.change_ring(Integers(2000)) + False """ cdef Py_ssize_t i cdef celement* other_ent = (right)._entries diff --git a/src/sage/matrix/matrix_modn_sparse.pyx b/src/sage/matrix/matrix_modn_sparse.pyx index c36daac089a..cd820122efe 100644 --- a/src/sage/matrix/matrix_modn_sparse.pyx +++ b/src/sage/matrix/matrix_modn_sparse.pyx @@ -127,7 +127,6 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): # x * __init__ # x * set_unsafe # x * get_unsafe - # x * __richcmp__ -- always the same ######################################################################## def __cinit__(self, parent, entries, copy, coerce): matrix.Matrix.__init__(self, parent) @@ -235,8 +234,6 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): n.ivalue = get_entry(&self.rows[i], j) return n - def __richcmp__(matrix.Matrix self, right, int op): # always need for mysterious reasons. - return self._richcmp(right, op) def __hash__(self): return self._hash() diff --git a/src/sage/matrix/matrix_mpolynomial_dense.pyx b/src/sage/matrix/matrix_mpolynomial_dense.pyx index 31093f16585..f2c7b9d7bd0 100644 --- a/src/sage/matrix/matrix_mpolynomial_dense.pyx +++ b/src/sage/matrix/matrix_mpolynomial_dense.pyx @@ -38,7 +38,6 @@ cdef class Matrix_mpolynomial_dense(Matrix_generic_dense): """ Dense matrix over a multivariate polynomial ring over a field. """ - def echelon_form(self, algorithm='row_reduction', **kwds): """ Return an echelon form of ``self`` using chosen algorithm. diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 04ed1d200fa..c9982313d70 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -41,21 +41,25 @@ TESTS:: sage: TestSuite(a).run() """ -############################################################################## +#***************************************************************************** # Copyright (C) 2004,2005,2006 William Stein -# Distributed under the terms of the GNU General Public License (GPL) -# The full text of the GPL is available at: +# +# 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.modules.vector_rational_dense cimport Vector_rational_dense include "sage/ext/cdefs.pxi" include "sage/ext/interrupt.pxi" include "sage/ext/stdsage.pxi" -include "sage/ext/random.pxi" from sage.libs.gmp.rational_reconstruction cimport mpq_rational_reconstruction +from sage.libs.gmp.randomize cimport * from sage.libs.flint.fmpz cimport * from sage.libs.flint.fmpz_mat cimport * cimport sage.structure.element @@ -305,8 +309,6 @@ cdef class Matrix_rational_dense(matrix_dense.Matrix_dense): if mpq_set_str(self._entries[i], s, 32): raise RuntimeError("invalid pickle data") - def __richcmp__(Matrix self, right, int op): - return self._richcmp(right, op) def __hash__(self): return self._hash() diff --git a/src/sage/matrix/matrix_rational_sparse.pyx b/src/sage/matrix/matrix_rational_sparse.pyx index 64c70ac9ef6..f49bfec1d3a 100644 --- a/src/sage/matrix/matrix_rational_sparse.pyx +++ b/src/sage/matrix/matrix_rational_sparse.pyx @@ -56,7 +56,6 @@ cdef class Matrix_rational_sparse(matrix_sparse.Matrix_sparse): # * __init__ # * set_unsafe # * get_unsafe - # * __richcmp__ -- always the same # * __hash__ -- always simple ######################################################################## def __cinit__(self, parent, entries, copy, coerce): @@ -159,8 +158,6 @@ cdef class Matrix_rational_sparse(matrix_sparse.Matrix_sparse): mpq_vector_get_entry(x.value, &self._matrix[i], j) return x - def __richcmp__(Matrix self, right, int op): # always need for mysterious reasons. - return self._richcmp(right, op) def __hash__(self): return self._hash() diff --git a/src/sage/matrix/matrix_real_double_dense.pyx b/src/sage/matrix/matrix_real_double_dense.pyx index 2b5b1264322..9d7e1bc977e 100644 --- a/src/sage/matrix/matrix_real_double_dense.pyx +++ b/src/sage/matrix/matrix_real_double_dense.pyx @@ -94,7 +94,6 @@ cdef class Matrix_real_double_dense(matrix_double_dense.Matrix_double_dense): # * __init__ # * set_unsafe # * get_unsafe - # * __richcmp__ -- always the same # * __hash__ -- always simple ######################################################################## def __cinit__(self, parent, entries, copy, coerce): diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 8e954037d33..baac6e5efd9 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -46,7 +46,7 @@ import matrix_modn_sparse import matrix_mod2_dense -import matrix_mod2e_dense +import matrix_gf2e_dense import matrix_integer_dense import matrix_integer_sparse @@ -118,11 +118,13 @@ class MatrixSpace(UniqueRepresentation, parent_gens.ParentWithGens): sage: MatrixSpace(ZZ,10,5) Full MatrixSpace of 10 by 5 dense matrices over Integer Ring sage: MatrixSpace(ZZ,10,5).category() - Category of modules over (euclidean domains and infinite enumerated sets) + Category of infinite modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(ZZ,10,10).category() - Category of algebras over (euclidean domains and infinite enumerated sets) + Category of infinite algebras over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(QQ,10).category() - Category of algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) TESTS:: @@ -261,6 +263,15 @@ def __init__(self, base_ring, sage: A = MatrixSpace(RDF,1000,1000).random_element() sage: B = MatrixSpace(RDF,1000,1000).random_element() sage: C = A * B + + We check that :trac:`18186` is fixed:: + + sage: MatrixSpace(ZZ,0,3) in FiniteSets() + True + sage: MatrixSpace(Zmod(4),2) in FiniteSets() + True + sage: MatrixSpace(ZZ,2) in Sets().Infinite() + True """ self._implementation = implementation @@ -298,9 +309,42 @@ def __init__(self, base_ring, else: category = Modules(base_ring.category()) + if not self.__nrows or not self.__ncols: + is_finite = True + else: + is_finite = None + try: + is_finite = base_ring.is_finite() + except (AttributeError,NotImplementedError): + pass + + if is_finite is True: + category = category.Finite() + elif is_finite is False: + category = category.Infinite() + sage.structure.parent.Parent.__init__(self, category=category) #sage.structure.category_object.CategoryObject._init_category_(self, category) + def cardinality(self): + r""" + Return the number of elements in self. + + EXAMPLES:: + + sage: MatrixSpace(GF(3), 2, 3).cardinality() + 729 + sage: MatrixSpace(ZZ, 2).cardinality() + +Infinity + sage: MatrixSpace(ZZ, 0, 3).cardinality() + 1 + """ + if not self.__nrows or not self.__ncols: + from sage.rings.integer_ring import ZZ + return ZZ.one() + else: + return self.base_ring().cardinality() ** (self.__nrows * self.__ncols) + def full_category_initialisation(self): """ Make full use of the category framework. @@ -468,7 +512,7 @@ def __call__(self, entries=None, coerce=True, copy=True, sparse = False): cannot be converted to a matrix in Full MatrixSpace of 3 by 5 dense matrices over Integer Ring! - Check that trac:`15110` is fixed:: + Check that :trac:`15110` is fixed:: sage: S. = LaurentSeriesRing(ZZ) sage: MS = MatrixSpace(S,1,1) @@ -942,6 +986,8 @@ def _get_matrix_class(self): sage: type(matrix(GF(16007), 2, range(4))) + sage: type(matrix(CBF, 2, range(4))) + """ R = self.base_ring() if self.is_dense(): @@ -970,7 +1016,7 @@ def _get_matrix_class(self): return matrix_modn_dense_double.Matrix_modn_dense_double return matrix_generic_dense.Matrix_generic_dense elif sage.rings.finite_rings.constructor.is_FiniteField(R) and R.characteristic() == 2 and R.order() <= 65536: - return matrix_mod2e_dense.Matrix_mod2e_dense + return matrix_gf2e_dense.Matrix_gf2e_dense elif sage.rings.polynomial.multi_polynomial_ring_generic.is_MPolynomialRing(R) and R.base_ring() in _Fields: return matrix_mpolynomial_dense.Matrix_mpolynomial_dense #elif isinstance(R, sage.rings.padics.padic_ring_capped_relative.pAdicRingCappedRelative): @@ -981,13 +1027,13 @@ def _get_matrix_class(self): if R is SR: import matrix_symbolic_dense return matrix_symbolic_dense.Matrix_symbolic_dense - try: - from sage.rings.complex_ball_acb import ComplexBallField - if isinstance(R, ComplexBallField): - import matrix_complex_ball_dense - return matrix_complex_ball_dense.Matrix_complex_ball_dense - except ImportError: - pass + + # ComplexBallField might become a lazy import, + # thus do not import it here too early. + from sage.rings.complex_arb import ComplexBallField + if isinstance(R, ComplexBallField): + import matrix_complex_ball_dense + return matrix_complex_ball_dense.Matrix_complex_ball_dense return matrix_generic_dense.Matrix_generic_dense else: @@ -1746,3 +1792,5 @@ def test_trivial_matrices_inverse(ring, sparse=True, checkrank=True): 'Matrix_integer_2x2', Matrix_integer_dense) register_unpickle_override('sage.matrix.matrix_integer_2x2', 'MatrixSpace_ZZ_2x2_class', MatrixSpace) +register_unpickle_override('sage.matrix.matrix_mod2e_dense', + 'unpickle_matrix_mod2e_dense_v0', matrix_gf2e_dense.unpickle_matrix_gf2e_dense_v0) diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index 12a1827c431..c8705d5ed1f 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -485,7 +485,7 @@ cdef class Matrix_sparse(matrix.Matrix): raised. This routine is meant to be called from the - meth:`~sage.matrix.matrix2.Matrix.elementwise_product` + :meth:`~sage.matrix.matrix2.Matrix.elementwise_product` method, which will ensure that this routine receives proper input. More thorough documentation is provided there. diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 6035cf0cdea..a9ffef3e9ee 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -152,27 +152,14 @@ from sage.rings.polynomial.all import PolynomialRing from sage.structure.element cimport ModuleElement, RingElement, Element from sage.structure.factorization import Factorization -cimport matrix_generic_dense +from matrix_generic_dense cimport Matrix_generic_dense cimport matrix cdef maxima from sage.calculus.calculus import symbolic_expression_from_maxima_string, maxima -cdef class Matrix_symbolic_dense(matrix_generic_dense.Matrix_generic_dense): - def _new_c(self): - """ - Called when creating a new matrix. - - EXAMPLES:: - - sage: matrix(SR,0) # this implicitly calls _new_c - [] - """ - cdef Matrix_symbolic_dense M = Matrix_symbolic_dense.__new__(Matrix_symbolic_dense, 0, 0, 0) - matrix.Matrix.__init__(M, self._parent) - return M - +cdef class Matrix_symbolic_dense(Matrix_generic_dense): def eigenvalues(self): """ Compute the eigenvalues by solving the characteristic @@ -520,6 +507,114 @@ cdef class Matrix_symbolic_dense(matrix_generic_dense.Matrix_generic_dense): sub_dict = {var: SR.var(var)} return Factorization(self.charpoly(var).subs(**sub_dict).factor_list()) + def jordan_form(self, subdivide=True, transformation=False): + """ + Return a Jordan normal form of ``self``. + + INPUT: + + - ``self`` -- a square matrix + + - ``subdivide`` -- boolean (default: ``True``) + + - ``transformation`` -- boolean (default: ``False``) + + OUTPUT: + + If ``transformation`` is ``False``, only a Jordan normal form + (unique up to the ordering of the Jordan blocks) is returned. + Otherwise, a pair ``(J, P)`` is returned, where ``J`` is a + Jordan normal form and ``P`` is an invertible matrix such that + ``self`` equals ``P * J * P^(-1)``. + + If ``subdivide`` is ``True``, the Jordan blocks in the + returned matrix ``J`` are indicated by a subdivision in + the sense of :meth:`~sage.matrix.matrix2.subdivide`. + + EXAMPLES: + + We start with some examples of diagonalisable matrices:: + + sage: a,b,c,d = var('a,b,c,d') + sage: matrix([a]).jordan_form() + [a] + sage: matrix([[a, 0], [1, d]]).jordan_form(subdivide=True) + [d|0] + [-+-] + [0|a] + sage: matrix([[a, 0], [1, d]]).jordan_form(subdivide=False) + [d 0] + [0 a] + sage: matrix([[a, x, x], [0, b, x], [0, 0, c]]).jordan_form() + [c|0|0] + [-+-+-] + [0|b|0] + [-+-+-] + [0|0|a] + + In the following examples, we compute Jordan forms of some + non-diagonalisable matrices:: + + sage: matrix([[a, a], [0, a]]).jordan_form() + [a 1] + [0 a] + sage: matrix([[a, 0, b], [0, c, 0], [0, 0, a]]).jordan_form() + [c|0 0] + [-+---] + [0|a 1] + [0|0 a] + + The following examples illustrate the ``transformation`` flag. + Note that symbolic expressions may need to be simplified to + make consistency checks succeed:: + + sage: A = matrix([[x - a*c, a^2], [-c^2, x + a*c]]) + sage: J, P = A.jordan_form(transformation=True) + sage: J, P + ( + [x 1] [-a*c 1] + [0 x], [-c^2 0] + ) + sage: A1 = P * J * ~P; A1 + [ -a*c + x (a*c - x)*a/c + a*x/c] + [ -c^2 a*c + x] + sage: A1.simplify_rational() == A + True + + sage: B = matrix([[a, b, c], [0, a, d], [0, 0, a]]) + sage: J, T = B.jordan_form(transformation=True) + sage: J, T + ( + [a 1 0] [b*d c 0] + [0 a 1] [ 0 d 0] + [0 0 a], [ 0 0 1] + ) + sage: (B * T).simplify_rational() == T * J + True + + Finally, some examples involving square roots:: + + sage: matrix([[a, -b], [b, a]]).jordan_form() + [a - I*b| 0] + [-------+-------] + [ 0|a + I*b] + sage: matrix([[a, b], [c, d]]).jordan_form(subdivide=False) + [1/2*a + 1/2*d - 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2) 0] + [ 0 1/2*a + 1/2*d + 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2)] + """ + A = self._maxima_lib_() + jordan_info = A.jordan() + J = jordan_info.dispJordan()._sage_() + if subdivide: + v = [x[1] for x in jordan_info] + w = [sum(v[0:i]) for i in xrange(1, len(v))] + J.subdivide(w, w) + if transformation: + P = A.diag_mode_matrix(jordan_info)._sage_() + return J, P + else: + return J + def simplify(self): """ Simplifies self. @@ -574,6 +669,37 @@ cdef class Matrix_symbolic_dense(matrix_generic_dense.Matrix_generic_dense): """ return self._maxima_(maxima).fullratsimp()._sage_() + def simplify_full(self): + """ + Simplify a symbolic matrix by calling + :meth:`Expression.simplify_full()` componentwise. + + INPUT: + + - ``self`` - The matrix whose entries we should simplify. + + OUTPUT: + + A copy of ``self`` with all of its entries simplified. + + EXAMPLES: + + Symbolic matrices will have their entries simplified:: + + sage: a,n,k = SR.var('a,n,k') + sage: f1 = sin(x)^2 + cos(x)^2 + sage: f2 = sin(x/(x^2 + x)) + sage: f3 = binomial(n,k)*factorial(k)*factorial(n-k) + sage: f4 = x*sin(2)/(x^a) + sage: A = matrix(SR, [[f1,f2],[f3,f4]]) + sage: A.simplify_full() + [ 1 sin(1/(x + 1))] + [ factorial(n) x^(-a + 1)*sin(2)] + + """ + M = self.parent() + return M([expr.simplify_full() for expr in self]) + def factor(self): """ Operates point-wise on each element. diff --git a/src/sage/matrix/operation_table.py b/src/sage/matrix/operation_table.py index eca698fd93d..e76a66c0ab8 100644 --- a/src/sage/matrix/operation_table.py +++ b/src/sage/matrix/operation_table.py @@ -419,16 +419,28 @@ def __init__(self, S, operation, names='letters', elements=None): # If not, we'll discover that next in actual use. self._table = [] + + # the elements might not be hashable. But if they are it is much + # faster to lookup in a hash table rather than in a list! + try: + get_row = {e: i for i,e in enumerate(self._elts)}.__getitem__ + except TypeError: + get_row = self._elts.index + for g in self._elts: row = [] for h in self._elts: try: result = self._operation(g, h) - row.append(self._elts.index(result)) - except ValueError: # list/index condition - raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) except Exception: raise TypeError('elements %s and %s of %s are incompatible with operation: %s' % (g,h,S,self._operation)) + + try: + r = get_row(result) + except (KeyError,ValueError): + raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) + + row.append(r) self._table.append(row) def _name_maker(self, names): @@ -559,8 +571,8 @@ def __getitem__(self, pair): TESTS:: sage: from sage.matrix.operation_table import OperationTable - sage: G=DiCyclicGroup(3) - sage: T=OperationTable(G, operator.mul) + sage: G = DiCyclicGroup(3) + sage: T = OperationTable(G, operator.mul) sage: T[G('(1,2)(3,4)(5,6,7)')] Traceback (most recent call last): ... diff --git a/src/sage/matroids/catalog.py b/src/sage/matroids/catalog.py index 06fed26dd61..87b41e49e1e 100644 --- a/src/sage/matroids/catalog.py +++ b/src/sage/matroids/catalog.py @@ -1365,7 +1365,7 @@ def Block_9_4(): sage: M = matroids.named_matroids.Block_9_4() sage: M.is_valid() # long time True - sage: BD = designs.BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD = BlockDesign(M.groundset(), M.nonspanning_circuits()) sage: BD.is_t_design(return_parameters=True) (True, (2, 9, 4, 3)) """ @@ -1389,7 +1389,7 @@ def Block_10_5(): sage: M = matroids.named_matroids.Block_10_5() sage: M.is_valid() # long time True - sage: BD = designs.BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD = BlockDesign(M.groundset(), M.nonspanning_circuits()) sage: BD.is_t_design(return_parameters=True) (True, (3, 10, 5, 3)) """ diff --git a/src/sage/matroids/lean_matrix.pxd b/src/sage/matroids/lean_matrix.pxd index a112647a0bb..c284f6a0847 100644 --- a/src/sage/matroids/lean_matrix.pxd +++ b/src/sage/matroids/lean_matrix.pxd @@ -32,6 +32,9 @@ cdef class LeanMatrix: cdef LeanMatrix _matrix_times_matrix_(self, LeanMatrix other) cdef LeanMatrix matrix_from_rows_and_columns(self, rows, columns) + cdef shifting_all(self, P_rows, P_cols, Q_rows, Q_cols, int m) + cdef shifting(self, U_1, V_2, U_2, V_1, z2, z1, int m) + cdef class GenericMatrix(LeanMatrix): cdef _base_ring, _characteristic cdef list _entries diff --git a/src/sage/matroids/lean_matrix.pyx b/src/sage/matroids/lean_matrix.pyx index df3b3bc7c30..2c920b21519 100644 --- a/src/sage/matroids/lean_matrix.pyx +++ b/src/sage/matroids/lean_matrix.pyx @@ -1,3 +1,4 @@ +# cython: profile=True """ Lean matrices @@ -509,6 +510,151 @@ cdef class LeanMatrix: """ raise NotImplementedError("subclasses need to implement this.") + cdef shifting_all(self, P_rows, P_cols, Q_rows, Q_cols, int m): + r""" + Given a partial matrix `M`. If the submatrix `M` using rows + `P_rows` columns `P_cols` and submatrix using rows `Q_rows` columns + `Q_cols` can be extended to a ``m``-separator, then it returns + `True, E`, where `E` is a ``m``-separator. Otherwise it returns + `False, None` + + `P_rows` and `Q_rows` must be disjoint subsets of row indices. + `P_cols` and `Q_cols` must be disjoint subsets of column indices. + + Internal version does not verify the above properties hold. + + INPUT: + + - ``P_rows`` -- list of row indices of the first submatrix + - ``P_cols`` -- list of column indices of the first submatrix + - ``Q_rows`` -- list of row indices of the second submatrix + - ``Q_cols`` -- list of column indices of the second submatrix + - ``m`` -- separation size + + OUTPUT: + + - `False, None` -- if the input submatrices does not induce a `m``-separator. + - `True, E` -- if there exist a ``m``-separator ``E``. + + """ + for z in xrange(self.ncols()): + if z in P_cols+Q_cols: + continue + sol,cert = self.shifting(P_rows,P_cols,Q_rows,Q_cols,z,None,m) + if sol: + return True, cert + sol,cert = self.shifting(Q_rows,Q_cols,P_rows,P_cols,None,z,m) + if sol: + return True, cert + sol,cert = self.shifting(P_rows,P_cols,Q_rows,Q_cols,None,z,m) + if sol: + return True, cert + sol,cert = self.shifting(Q_rows,Q_cols,P_rows,P_cols,z,None,m) + if sol: + return True, cert + return False, None + + cdef shifting(self, U_1, V_2, U_2, V_1, z2, z1, int m): + r""" + Let `E_1` be the submatrix using rows `U_1` and columns `V_2` with + optional column `z2` attached. + Let `E_2` be the submatrix using rows `U_2` and columns `V_1` with + optional column `z1` attached. + If `E_1` and `E_2` can be extended to a ``m``-separator, then it + returns `True, E`, where `E` is a ``m``-separator. Otherwise it + returns `False, None` + + `U_1` and `U_2` must be disjoint subsets of row indices. + `V_1` and `V_2` must be disjoint subsets of column indices. + + Internal version does not verify the above properties hold. + + INPUT: + + - ``U_1`` -- list of row indices of the first submatrix + - ``V_2`` -- list of column indices of the first submatrix + - ``U_2`` -- list of row indices of the second submatrix + - ``V_1`` -- list of column indices of the second submatrix + - ``z2`` -- start by add an additional column with index `z2` to `V_2` + - ``z1`` -- start by add an additional column with index `z1` to `V_1` + - ``m`` -- separation size + + OUTPUT: + + - `False, None` -- if the input submatrices does not induce a `m``-separator. + - `True, (X,Y)` -- row indices `X` and column indices `Y` defines a ``m``-separator. + """ + # make copy because of destructive updates + cdef list X_1 = list(U_1) + cdef list X_2 = list(U_2) + cdef list Y_1 = [] + cdef list Y_2 = [] + if z1 != None: + Y_1 = list(V_1) + [z1] + Y_2 = list(V_2) + else: + Y_1 = list(V_1) + Y_2 = list(V_2) + [z2] + + cdef int lX_2 = len(X_2) + cdef int lY_2 = len(Y_2) + + if len(X_1) + len(Y_1) < m: + return False, None + + cdef set X=set(xrange(self.nrows())) + cdef set Y=set(xrange(self.ncols())) + + cdef set X_3 = X-set(X_1+X_2) + cdef set Y_3 = Y-set(Y_1+Y_2) + + cdef list lU_2 = sorted(list(U_2)) + cdef list lV_2 = sorted(list(V_2)) + cdef dict rU = dict(zip(lU_2,xrange(len(U_2)))) + cdef dict rV = dict(zip(lV_2,xrange(len(V_2)))) + + # find a unique representation of every column in U_1xY_3 using columns in U_1xV_2 + B = self.matrix_from_rows_and_columns(list(U_1), xrange(len(Y))) + B.gauss_jordan_reduce(lV_2) + # find a unique representation of every rows in X_3xV_1 using rows in U_2xV_1 + BT = self.matrix_from_rows_and_columns(xrange(len(X)),list(V_1)).transpose() + BT.gauss_jordan_reduce(lU_2) + + cdef set X_p = set(X_1) + cdef set Y_p = set(Y_1) + while True: + #rowshifts + X_p_new = set([]) + for x in set(X_3): + for y in Y_p: + if sum([BT.get_unsafe(rU[u],x)*self.get_unsafe(u,y) for u in U_2]) != self.get_unsafe(x,y): + X_1.append(x) + X_3.remove(x) + X_p_new.add(x) + break + #colshifts + Y_p_new = set([]) + for y in set(Y_3): + for x in X_p: + if sum([B.get_unsafe(rV[v],y)*self.get_unsafe(x,v) for v in V_2]) != self.get_unsafe(x,y): + Y_1.append(y) + Y_3.remove(y) + Y_p_new.add(y) + break + X_p = X_p_new + Y_p = Y_p_new + if (not X_p_new and not Y_p_new): + break + + # size of S_2 + X_2 = list(X-set(X_1)) + Y_2 = list(Y-set(Y_1)) + if len(X_2)+len(Y_2) < m: + return False, None + if (lX_2==len(X_2) and lY_2==len(Y_2)): + return False, None + return True, (X_1, Y_1) + cdef class GenericMatrix(LeanMatrix): """ Matrix over arbitrary Sage ring. @@ -595,7 +741,7 @@ cdef class GenericMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: @@ -893,8 +1039,8 @@ cdef class BinaryMatrix(LeanMatrix): bitset_free(self._temp) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -1520,8 +1666,8 @@ cdef class TernaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2095,8 +2241,8 @@ cdef class QuaternaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2683,7 +2829,7 @@ cdef class IntegerMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: diff --git a/src/sage/matroids/linear_matroid.pxd b/src/sage/matroids/linear_matroid.pxd index 3bb43367090..357767c0acc 100644 --- a/src/sage/matroids/linear_matroid.pxd +++ b/src/sage/matroids/linear_matroid.pxd @@ -60,7 +60,10 @@ cdef class LinearMatroid(BasisExchangeMatroid): cpdef linear_coextension_cochains(self, F=*, cosimple=*, fundamentals=*) cpdef linear_extensions(self, element=*, F=*, simple=*, fundamentals=*) cpdef linear_coextensions(self, element=*, F=*, cosimple=*, fundamentals=*) - + + cpdef _is_3connected_shifting(self, certificate=*) + cpdef _is_4connected_shifting(self, certificate=*) + cpdef is_valid(self) cdef class BinaryMatroid(LinearMatroid): diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index 1e7dc4dcccd..70be4f2c5b2 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -114,14 +114,17 @@ from sage.matroids.matroid cimport Matroid from sage.matroids.basis_exchange_matroid cimport BasisExchangeMatroid from lean_matrix cimport LeanMatrix, GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, IntegerMatrix, generic_identity from set_system cimport SetSystem -from utilities import newlabel, lift_cross_ratios +from utilities import newlabel, spanning_stars, spanning_forest, lift_cross_ratios +from sage.graphs.spanning_tree import kruskal +from sage.graphs.graph import Graph from sage.matrix.matrix2 cimport Matrix import sage.matrix.constructor +from sage.matrix.constructor import matrix from copy import copy, deepcopy from sage.rings.all import ZZ, QQ, FiniteField, GF import itertools -from itertools import combinations +from itertools import combinations, product cdef bint GF2_not_defined = True cdef GF2, GF2_one, GF2_zero @@ -2612,6 +2615,196 @@ cdef class LinearMatroid(BasisExchangeMatroid): return False return True + # connectivity + + cpdef _is_3connected_shifting(self, certificate=False): + r""" + Return ``True`` if the matroid is 4-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is 3-connected, + and ``False,`` `X` otherwise, where `X` is a `<3`-separation + + OUTPUT: + + boolean, or a tuple ``(boolean, frozenset)`` + + ALGORITHM: + + The shifting algorithm + + EXAMPLES:: + + sage: matroids.Uniform(2, 3)._is_3connected_shifting() + True + sage: M = Matroid(ring=QQ, matrix=[[1, 0, 0, 1, 1, 0], + ....: [0, 1, 0, 1, 2, 0], + ....: [0, 0, 1, 0, 0, 1]]) + sage: M._is_3connected_shifting() + False + sage: N = Matroid(circuit_closures={2: ['abc', 'cdef'], + ....: 3: ['abcdef']}, + ....: groundset='abcdef') + sage: N._is_3connected_shifting() + False + sage: matroids.named_matroids.BetsyRoss()._is_3connected_shifting() + True + sage: M = matroids.named_matroids.R6() + sage: M._is_3connected_shifting() + False + sage: B, X = M._is_3connected_shifting(True) + sage: M.connectivity(X)<3 + True + """ + if not self.is_connected(): + if certificate: + return False, self.components()[0] + else: + return False + if self.rank()>self.size()-self.rank(): + return self.dual()._is_3connected_shifting(certificate) + + # the partial matrix + M2 = self._reduced_representation() + M = M2._matrix_() + X, Y = self._current_rows_cols() + + # create mapping between elements and columns + dX = dict(zip(range(len(X)),X)) + dY = dict(zip(range(len(Y)),Y)) + + for (x,y) in spanning_forest(M): + P_rows=[x] + P_cols=[y] + Q_rows=[] + Q_cols=[] + sol,cert_pair = M2.shifting_all(P_rows, P_cols, Q_rows, Q_cols, 2) + if sol: + if certificate: + cert = set([]) + for x in cert_pair[0]: + cert.add(dX[x]) + for y in cert_pair[1]: + cert.add(dY[y]) + return False, cert + return False + if certificate: + return True, None + return True + + cpdef _is_4connected_shifting(self, certificate=False): + r""" + Return ``True`` if the matroid is 4-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is 4-connected, + and ``False,`` `X` otherwise, where `X` is a `<4`-separation + + OUTPUT: + + boolean, or a tuple ``(boolean, frozenset)`` + + ALGORITHM: + + The shifting algorithm + + EXAMPLES:: + + sage: M = matroids.Uniform(2, 6) + sage: B, X = M._is_4connected_shifting(True) + sage: (B, M.connectivity(X)<=3) + (False, True) + sage: matroids.Uniform(4, 8)._is_4connected_shifting() + True + sage: M = Matroid(field=GF(2), matrix=[[1,0,0,1,0,1,1,0,0,1,1,1], + ....: [0,1,0,1,0,1,0,1,0,0,0,1], + ....: [0,0,1,1,0,0,1,1,0,1,0,1], + ....: [0,0,0,0,1,1,1,1,0,0,1,1], + ....: [0,0,0,0,0,0,0,0,1,1,1,1]]) + sage: M._is_4connected_shifting() + True + """ + if self.rank()>self.size()-self.rank(): + return self.dual()._is_4connected_shifting(certificate) + if not self._is_3connected_shifting(): + return self._is_3connected_shifting(certificate) + + # the partial matrix + M2 = self._reduced_representation() + M = M2._matrix_() + X, Y = self._current_rows_cols() + + dX = dict(zip(range(len(X)),X)) + dY = dict(zip(range(len(Y)),Y)) + n = len(X) + m = len(Y) + + sol=False + T = spanning_stars(M) + + for (x1,y1) in T: + # The whiting out + B = copy(M) + for (x,y) in product(range(n),range(m)): + if (x1!=x and y1!=y): + if(M[x1,y]!=0 and + M[x,y1]!=0 and + M[x,y]!=0): + B[x,y]=0 + + # remove row x1 and y1 + Xp = range(n) + Xp.remove(x1) + Yp = range(m) + Yp.remove(y1) + + B = B.matrix_from_rows_and_columns(Xp,Yp) + + # produce a spanning forest of B + for (x,y) in spanning_forest(B): + if x >= x1: + x = x + 1 + if y >= y1: + y = y + 1 + # rank 2 matrix and rank 0 matrix + P_rows = [x,x1] + P_cols = [y,y1] + Q_rows = [] + Q_cols = [] + # make sure the matrix has rank 2 + if(M.matrix_from_rows_and_columns(P_rows,P_cols).rank()==2): + sol,cert_pair = M2.shifting_all(P_rows, P_cols, Q_rows, Q_cols, 3) + if sol: + break + # rank 1 matrix and rank 1 matrix + P_rows = [x1] + P_cols = [y1] + Q_rows = [x] + Q_cols = [y] + # both matrix have rank 1 + sol,cert_pair = M2.shifting_all(P_rows, P_cols, Q_rows, Q_cols, 3) + if sol: + break + if sol: + if certificate: + (certX, certY) = cert_pair + cert = set([]) + for x in certX: + cert.add(dX[x]) + for y in certY: + cert.add(dY[y]) + return False, cert + return False + if certificate: + return True, None + return True + # Copying, loading, saving def __copy__(self): diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 415c02f26c9..8bf731011fb 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -140,7 +140,12 @@ cdef class Matroid(SageObject): cpdef is_kconnected(self, k, certificate=*) cpdef link(self, S, T) cpdef _link(self, S, T) + cpdef _is_3connected_shifting(self, certificate=*) + cpdef _is_4connected_shifting(self, certificate=*) + cpdef _shifting_all(self, X, P_rows, P_cols, Q_rows, Q_cols, m) + cpdef _shifting(self, X, X_1, Y_2, X_2, Y_1, m) cpdef is_3connected(self, certificate=*, algorithm=*, separation=*) + cpdef is_4connected(self, certificate=*, algorithm=*) cpdef _is_3connected_CE(self, certificate=*) cpdef _is_3connected_BC(self, certificate=*) cpdef _is_3connected_BC_recursion(self, basis, fund_cocircuits) @@ -168,6 +173,10 @@ cdef class Matroid(SageObject): cpdef intersection(self, other, weights=*) cpdef _intersection(self, other, weights) cpdef _intersection_augmentation(self, other, weights, Y) + cpdef intersection_unweighted(self, other) + cpdef _intersection_unweighted(self, other) + cpdef _intersection_augmentation_unweighted(self, other, Y) + cpdef partition(self) # invariants cpdef _internal(self, B) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 0f9a3dfde30..5309ccee5d6 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -107,6 +107,8 @@ additional functionality (e.g. linear extensions). - :meth:`components() ` - :meth:`is_connected() ` - :meth:`is_3connected() ` + - :meth:`is_4connected() ` + - :meth:`is_kconnected() ` - :meth:`connectivity() ` - Representation @@ -119,6 +121,7 @@ additional functionality (e.g. linear extensions). - :meth:`max_weight_independent() ` - :meth:`max_weight_coindependent() ` - :meth:`intersection() ` + - :meth:`intersection_unweighted() ` - Invariants - :meth:`tutte_polynomial() ` @@ -283,8 +286,10 @@ REFERENCES ========== .. [BC79] R. E. Bixby, W. H. Cunningham, Matroids, Graphs, and 3-Connectivity. In Graph theory and related topics (Proc. Conf., Univ. Waterloo, Waterloo, ON, 1977), 91-103 +.. [Cunningham86] W. H. Cunningham, Improved Bounds for Matroid Partition and Intersection Algorithms. SIAM Journal on Computing 1986 15:4, 948-957 .. [CMO11] C. Chun, D. Mayhew, J. Oxley, A chain theorem for internally 4-connected binary matroids. J. Combin. Theory Ser. B 101 (2011), 141-189. .. [CMO12] C. Chun, D. Mayhew, J. Oxley, Towards a splitter theorem for internally 4-connected binary matroids. J. Combin. Theory Ser. B 102 (2012), 688-700. +.. [Cunningham] W. H. Cunningham. Improved bounds for matroid partition and intersection algorithms. SIAM J. Comput. 15, 4 (November 1986), 948-957. .. [GG12] Jim Geelen and Bert Gerards, Characterizing graphic matroids by a system of linear equations, submitted, 2012. Preprint: http://www.gerardsbase.nl/papers/geelen_gerards=testing-graphicness%5B2013%5D.pdf .. [GR01] C.Godsil and G.Royle, Algebraic Graph Theory. Graduate Texts in Mathematics, Springer, 2001. .. [Hlineny] Petr Hlineny, "Equivalence-free exhaustive generation of matroid representations", Discrete Applied Mathematics 154 (2006), pp. 1210-1222. @@ -296,6 +301,7 @@ REFERENCES .. [PvZ] R. A. Pendavingh, S. H. M. van Zwam, Lifts of matroid representations over partial fields, Journal of Combinatorial Theory, Series B, Volume 100, Issue 1, January 2010, Pages 36-67 +.. [Rajan] A. Rajan, Algorithmic applications of connectivity and related topics in matroid theory. Ph.D. Thesis, Northwestern university, 1987. AUTHORS: @@ -318,12 +324,14 @@ Methods # http://www.gnu.org/licenses/ #***************************************************************************** from sage.structure.sage_object cimport SageObject -from itertools import combinations, permutations +from itertools import combinations, permutations, product from set_system cimport SetSystem +from sage.graphs.spanning_tree import kruskal from sage.graphs.graph import Graph +from sage.matrix.constructor import matrix from sage.misc.superseded import deprecation -from utilities import newlabel, sanitize_contractions_deletions +from utilities import newlabel, sanitize_contractions_deletions, spanning_forest, spanning_stars from sage.rings.all import ZZ from sage.numerical.mip import MixedIntegerLinearProgram @@ -4797,7 +4805,9 @@ cdef class Matroid(SageObject): .. SEEALSO:: - :meth:`is_connected`, `is_3connected` + :meth:`M.is_connected() ` + :meth:`M.is_3connected() ` + :meth:`M.is_4connected() ` ALGORITHM: @@ -4923,7 +4933,8 @@ cdef class Matroid(SageObject): - ``None`` -- The most appropriate algorithm is chosen automatically. - ``"bridges"`` -- Bixby and Cunningham's algorithm, based on bridges [BC79]_. Note that this cannot return a separator. - - ``"intersection"`` An algorithm based on matroid intersection. + - ``"intersection"`` -- An algorithm based on matroid intersection. + - ``"shifting"`` -- An algorithm based on the shifting algorithm [Rajan]_. OUTPUT: @@ -4931,13 +4942,16 @@ cdef class Matroid(SageObject): .. SEEALSO:: - :meth:`is_connected` + :meth:`M.is_connected() ` + :meth:`M.is_4connected() ` + :meth:`M.is_kconnected() ` ALGORITHM: - Bridges based: The 3-connectivity algorithm from [BC79]_ which runs in `O((r(E))^2|E|)` time. - Matroid intersection based: Evaluates the connectivity between `O(|E|^2)` pairs of disjoint sets `S`, `T` with `|S| = |T| = 2`. + - Shifting algorithm: The shifting algorithm from [Rajan]_ which runs in `O((r(E))^2|E|)` time. EXAMPLES:: @@ -4978,6 +4992,60 @@ cdef class Matroid(SageObject): return self._is_3connected_BC(separation) if algorithm == "intersection": return self._is_3connected_CE(separation) + if algorithm == "shifting": + return self._is_3connected_shifting(certificate) + raise ValueError("Not a valid algorithm.") + + cpdef is_4connected(self, certificate=False, algorithm=None): + r""" + Return ``True`` if the matroid is 4-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is 4-connected, + and ``False,`` `X` otherwise, where `X` is a `<4`-separation + - ``algorithm`` -- (default: ``None``); specify which algorithm + to compute 4-connectivity: + + - ``None`` -- The most appropriate algorithm is chosen automatically. + - ``"intersection"`` -- an algorithm based on matroid intersection, equivalent + to calling ``is_kconnected(4,certificate)``. + - ``"shifting"`` -- an algorithm based on the shifting algorithm [Rajan]_. + + OUTPUT: + + boolean, or a tuple ``(boolean, frozenset)`` + + .. SEEALSO:: + + :meth:`M.is_connected() ` + :meth:`M.is_3connected() ` + :meth:`M.is_kconnected() ` + + EXAMPLES:: + + sage: M = matroids.Uniform(2, 6) + sage: B, X = M.is_4connected(True) + sage: (B, M.connectivity(X)<=3) + (False, True) + sage: matroids.Uniform(4, 8).is_4connected() + True + sage: M = Matroid(field=GF(2), matrix=[[1,0,0,1,0,1,1,0,0,1,1,1], + ....: [0,1,0,1,0,1,0,1,0,0,0,1], + ....: [0,0,1,1,0,0,1,1,0,1,0,1], + ....: [0,0,0,0,1,1,1,1,0,0,1,1], + ....: [0,0,0,0,0,0,0,0,1,1,1,1]]) + sage: M.is_4connected() == M.is_4connected(algorithm="shifting") + True + sage: M.is_4connected() == M.is_4connected(algorithm="intersection") + True + """ + if algorithm == None or algorithm == "intersection": + return self.is_kconnected(4, certificate) + if algorithm == "shifting": + return self._is_4connected_shifting(certificate) raise ValueError("Not a valid algorithm.") cpdef _is_3connected_CE(self, certificate=False): @@ -5108,6 +5176,341 @@ cdef class Matroid(SageObject): else: return True + cpdef _is_3connected_shifting(self, certificate=False): + r""" + Return ``True`` if the matroid is 3-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is 3-connected, + and ``False,`` `X` otherwise, where `X` is a `<3`-separation + + OUTPUT: + + boolean, or a tuple ``(boolean, frozenset)`` + + ALGORITHM: + + The shifting algorithm + + EXAMPLES:: + + sage: matroids.Uniform(2, 3)._is_3connected_shifting() + True + sage: M = Matroid(ring=QQ, matrix=[[1, 0, 0, 1, 1, 0], + ....: [0, 1, 0, 1, 2, 0], + ....: [0, 0, 1, 0, 0, 1]]) + sage: M._is_3connected_shifting() + False + sage: N = Matroid(circuit_closures={2: ['abc', 'cdef'], + ....: 3: ['abcdef']}, + ....: groundset='abcdef') + sage: N._is_3connected_shifting() + False + sage: matroids.named_matroids.BetsyRoss()._is_3connected_shifting() + True + sage: M = matroids.named_matroids.R6() + sage: M._is_3connected_shifting() + False + sage: B, X = M._is_3connected_shifting(True) + sage: M.connectivity(X) + 1 + """ + if not self.is_connected(): + if certificate: + return False, self.components()[0] + else: + return False + if self.rank()>self.size()-self.rank(): + return self.dual()._is_3connected_shifting(certificate) + X = set(self.basis()) + Y = set(self.groundset()-X) + + # Dictionary allow conversion between two representations + dX = dict(zip(range(len(X)),X)) + dY = dict(zip(range(len(Y)),Y)) + rdX = dict(zip(X,range(len(X)))) + rdY = dict(zip(Y,range(len(Y)))) + + # the partial matrix + M = matrix(len(X),len(Y)) + for y in Y: + for x in (X & self.fundamental_circuit(X,y)): + M[rdX[x],rdY[y]]=1 + + for (x,y) in spanning_forest(M): + P_rows=set([dX[x]]) + P_cols=set([dY[y]]) + Q_rows=set([]) + Q_cols=set([]) + sol,cert = self._shifting_all(X, P_rows, P_cols, Q_rows, Q_cols, 2) + if sol: + if certificate: + return False, cert + return False + if certificate: + return True, None + return True + + cpdef _is_4connected_shifting(self, certificate=False): + r""" + Return ``True`` if the matroid is 4-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is 4-connected, + and ``False,`` `X` otherwise, where `X` is a `<4`-separation + + OUTPUT: + + boolean, or a tuple ``(boolean, frozenset)`` + + ALGORITHM: + + The shifting algorithm + + EXAMPLES:: + + sage: M = matroids.Uniform(2, 6) + sage: B, X = M._is_4connected_shifting(True) + sage: (B, M.connectivity(X)<=3) + (False, True) + sage: matroids.Uniform(4, 8)._is_4connected_shifting() + True + sage: M = Matroid(field=GF(2), matrix=[[1,0,0,1,0,1,1,0,0,1,1,1], + ....: [0,1,0,1,0,1,0,1,0,0,0,1], + ....: [0,0,1,1,0,0,1,1,0,1,0,1], + ....: [0,0,0,0,1,1,1,1,0,0,1,1], + ....: [0,0,0,0,0,0,0,0,1,1,1,1]]) + sage: M._is_4connected_shifting() + True + """ + if self.rank()>self.size()-self.rank(): + return self.dual()._is_4connected_shifting(certificate) + if not self._is_3connected_shifting(): + return self._is_3connected_shifting(certificate) + + X = set(self.basis()) + Y = set(self.groundset()-X) + + dX = dict(zip(range(len(X)),X)) + dY = dict(zip(range(len(Y)),Y)) + rdX = dict(zip(X,range(len(X)))) + rdY = dict(zip(Y,range(len(Y)))) + + # the partial matrix + M = matrix(len(X),len(Y)) + for y in Y: + for x in (X & self.fundamental_circuit(X,y)): + M[rdX[x],rdY[y]]=1 + n = len(X) + m = len(Y) + + # compute a connected set of stars + + T = spanning_stars(M) + for (x1,y1) in T: + # The whiting out + B = matrix(M) + for (x,y) in product(range(n),range(m)): + if (x1!=x and y1!=y): + if(M[x1,y]==1 and + M[x,y1]==1 and + M[x,y]==1): + B[x,y]=0 + + # remove row x1 and y1 + Xp = range(n) + Xp.remove(x1) + Yp = range(m) + Yp.remove(y1) + B = B.matrix_from_rows_and_columns(Xp,Yp) + + # produce a spanning forest of B + for (x,y) in spanning_forest(B): + if x >= x1: + x = x+1 + if y >= y1: + y = y+1 + # rank 2 matrix and rank 0 matrix + P_rows = set([dX[x],dX[x1]]) + P_cols = set([dY[y],dY[y1]]) + Q_rows = set([]) + Q_cols = set([]) + sol,cert = self._shifting_all(X, P_rows, P_cols, Q_rows, Q_cols, 3) + if sol: + if certificate: + return False, cert + return False + # rank 1 matrix and rank 1 matrix + P_rows = set([dX[x1]]) + P_cols = set([dY[y1]]) + Q_rows = set([dX[x]]) + Q_cols = set([dY[y]]) + sol,cert = self._shifting_all(X, P_rows, P_cols, Q_rows, Q_cols, 3) + if sol: + if certificate: + return False, cert + return False + if certificate: + return True, None + return True + + cpdef _shifting_all(self, X, P_rows, P_cols, Q_rows, Q_cols, m): + r""" + Given a basis ``X``. If the submatrix of the partial matrix using rows + `P_rows` columns `P_cols` and submatrix using rows `Q_rows` columns + `Q_cols` can be extended to a ``m``-separator, then it returns + `True, E`, where `E` is a ``m``-separator. Otherwise it returns + `False, None` + + `P_rows` and `Q_rows` must be disjoint subsets of `X`. + `P_cols` and `Q_cols` must be disjoint subsets of `Y`. + + Internal version does not verify the above properties hold. + + INPUT: + + - ``X`` -- A basis + - ``P_rows`` -- a set of row indices of the first submatrix + - ``P_cols`` -- a set of column indices of the first submatrix + - ``Q_rows`` -- a set of row indices of the second submatrix + - ``Q_cols`` -- a set of column indices of the second submatrix + - ``m`` -- separation size + + OUTPUT: + + - `False, None` -- if there is no ``m``-separator. + - `True, E` -- if there exist a ``m``-separator ``E``. + + EXAMPLES:: + + sage: M = Matroid(field=GF(2), matrix=[[1,0,0,1,0,1,1,0,0,1,1,1], + ....: [0,1,0,1,0,1,0,1,0,0,0,1], + ....: [0,0,1,1,0,0,1,1,0,1,0,1], + ....: [0,0,0,0,1,1,1,1,0,0,1,1], + ....: [0,0,0,0,0,0,0,0,1,1,1,1]]) + sage: M._shifting_all(M.basis(),set([0,1]),set([0,1]),set([]),set([]),3) + (False, None) + sage: M = Matroid(field=GF(2), reduced_matrix=[[1,0,1,1,1], + ....: [1,1,1,1,0], + ....: [0,1,1,1,0], + ....: [0,0,0,1,1]]) + sage: M._shifting_all(M.basis(), set([0,1]), set([5,8]), set([]), set([]), 3)[0] + True + + """ + Y = self.groundset()-X + for z in (Y - P_cols) - Q_cols: + sol,cert = self._shifting(X,P_rows,P_cols|set([z]),Q_rows,Q_cols,m) + if sol: + return True, cert + sol,cert = self._shifting(X,Q_rows,Q_cols,P_rows,P_cols|set([z]),m) + if sol: + return True, cert + sol,cert = self._shifting(X,P_rows,P_cols,Q_rows,Q_cols|set([z]),m) + if sol: + return True, cert + sol,cert = self._shifting(X,Q_rows,Q_cols|set([z]),P_rows,P_cols,m) + if sol: + return True, cert + return False, None + + cpdef _shifting(self, X, X_1, Y_2, X_2, Y_1, m): + r""" + Given a basis ``X``. If the submatrix of the partial matrix using rows + `X_1` columns `Y_2` and submatrix using rows `X_2` columns + `Y_1` can be extended to a ``m``-separator, then it returns + `True, E`, where `E` is a ``m``-separator. Otherwise it returns + `False, None` + + `X_1` and `X_2` must be disjoint subsets of `X`. + `Y_1` and `Y_2` must be disjoint subsets of `Y`. + + Internal version does not verify the above properties hold. + + INPUT: + + - ``X`` -- A basis + - ``X_1`` -- set of row indices of the first submatrix + - ``Y_2`` -- set of column indices of the first submatrix + - ``X_2`` -- set of row indices of the second submatrix + - ``Y_1`` -- set of column indices of the second submatrix + - ``m`` -- separation size + + OUTPUT: + + - `False, None` -- if there is no ``m``-separator. + - `True, E` -- if there exist a ``m``-separator ``E``. + + EXAMPLES:: + + sage: M = Matroid(field=GF(2), matrix=[[1,0,0,1,0,1,1,0,0,1,1,1], + ....: [0,1,0,1,0,1,0,1,0,0,0,1], + ....: [0,0,1,1,0,0,1,1,0,1,0,1], + ....: [0,0,0,0,1,1,1,1,0,0,1,1], + ....: [0,0,0,0,0,0,0,0,1,1,1,1]]) + sage: M._shifting(M.basis(),set([0,1]),set([0,1]),set([]),set([]),3) + (False, None) + sage: M = Matroid(field=GF(2), reduced_matrix=[[1,0,1,1,1], + ....: [1,1,1,1,0], + ....: [0,1,1,1,0], + ....: [0,0,0,1,1]]) + sage: M._shifting(M.basis(), set([0,1]), set([5,8]), set([]), set([4]), 3)[0] + True + """ + + X_1 = set(X_1) + X_2 = set(X_2) + Y_1 = set(Y_1) + Y_2 = set(Y_2) + + lX_2 = len(X_2) + lY_2 = len(Y_2) + + Y = self.groundset()-X + # Returns true if there is a m-separator + if (self.rank(Y_2|(X-X_1)) - len(X-X_1) + + self.rank(Y_1|(X-X_2)) - len(X-X_2) != m-1): + return False, None + if len(X_1|Y_1) < m: + return False, None + remainX = set(X-(X_1|X_2)) + remainY = set(Y-(Y_1|Y_2)) + while True: + #rowshifts + rowshift = False + for x in set(remainX): + if(self.rank(Y_1|(X-(X_2|set([x])))) - len(X-(X_2|set([x]))) + > self.rank(Y_1|(X-X_2)) - len(X-X_2)): + X_1.add(x) + remainX.remove(x) + rowshift = True + #colshifts + colshift = False + for y in set(remainY): + if(self.rank(Y_2|set([y])|(X-X_1)) - len(X-X_1) + > self.rank(Y_2|(X-X_1)) - len(X-X_1)): + Y_1.add(y) + remainY.remove(y) + colshift = True + if (colshift==False and rowshift==False): + break + X_2 = X-X_1 + Y_2 = Y-Y_1 + S_2 = X_2|Y_2 + + + if len(S_2) < m: + return False, None + if (lX_2==len(X_2) and lY_2==len(Y_2)): + return False, None + return True, S_2 + cpdef _is_3connected_BC(self, certificate=False): r""" Return ``True`` if the matroid is 3-connected, ``False`` otherwise. @@ -5172,9 +5575,10 @@ cdef class Matroid(SageObject): ``True`` if the matroid is 3-connected, ``False`` otherwise. INPUT: + - ``basis`` -- a basis of the matroid. - ``fund_cocircuits`` -- a iterable of some fundamental cocircuits with - respect to ``basis``. It must contain all separating fundamental cocircuits. + respect to ``basis``. It must contain all separating fundamental cocircuits. OUTPUT: @@ -5195,8 +5599,8 @@ cdef class Matroid(SageObject): .. NOTE:: - The function does not check its input at all. You may want to make - sure the matroid is both simple and cosimple. + The function does not check its input at all. You may want to make + sure the matroid is both simple and cosimple. """ # Step 1: base case if self.rank() <= 2: @@ -6101,6 +6505,276 @@ cdef class Matroid(SageObject): path.add(u) return True, frozenset(path) + cpdef intersection_unweighted(self, other): + r""" + Return a maximum-cardinality common independent set. + + A *common independent set* of matroids `M` and `N` with the same + groundset `E` is a subset of `E` that is independent both in `M` and + `N`. + + INPUT: + + - ``other`` -- a second matroid with the same groundset as this + matroid. + + OUTPUT: + + A subset of the groundset. + + EXAMPLES:: + + sage: M = matroids.named_matroids.T12() + sage: N = matroids.named_matroids.ExtendedTernaryGolayCode() + sage: len(M.intersection_unweighted(N)) + 6 + sage: M = matroids.named_matroids.Fano() + sage: N = matroids.Uniform(4, 7) + sage: M.intersection_unweighted(N) + Traceback (most recent call last): + ... + ValueError: matroid intersection requires equal groundsets. + """ + if not isinstance(other, Matroid): + raise TypeError("can only intersect two matroids.") + if not self.groundset() == other.groundset(): + raise ValueError("matroid intersection requires equal groundsets.") + return self._intersection_unweighted(other) + + cpdef _intersection_unweighted(self, other): + r""" + Return a maximum common independent. + + INPUT: + + - ``other`` -- a second matroid with the same groundset as this + matroid. + + OUTPUT: + + A subset of the groundset. + + .. NOTE:: + + This does not test if the input is well-formed. + + ALGORITHM: + + A blocking flow based algorithm which performs well if the size of + the intersection is large [Cunningham]_. + + EXAMPLES:: + + sage: M = matroids.named_matroids.T12() + sage: N = matroids.named_matroids.ExtendedTernaryGolayCode() + sage: len(M._intersection_unweighted(N)) + 6 + """ + Y = set() + U = self._intersection_augmentation_unweighted(other, Y) + while U[0]: + Y = U[1] + U = self._intersection_augmentation_unweighted(other, Y) + return Y + + cpdef _intersection_augmentation_unweighted(self, other, Y): + r""" + Return a common independent set larger than `Y` or report failure. + + INPUT: + + - ``other`` -- a matroid with the same ground set as ``self``. + - ``Y`` -- an common independent set of ``self`` and ``other`` of size `k`. + + OUTPUT: + + A pair ``True, U`` such that the ``U`` is a common independent set with + at least `k + 1` elements; or a pair ``False, X``, if there is no common + independent set of size `k + 1`. + + .. NOTE:: + + This is an unchecked method. In particular, if the given ``Y`` is + not a common independent set, the behavior is unpredictable. + + EXAMPLES:: + + sage: M = matroids.named_matroids.T12() + sage: N = matroids.named_matroids.ExtendedTernaryGolayCode() + sage: Y = M.intersection(N) + sage: M._intersection_augmentation_unweighted(N, Y)[0] + False + sage: Y = M._intersection_augmentation_unweighted(N,set()) + sage: Y[0] + True + sage: len(Y[1])>0 + True + """ + E = self.groundset() + X = E - Y + X1 = E - self._closure(Y) + X2 = E - other._closure(Y) + # partition the vertices into layers according to distance + # the modification of the code in _intersection_augmentation + w = {x: -1 for x in X1} + out_neighbors = {x: set() for x in X2} + d = {} + dist=0 + todo = set(X1) + next_layer = set() + layers = {} + + X3 = X2.intersection(w) + while todo: + layers[dist] = set(todo) + if X3: + break + while todo: # todo is subset of X + u = todo.pop() + m = w[u] + if u not in out_neighbors: + out_neighbors[u] = other._circuit(Y.union([u])) - set([u]) # if u in X2 then out_neighbors[u] was set to empty + for y in out_neighbors[u]: + m2 = m + 1 + if not y in w or w[y] > m2: + w[y] = m2 + next_layer.add(y) + todo = next_layer + next_layer = set() + dist += 1 + X3 = X2.intersection(w) + layers[dist] = set(todo) + if X3: + break + if not todo: + break + while todo: # todo is subset of Y + u = todo.pop() + m = w[u] + if u not in out_neighbors: + out_neighbors[u] = X - self._closure(Y - set([u])) + for x in out_neighbors[u]: + m2 = m - 1 + if not x in w or w[x] > m2: + w[x] = m2 + next_layer.add(x) + todo = next_layer + next_layer = set() + dist += 1 + X3 = X2.intersection(w) + + for x, y in layers.iteritems(): + for z in y: + d[z] = x + if not X3: # if no path from X1 to X2, then no augmenting set exists + return False, frozenset(w) + else: + visited = set() + # find augmenting paths successively without explicitly construct the graph + while (layers[0] & X1)-visited: + stack = [set(((layers[0]&X1)-visited)).pop()] + predecessor = {} + # use DFS + while stack: + u = stack.pop() + visited.add(u) + # reached the final layer + if d[u]==len(layers)-1: + # check if this is in X2, if so augment the path + if (u in X2): + path = set([u]) # reconstruct path + while u in predecessor: + u = predecessor[u] + path.add(u) + # augment the path and update all sets + Y = Y.symmetric_difference(path) + X = E - Y + X1 = E - self._closure(Y) + X2 = E - other._closure(Y) + break + else: + continue + # there are more layers + for v in layers[d[u]+1] - visited: + # check if edge (u,v) exists in the auxiliary digraph + exist = False + if ((u in Y) and + (v in E-Y) and + (not self.is_independent(Y|set([v]))) and + (self.is_independent((Y|set([v])) - set([u])))): + exist = True + if ((u in E-Y) and + (v in Y) and + (not other.is_independent(Y|set([u]))) and + (other.is_independent((Y|set([u])) - set([v])))): + exist = True + if exist: + stack.append(v) + predecessor[v] = u + return True, Y + + cpdef partition(self): + r""" + Returns a minimum number of disjoint independent sets that covers the + groundset. + + OUTPUT: + + A list of disjoint independent sets that covers the goundset. + + EXAMPLES:: + + sage: M = matroids.named_matroids.Block_9_4() + sage: P = M.partition() + sage: all(map(M.is_independent,P)) + True + sage: set.union(*P)==M.groundset() + True + sage: sum(map(len,P))==len(M.groundset()) + True + sage: Matroid(matrix([])).partition() + [] + + ALGORITHM: + + Reduce partition to a matroid intersection between a matroid sum + and a partition matroid. It's known the direct method doesn't gain + much advantage over matroid intersection. [Cunningham86] + """ + from sage.matroids.union_matroid import MatroidSum, PartitionMatroid + if self.loops(): + raise ValueError("Cannot partition matroids with loops.") + if self.size()==0: + return [] + # doubling search for minimum independent sets that partitions the groundset + n = self.size() + r = self.rank() + hi = -(-n//r) + lo = hi + X = set() + # doubling step + while True: + p = PartitionMatroid([[(i,x) for i in range(hi)] for x in self.groundset()]) + X = MatroidSum([self]*hi).intersection(p) + if len(X)==self.size(): + break + lo = hi + hi = min(hi*2,n) + # binary search step + while lo < hi: + mid = (lo+hi)//2 + p = PartitionMatroid([[(i,x) for i in range(mid)] for x in self.groundset()]) + X = MatroidSum([self]*mid).intersection(p) + if len(X)!=self.size() : lo = mid+1 + else: hi = mid + + partition = {} + for (i,x) in X: + if not i in partition: + partition[i] = set() + partition[i].add(x) + return partition.values() + # invariants cpdef _internal(self, B): diff --git a/src/sage/matroids/union_matroid.pxd b/src/sage/matroids/union_matroid.pxd new file mode 100644 index 00000000000..1dcf1a6d5ec --- /dev/null +++ b/src/sage/matroids/union_matroid.pxd @@ -0,0 +1,19 @@ +from matroid cimport Matroid + +cdef class MatroidUnion(Matroid): + cdef list matroids + cdef frozenset _groundset + cpdef groundset(self) + cpdef _rank(self, X) + +cdef class MatroidSum(Matroid): + cdef list summands + cdef frozenset _groundset + cpdef groundset(self) + cpdef _rank(self, X) + +cdef class PartitionMatroid(Matroid): + cdef dict p + cdef frozenset _groundset + cpdef groundset(self) + cpdef _rank(self, X) diff --git a/src/sage/matroids/union_matroid.pyx b/src/sage/matroids/union_matroid.pyx new file mode 100644 index 00000000000..1eeececb4f5 --- /dev/null +++ b/src/sage/matroids/union_matroid.pyx @@ -0,0 +1,325 @@ +from matroid cimport Matroid +cdef class MatroidUnion(Matroid): + r""" + Matroid Union. + + The matroid union of a set of matroids `\{(E_1,I_1),\ldots,(E_n,I_n)\}` is + a matroid `(E,I)` where `E= \bigcup_{i=1}^n E_i` and + + `I= \{\bigcup_{i=1}^n J_i | J_i \in I_i \}`. + + INPUT: + + - ``matroids`` -- a iterator of matroids. + + OUTPUT: + + A ``MatroidUnion`` instance, it's a matroid union of all matroids in ``matroids``. + """ + def __init__(self, matroids): + """ + See class definition for full documentation. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: MatroidUnion([matroids.Uniform(2,4),matroids.Uniform(5,8)]) + Matroid of rank 7 on 8 elements as matroid union of + Matroid of rank 2 on 4 elements with circuit-closures + {2: {{0, 1, 2, 3}}} + Matroid of rank 5 on 8 elements with circuit-closures + {5: {{0, 1, 2, 3, 4, 5, 6, 7}}} + """ + self.matroids = list(matroids) + E = set() + for M in self.matroids: + E.update(M.groundset()) + self._groundset = frozenset(E) + + cpdef groundset(self): + """ + Return the groundset of the matroid. + + The groundset is the set of elements that comprise the matroid. + + OUTPUT: + + A set. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = MatroidUnion([matroids.Uniform(2,4),matroids.Uniform(5,8)]) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5, 6, 7] + """ + return self._groundset + + cpdef _rank(self, X): + r""" + Return the rank of a set ``X``. + + This method does no checking on ``X``, and ``X`` may be assumed to + have the same interface as ``frozenset``. + + INPUT: + + - ``X`` -- an object with Python's ``frozenset`` interface. + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = MatroidSum([matroids.Uniform(2,4),matroids.Uniform(2,4)]) + sage: M._rank([(0,0),(1,0)]) + 2 + sage: M._rank([(0,0),(0,1),(0,2),(1,0),(1,1)]) + 4 + + ALGORITHM: + + Matroid intersection of a matroid sum and partition matroid. + + """ + summands = [] + for e in self.matroids: + summands.append(e.delete(e.groundset()-X)) + sum_matroid = MatroidSum(summands) + d = {} + for (i,x) in sum_matroid.groundset(): + if not x in d: + d[x]=set() + d[x].add(i) + part_matroid = PartitionMatroid([[(i,x) for i in d[x]] for x in d]) + return len(sum_matroid.intersection(part_matroid)) + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: MatroidUnion([matroids.Uniform(2,4),matroids.Uniform(5,8)]) + Matroid of rank 7 on 8 elements as matroid union of + Matroid of rank 2 on 4 elements with circuit-closures + {2: {{0, 1, 2, 3}}} + Matroid of rank 5 on 8 elements with circuit-closures + {5: {{0, 1, 2, 3, 4, 5, 6, 7}}} + """ + S = Matroid._repr_(self) + " as matroid union of \n" + for M in self.matroids: + S = S + M._repr_() +"\n" + return S[:-1] + +cdef class MatroidSum(Matroid): + r""" + Matroid Sum. + + The matroid sum of a list of matroids `(E_1,I_1),\ldots,(E_n,I_n)` is a matroid + `(E,I)` where `E= \bigsqcup_{i=1}^n E_i` and `I= \bigsqcup_{i=1}^n I_i`. + + INPUT: + + - ``matroids`` -- a iterator of matroids. + + OUTPUT: + + A ``MatroidSum`` instance, it's a matroid sum of all matroids in ``matroids``. + """ + def __init__(self, summands): + """ + See class definition for full documentation. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: MatroidSum([matroids.Uniform(2,4),matroids.Uniform(5,8)]) + Matroid of rank 7 on 12 elements as matroid sum of + Matroid of rank 2 on 4 elements with circuit-closures + {2: {{0, 1, 2, 3}}} + Matroid of rank 5 on 8 elements with circuit-closures + {5: {{0, 1, 2, 3, 4, 5, 6, 7}}} + """ + self.summands = list(summands) + E = set() + for i in range(len(self.summands)): + g = self.summands[i].groundset() + E.update(zip([i]*len(g),g)) + self._groundset = frozenset(E) + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: MatroidSum([matroids.Uniform(2,4),matroids.Uniform(2,4)]) + Matroid of rank 4 on 8 elements as matroid sum of + Matroid of rank 2 on 4 elements with circuit-closures + {2: {{0, 1, 2, 3}}} + Matroid of rank 2 on 4 elements with circuit-closures + {2: {{0, 1, 2, 3}}} + """ + S = Matroid._repr_(self) + " as matroid sum of \n" + for M in self.summands: + S = S + M._repr_() +"\n" + return S[:-1] + + cpdef groundset(self): + """ + Return the groundset of the matroid. + + The groundset is the set of elements that comprise the matroid. + + OUTPUT: + + A set. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = MatroidSum([matroids.Uniform(2,4),matroids.Uniform(2,4)]) + sage: sorted(M.groundset()) + [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3)] + """ + return self._groundset + + cpdef _rank(self, X): + r""" + Return the rank of a set ``X``. + + This method does no checking on ``X``, and ``X`` may be assumed to + have the same interface as ``frozenset``. + + INPUT: + + - ``X`` -- an object with Python's ``frozenset`` interface. + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = MatroidSum([matroids.Uniform(2,4),matroids.Uniform(2,4)]) + sage: M._rank([(0,0),(1,0)]) + 2 + sage: M._rank([(0,0),(0,1),(0,2),(1,0),(1,1)]) + 4 + """ + partition = {} + for (i,x) in X: + if not i in partition: + partition[i] = set() + partition[i].add(x) + rk = 0 + for i, Xi in partition.iteritems(): + rk+= self.summands[i]._rank(Xi) + return rk + +cdef class PartitionMatroid(Matroid): + r""" + Partition Matroid. + + Given a set of disjoint sets `S=\{S_1,\ldots,S_n\}`, the partition matroid + on `S` is `(E,I)` where `E=\bigcup_{i=1}^n S_i` and + + `I= \{X| |X\cap S_i|\leq 1,X\subset E \}`. + + INPUT: + + - ``partition`` -- an iterator of disjoint sets. + + OUTPUT: + + A ``PartitionMatroid`` instance, it's partition matroid of the ``partition``. + """ + def __init__(self, partition): + """ + See class definition for full documentation. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: PartitionMatroid([[1,2,3],[4,5,6]]) + Partition Matroid of rank 2 on 6 elements + sage: PartitionMatroid([[1,2],[2,3]]) + Traceback (most recent call last): + ... + ValueError: not an iterator of disjoint sets + """ + P2 = map(set,list(partition)) + if len(set.union(*P2)) != sum(map(len,P2)): + raise ValueError("not an iterator of disjoint sets") + self.p = {} + for i in range(len(P2)): + for j in P2[i]: + self.p[j] = i + E = set() + for P in partition: + E.update(P) + self._groundset = frozenset(E) + + cpdef groundset(self): + """ + Return the groundset of the matroid. + + The groundset is the set of elements that comprise the matroid. + + OUTPUT: + + A set. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = PartitionMatroid([[1,2,3],[4,5,6]]) + sage: sorted(M.groundset()) + [1, 2, 3, 4, 5, 6] + """ + return self._groundset + + cpdef _rank(self, X): + r""" + Return the rank of a set ``X``. + + This method does no checking on ``X``, and ``X`` may be assumed to + have the same interface as ``frozenset``. + + INPUT: + + - ``X`` -- an object with Python's ``frozenset`` interface. + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: M = PartitionMatroid([[1,2,3],[4,5,6]]) + sage: M._rank([1,5]) + 2 + sage: M._rank([1,2]) + 1 + """ + return len(set(map(self.p.get,X))) + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.union_matroid import * + sage: PartitionMatroid([[1,2,3],[4,5,6]]) + Partition Matroid of rank 2 on 6 elements + """ + S = "Partition "+Matroid._repr_(self) + return S diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index ba1c2aa9041..c5f6982b961 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -29,6 +29,10 @@ from sage.graphs.all import BipartiteGraph, Graph from pprint import pformat from sage.structure.all import SageObject +from sage.graphs.spanning_tree import kruskal +from sage.graphs.graph import Graph +from sage.matrix.constructor import matrix +from operator import itemgetter from sage.rings.number_field.number_field import NumberField def setprint(X): @@ -355,6 +359,125 @@ def get_nonisomorphic_matroids(MSet): OutSet.append(M) return OutSet +def spanning_forest(M): + r""" + Return a list of edges of a spanning forest of the bipartite + graph defined by `M` + + INPUT: + + - ``M`` -- a matrix defining a bipartite graph G. The vertices are the + rows and columns, if `M[i,j]` is non-zero, then there is an edge + between row `i` and column `j`. + + OUTPUT: + + A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`. + + EXAMPLES:: + + sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]]))) + 5 + sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]]))) + 3 + """ + # Given a matrix, produce a spanning tree + G = Graph() + m = M.ncols() + for (x,y) in M.dict(): + G.add_edge(x+m,y) + T = [] + # find spanning tree in each component + for component in G.connected_components(): + spanning_tree = kruskal(G.subgraph(component)) + for (x,y,z) in spanning_tree: + if x < m: + t = x + x = y + y = t + T.append((x-m,y)) + return T + +def spanning_stars(M): + r""" + Returns the edges of a connected subgraph that is a union of + all edges incident some subset of vertices. + + INPUT: + + - ``M`` -- a matrix defining a bipartite graph G. The vertices are the + rows and columns, if `M[i,j]` is non-zero, then there is an edge + between row i and column 0. + + OUTPUT: + + A list of tuples `(row,column)` in a spanning forest of the bipartite graph defined by ``M`` + + EXAMPLES:: + + sage: edges = sage.matroids.utilities.spanning_stars(matrix([[1,1,1],[1,1,1],[1,1,1]])) + sage: Graph(map(lambda (x,y): (x+3,y), edges)).is_connected() + True + """ + + G = Graph() + m = M.ncols() + for (x,y) in M.dict(): + G.add_edge(x+m,y) + + delta = (M.nrows()+m)**0.5 + # remove low degree vertices + H = [] + # candidate vertices + V_0 = set([]) + d = 0 + while G.order()>0: + (x,d) = min(G.degree_iterator(labels=True),key=itemgetter(1)) + if d < delta: + V_0.add(x) + H.extend(G.edges_incident(x,False)) + G.delete_vertex(x) + else: + break + + # min degree is at least sqrt(n) + # greedily remove vertices + G2 = G.copy() + # set of picked vertices + V_1 = set([]) + while G2.order()>0: + # choose vertex with maximum degree in G2 + (x,d) = max(G2.degree_iterator(labels=True),key=itemgetter(1)) + V_1.add(x) + G2.delete_vertices(G2.neighbors(x)) + G2.delete_vertex(x) + + # G2 is a graph of all edges incident to V_1 + G2 = Graph() + for v in V_1: + for u in G.neighbors(v): + G2.add_edge(u,v) + + V = V_0 | V_1 + # compute a spanning tree + T = spanning_forest(M) + for (x,y) in T: + if not x in V and not y in V: + V.add(v) + + for v in V: + if G.has_vertex(v): # some vertices are not in G + H.extend(G.edges_incident(v,False)) + + # T contain all edges in some spanning tree + T = [] + for (x,y) in H: + if x < m: + t = x + x = y + y = t + T.append((x-m,y)) + return T # Partial fields and lifting @@ -416,7 +539,7 @@ def lift_cross_ratios(A, lift_map = None): sage: Z [ 1 0 1 1 1] [ 1 1 0 0 z] - [ 0 1 -z -1 0] + [ 0 z - 1 1 -z + 1 0] sage: M = LinearMatroid(reduced_matrix = A) sage: sorted(M.cross_ratios()) [3, 5] diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index 0fecc7da1c4..25838c1ba26 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -50,7 +50,10 @@ from dist import install_scripts -from package import install_package, is_package_installed, standard_packages, optional_packages, experimental_packages, upgrade, package_versions +from package import (install_package, + installed_packages, is_package_installed, + standard_packages, optional_packages, experimental_packages, + upgrade, package_versions) from pager import pager diff --git a/src/sage/misc/binary_tree.pyx b/src/sage/misc/binary_tree.pyx index c4eb345ee53..8582b89b032 100644 --- a/src/sage/misc/binary_tree.pyx +++ b/src/sage/misc/binary_tree.pyx @@ -1,4 +1,6 @@ """ +Binary trees + Implements a binary tree in Cython. AUTHORS: diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index cb1c4fe2e18..9b40f1641af 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -326,9 +326,9 @@ For a typical category, few bases, if any, need to be added to force sage: x.mro == x.mro_standard False sage: x.all_bases_len() - 82 + 92 sage: x.all_bases_controlled_len() - 89 + 100 The following can be used to search through the Sage named categories for any that requires the addition of some bases:: @@ -343,8 +343,7 @@ for any that requires the addition of some bases:: Category of finite dimensional algebras with basis over Rational Field, Category of finite dimensional hopf algebras with basis over Rational Field, Category of finite permutation groups, - Category of graded hopf algebras with basis over Rational Field, - Category of hopf algebras with basis over Rational Field] + Category of graded hopf algebras with basis over Rational Field] AUTHOR: diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 66932184b3c..9b7463b2e25 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -781,7 +781,7 @@ cdef class CachedFunction(object): Return the reduced Groebner basis of this ideal. ... ALGORITHM: Uses Singular, Magma (if available), Macaulay2 (if - available), or a toy implementation. + available), Giac (if available), or a toy implementation. Test that :trac:`15184` is fixed:: diff --git a/src/sage/misc/converting_dict.py b/src/sage/misc/converting_dict.py new file mode 100644 index 00000000000..6273b7e5e12 --- /dev/null +++ b/src/sage/misc/converting_dict.py @@ -0,0 +1,291 @@ +r""" +Converting Dictionary + +At the moment, the only class contained in this model is a key +converting dictionary, which applies some function (e.g. type +conversion function) to all arguments used as keys. + +.. It is conceivable that a other dicts might be added later on. + +AUTHORS: + +- Martin von Gagern (2015-01-31): initial version + +EXAMPLES: + +A ``KeyConvertingDict`` will apply a conversion function to all method +arguments which are keys:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + +This is used e.g. in the result of a variety, to allow access to the +result no matter how a generator is identified:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? +""" + +#***************************************************************************** +# Copyright (C) 2015 Martin von Gagern +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import collections + +class KeyConvertingDict(dict): + r""" + A dictionary which automatically applys a conversions to its keys. + + The most common application is the case where the conversion + function is the object representing some category, so that key + conversion means a type conversion to adapt keys to that + category. This allows different representations for keys which in + turn makes accessing the correct element easier. + + INPUT: + + - ``key_conversion_function`` -- a function which will be + applied to all method arguments which represent keys. + - ``data`` -- optional dictionary or sequence of key-value pairs + to initialize this mapping. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: d[5.0] = 64 + sage: d["05"] + 64 + + """ + + def __init__(self, key_conversion_function, data=None): + r""" + Construct a dictionary with a given conversion function. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: KeyConvertingDict(int, {"5": 7}).items() + [(5, 7)] + sage: KeyConvertingDict(int, [("9", 99)]).items() + [(9, 99)] + """ + super(KeyConvertingDict, self).__init__() + self.key_conversion_function = key_conversion_function + if data: + self.update(data) + + def __getitem__(self, key): + r""" + Retrieve an element from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d["3"] + 42 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__getitem__(key) + + def __setitem__(self, key, value): + r""" + Assign an element in the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``value`` -- The associated value, will be left unmodified. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__setitem__(key, value) + + def __delitem__(self, key): + r""" + Remove a mapping from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: del d["3"] + sage: len(d) + 0 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__delitem__(key) + + def __contains__(self, key): + r""" + Test whether a given key is contained in the mapping. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: "3" in d + True + sage: 4 in d + False + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__contains__(key) + + def has_key(self, key): + r""" + Deprecated; present just for the sake of compatibility. + Use ``key in self`` instead. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.has_key("3") + True + sage: d.has_key(4) + False + """ + return key in self + + def pop(self, key, *args): + r""" + Remove and retreive a given element from the dictionary + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to return if the element is not mapped, optional. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.pop("3") + 42 + sage: d.pop("3", 33) + 33 + sage: d.pop("3") + Traceback (most recent call last): + ... + KeyError: ... + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).pop(key, *args) + + def setdefault(self, key, default=None): + r""" + Create a given mapping unless there already exists a mapping + for that key. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to associate with the key. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.setdefault("3") + sage: d.items() + [(3, None)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).setdefault(key, default) + + def update(self, *args, **kwds): + r""" + Update the dictionary with key-value pairs from another dictionary, + sequence of key-value pairs, or keyword arguments. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``args`` -- A single dict or sequence of pairs. + - ``kwds`` -- Named elements require that the conversion + function accept strings. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.update([("3",1),(4,2)]) + sage: d[3] + 1 + sage: d.update({"5": 7, "9": 12}) + sage: d[9] + 12 + sage: d = KeyConvertingDict(QQ['x']) + sage: d.update(x=42) + sage: d + {x: 42} + """ + f = self.key_conversion_function + u = super(KeyConvertingDict, self).update + if args: + if len(args) != 1: + raise TypeError("update expected at most 1 argument") + arg = args[0] + if isinstance(arg, collections.Mapping): + seq = ((f(k), arg[k]) for k in arg) + else: + seq = ((f(k), v) for k, v in arg) + u(seq) + if kwds: + seq = ((f(k), v) for k, v in kwds.iteritems()) + u(seq) diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 01ee022b37d..8fd9e67f6ff 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -283,7 +283,7 @@ def pyx_preparse(s): def cython(filename, verbose=False, compile_message=False, use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True, create_local_so_file=False): - """ + r""" Compile a Cython file. This converts a Cython file to a C (or C++ file), and then compiles that. The .c file and the .so file are created in a temporary directory. @@ -341,6 +341,11 @@ def cython(filename, verbose=False, compile_message=False, sage: x x^2 + Check that compiling c++ code works:: + + sage: cython("#clang C++\n"+ + ....: "from libcpp.vector cimport vector\n" + ....: "cdef vector[int] * v = new vector[int](4)\n") """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -454,7 +459,6 @@ def cython(filename, verbose=False, compile_message=False, include_dirs = %s) """%(extra_args, name, name, extension, additional_source_files, libs, language, includes) open('%s/setup.py'%build_dir,'w').write(setup) - cython_include = ' '.join(["-I '%s'"%x for x in includes if len(x.strip()) > 0 ]) options = ['-p'] @@ -463,7 +467,12 @@ def cython(filename, verbose=False, compile_message=False, if sage_namespace: options.append('--pre-import sage.all') - cmd = "cd '%s' && cython %s %s '%s.pyx' 1>log 2>err " % (build_dir, ' '.join(options), cython_include, name) + cmd = "cd '{DIR}' && cython {OPT} {INC} {LANG} '{NAME}.pyx' 1>log 2>err ".format( + DIR=build_dir, + OPT=' '.join(options), + INC=cython_include, + LANG='--cplus' if language=='c++' else '', + NAME=name) if create_local_c_file: target_c = '%s/_%s.c'%(os.path.abspath(os.curdir), base) @@ -481,9 +490,6 @@ def cython(filename, verbose=False, compile_message=False, err = subtract_from_line_numbers(open('%s/err'%build_dir).read(), offset) raise RuntimeError("Error converting {} to C:\n{}\n{}".format(filename, log, err)) - if language=='c++': - os.system("cd '%s' && mv '%s.c' '%s.cpp'"%(build_dir,name,name)) - cmd = 'cd %s && python setup.py build 1>log 2>err'%build_dir if verbose: print(cmd) diff --git a/src/sage/misc/exceptions.py b/src/sage/misc/exceptions.py deleted file mode 100644 index 3a7a02bbdc6..00000000000 --- a/src/sage/misc/exceptions.py +++ /dev/null @@ -1,33 +0,0 @@ -r""" -Exceptions - -This module defines Sage-specific exceptions. -""" - -class OptionalPackageNotFoundError(RuntimeError): - """ - This class defines the exception that should be raised when a - function, method, or class cannot detect an optional package that - it depends on. When an ``OptionalPackageNotFoundError`` is - raised, this means one of the following: - - - The required optional package is not installed. - - - The required optional package is installed, but the relevant - interface to that package is unable to detect the package. - - EXAMPLES:: - - sage: from sage.misc.exceptions import OptionalPackageNotFoundError - sage: def find_package(fav_package): - ... try: - ... raise OptionalPackageNotFoundError("Unable to detect optional package: %s" % fav_package) - ... except OptionalPackageNotFoundError: - ... raise - ... - sage: find_package("ham and spam") - Traceback (most recent call last): - ... - OptionalPackageNotFoundError: Unable to detect optional package: ham and spam - """ - pass diff --git a/src/sage/misc/fast_methods.pxd b/src/sage/misc/fast_methods.pxd index 467b74a49bc..de5c496c168 100644 --- a/src/sage/misc/fast_methods.pxd +++ b/src/sage/misc/fast_methods.pxd @@ -1,2 +1,19 @@ +cdef extern from "Python.h": + cdef size_t SIZEOF_VOID_P + cdef class FastHashable_class: cdef Py_ssize_t _hash + +cdef inline long hash_by_id(void * p): + r""" + This function is a copy paste from the default Python hash function. + """ + cdef long x + cdef size_t y = p + # bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid + # excessive hash collisions for dicts and sets + y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4)) + x = y + if x == -1: + x = -2 + return x diff --git a/src/sage/misc/fast_methods.pyx b/src/sage/misc/fast_methods.pyx index 7eccb496225..25f19e077f3 100644 --- a/src/sage/misc/fast_methods.pyx +++ b/src/sage/misc/fast_methods.pyx @@ -40,7 +40,8 @@ from sage.misc.lazy_attribute import lazy_class_attribute from cpython.bool cimport * from cpython.ref cimport * -cdef int SIZEOF_VOID_P_SHIFT = 8*sizeof(void *) - 4 +cdef extern from "Python.h": + cdef size_t SIZEOF_VOID_P cdef class WithEqualityById: """ @@ -150,15 +151,14 @@ cdef class WithEqualityById: sage: hash(a) == object.__hash__(a) True + sage: from sage.misc.fast_methods import WithEqualityById + sage: o1 = WithEqualityById() + sage: o2 = WithEqualityById() + sage: hash(o1) == hash(o2) + False """ # This is the default hash function in Python's object.c: - cdef long x - cdef size_t y = self - y = (y >> 4) | (y << SIZEOF_VOID_P_SHIFT) - x = y - if x==-1: - x = -2 - return x + return hash_by_id(self) def __richcmp__(self, other, int m): """ diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 0fcbae217e4..5920e8e9c7b 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -124,7 +124,8 @@ def category(x): sage: V = VectorSpace(QQ,3) sage: category(V) - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ try: return x.category() @@ -438,14 +439,14 @@ def symbolic_sum(expression, *args, **kwds): zeta(5) .. WARNING:: - + This function only works with symbolic expressions. To sum any other objects like list elements or function return values, please use python summation, see http://docs.python.org/library/functions.html#sum In particular, this does not work:: - + sage: n = var('n') sage: list=[1,2,3,4,5] sage: sum(list[n],n,0,3) @@ -454,12 +455,12 @@ def symbolic_sum(expression, *args, **kwds): TypeError: unable to convert n to an integer Use python ``sum()`` instead:: - + sage: sum(list[n] for n in range(4)) 10 Also, only a limited number of functions are recognized in symbolic sums:: - + sage: sum(valuation(n,2),n,1,5) Traceback (most recent call last): ... diff --git a/src/sage/misc/gperftools.py b/src/sage/misc/gperftools.py index 0b23b578077..a1efc7665cb 100644 --- a/src/sage/misc/gperftools.py +++ b/src/sage/misc/gperftools.py @@ -366,8 +366,10 @@ def crun(s, evaluator): from sage.repl.preparse import preparse py_s = preparse(s) prof.start() - evaluator(py_s) - prof.stop() + try: + evaluator(py_s) + finally: + prof.stop() prof.top() diff --git a/src/sage/misc/html.py b/src/sage/misc/html.py index 1ed98bb6681..e1868885748 100644 --- a/src/sage/misc/html.py +++ b/src/sage/misc/html.py @@ -1,5 +1,9 @@ """ -HTML typesetting for the notebook +HTML Fragments + +This module defines a HTML fragment class, which holds a piece of +HTML. This is primarily used in browser-based notebooks, though it +might be useful for creating static pages as well. """ #***************************************************************************** @@ -11,11 +15,155 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import warnings from sage.misc.latex import latex from sage.misc.sage_eval import sage_eval +from sage.structure.sage_object import SageObject +from sage.misc.superseded import deprecation +from sage.misc.decorators import rename_keyword + + + +# Various hacks for the deprecation period in :trac:`18292` are +# conditional on this bool +_old_and_deprecated_behavior = True + +def old_and_deprecated_wrapper(method): + """ + Wrapper to reinstate the old behavior of ``html`` + + See :trac:`18292`. + + EXAMPLES:: + + sage: from sage.misc.html import HtmlFragment, old_and_deprecated_wrapper + sage: @old_and_deprecated_wrapper + ....: def foo(): + ....: return HtmlFragment('foo') + + The old behavior is to print and return nothing:: + + sage: import sage.misc.html + sage: sage.misc.html._old_and_deprecated_behavior = True + sage: f = foo() + foo + sage: f + + sage: type(f) + + sage: import sage.misc.html + + The new behavior will be to return a HTML fragment:: + + sage: sage.misc.html._old_and_deprecated_behavior = False + sage: f = foo() + sage: f + foo + sage: type(f) + + + A deprecation warning is generated if the html output is not printed:: + + sage: sage.misc.html._old_and_deprecated_behavior = True + sage: def html_without_print(): + ....: html('output without pretty_print') + sage: html_without_print() + output without pretty_print + doctest:...: DeprecationWarning: html(...) will change soon to return HTML instead of printing it. Instead use pretty_print(html(...)) for strings or just pretty_print(...) for math. + See http://trac.sagemath.org/18292 for details. + + sage: def html_with_print(): + ....: pretty_print(html('output with pretty_print')) + sage: html_with_print() + output with pretty_print + """ + from sage.repl.rich_output.pretty_print import pretty_print + def wrapped(*args, **kwds): + output = method(*args, **kwds) + assert isinstance(output, HtmlFragment) + if _old_and_deprecated_behavior: + # workaround for the old SageNB interacts + pretty_print(output) + return WarnIfNotPrinted() + else: + return output + return wrapped + + +class WarnIfNotPrinted(SageObject): + """ + To be removed when the deprecation for :trac:`18292` expires. + """ + + _printed = False + + def _repr_(self): + self._printed = True + return '' + + def __del__(self): + if not self._printed: + message = """ + html(...) will change soon to return HTML instead of + printing it. Instead use pretty_print(html(...)) for + strings or just pretty_print(...) for math. + """ + message = ' '.join([l.strip() for l in message.splitlines()]) + from sage.misc.superseded import deprecation + deprecation(18292, message) + + @classmethod + def skip_pretty_print(cls, obj): + if isinstance(obj, cls): + # Consider it printed, but don't actually print + obj._printed = True + return True + else: + return False + + +class HtmlFragment(str, SageObject): + r""" + A HTML fragment. + + This is a piece of HTML, usually not a complete document. For + example, just a ``
...
`` piece and not the entire + ``...``. + + EXAMPLES:: + + sage: from sage.misc.html import HtmlFragment + sage: HtmlFragment('test') + test + + .. automethod:: _rich_repr_ + """ + + def _rich_repr_(self, display_manager, **kwds): + """ + Rich Output Magic Method + + See :mod:`sage.repl.rich_output` for details. + + EXAMPLES:: + + sage: from sage.repl.rich_output import get_display_manager + sage: dm = get_display_manager() + sage: h = sage.misc.html.HtmlFragment('old') + sage: h._rich_repr_(dm) # the doctest backend does not suppot html + OutputPlainText container + """ + OutputHtml = display_manager.types.OutputHtml + if OutputHtml in display_manager.supported_output(): + return OutputHtml(self) + else: + return display_manager.types.OutputPlainText(self) + def math_parse(s): r""" + Replace TeX-``$`` with Mathjax equations. + Turn the HTML-ish string s that can have \$\$ and \$'s in it into pure HTML. See below for a precise definition of what this means. @@ -25,7 +173,7 @@ def math_parse(s): OUTPUT: - - a string. + A :class:`HtmlFragment` instance. Do the following: @@ -41,19 +189,19 @@ def math_parse(s): EXAMPLES:: - sage: sage.misc.html.math_parse('This is $2+2$.') - 'This is .' - sage: sage.misc.html.math_parse('This is $$2+2$$.') - 'This is .' - sage: sage.misc.html.math_parse('This is \\[2+2\\].') - 'This is .' - sage: sage.misc.html.math_parse(r'This is \[2+2\].') - 'This is .' + sage: pretty_print(sage.misc.html.math_parse('This is $2+2$.')) + This is . + sage: pretty_print(sage.misc.html.math_parse('This is $$2+2$$.')) + This is . + sage: pretty_print(sage.misc.html.math_parse('This is \\[2+2\\].')) + This is . + sage: pretty_print(sage.misc.html.math_parse(r'This is \[2+2\].')) + This is . TESTS:: sage: sage.misc.html.math_parse(r'This \$\$is $2+2$.') - 'This $$is .' + This $$is . """ # first replace \\[ and \\] by , respectively. @@ -76,7 +224,7 @@ def math_parse(s): i = s.find('$') if i == -1: # No dollar signs -- definitely done. - return t + s + return HtmlFragment(t + s) elif i > 0 and s[i-1] == '\\': # A dollar sign with a backslash right before it, so # we ignore it by sticking it in the parsed string t @@ -115,66 +263,104 @@ def math_parse(s): s = s[j+1:] if len(disp) > 0: s = s[1:] - return t + return HtmlFragment(t) -class HTMLExpr(str): - r""" - A class for HTML expression - """ - def __repr__(self): - return str(self) - -class HTML: - def __call__(self, s, globals=None, locals=None): - """ - Display the given HTML expression in the notebook. - INPUT: +class HTMLFragmentFactory(SageObject): - - ``s`` -- a string + def _repr_(self): + """ + Return string representation OUTPUT: - - prints a code that embeds HTML in the output. - - By default in the notebook an output cell has two parts, first a plain - text preformat part, then second a general HTML part (not pre). If - you call html(s) at any point then that adds something that will be - displayed in the preformated part in html. + String. EXAMPLES:: - - sage: html('
sagemath') - sagemath - sage: html('
') -
+ + sage: html + Create HTML output (see html? for details) """ - return HTMLExpr(self.eval(s, globals, locals)) + return 'Create HTML output (see html? for details)' + + @old_and_deprecated_wrapper + def __call__(self, obj): + r""" + Construct a HTML fragment + + INPUT: + + - ``obj`` -- anything. An object for which you want a HTML + representation. + + OUTPUT: + + A :class:`HtmlFragment` instance. + + EXAMPLES:: + + sage: h = html('
'); pretty_print(h) +
+ sage: type(h) # should be + + + sage: pretty_print(html(1/2)) + - def eval(self, s, globals=None, locals=None): + sage: pretty_print(html('sagemath')) + sagemath + """ + # Prefer dedicated _html_() method + try: + result = obj._html_() + except AttributeError: + pass + else: + if not isinstance(result, HtmlFragment): + warnings.warn('_html_() did not return a HtmlFragment') + return HtmlFragment(result) + else: + return result + # Otherwise: convert latex to html + try: + result = obj._latex_() + except AttributeError: + pass + else: + return math_parse('${0}$'.format(obj._latex_())) + # If all else fails + return math_parse(str(obj)) + + @old_and_deprecated_wrapper + def eval(self, s, locals=None): r""" - Return an html representation for an object ``s``. + Evaluate embedded tags - If ``s`` has a method ``_html_()``, call that. Otherwise, call - :func:`math_parse` on ``str(s)``, evaluate any variables in - the result, and add some html preamble and postamble. + INPUT: - In any case, *print* the resulting html string. This method - always *returns* an empty string. + - ``s`` -- string. + - ``globals`` -- dictionary. The global variables when + evaluating ``s``. Default: the current global variables. + + OUTPUT: + + A :class:`HtmlFragment` instance. + EXAMPLES:: - sage: html.eval('
') -
- '' + sage: a = 123 + sage: html.eval('a') + + sage: html.eval('a', locals={'a': 456}) + """ if hasattr(s, '_html_'): - s._html_() - return '' - if globals is None: - globals = {} + deprecation(18292, 'html.eval() is for strings, use html() for sage objects') + return s._html_() if locals is None: - locals = {} + from sage.repl.user_globals import get_globals + locals = get_globals() s = str(s) s = math_parse(s) t = '' @@ -190,27 +376,31 @@ def eval(self, s, globals=None, locals=None): t += s[:i] + ''%\ latex(sage_eval(s[6+i:j], locals=locals)) s = s[j+7:] - print("{}".format(t)) - return '' + return HtmlFragment(t) - def table(self, x, header = False): + @old_and_deprecated_wrapper + def table(self, x, header=False): r""" - Print a nested list as a HTML table. Strings of html - will be parsed for math inside dollar and double-dollar signs. - 2D graphics will be displayed in the cells. Expressions will - be latexed. + Generate a HTML table. + See :class:`~sage.misc.table.table`. INPUT: - ``x`` -- a list of lists (i.e., a list of table rows) + - ``header`` -- a row of headers. If ``True``, then the first row of the table is taken to be the header. + OUTPUT: + + A :class:`HtmlFragment` instance. + EXAMPLES:: - sage: html.table([(i, j, i == j) for i in [0..1] for j in [0..1]]) - + sage: pretty_print(html.table([(i, j, i == j) for i in [0..1] for j in [0..1]])) + doctest:...: DeprecationWarning: use table() instead of html.table() + See http://trac.sagemath.org/18292 for details.
@@ -237,10 +427,10 @@ def table(self, x, header = False):
- - sage: html.table([(x,n(sin(x), digits=2)) for x in [0..3]], header = ["$x$", "$\sin(x)$"]) - + sage: pretty_print(html(table( + ....: [(x,n(sin(x), digits=2)) for x in range(4)], + ....: header_row=["$x$", "$\sin(x)$"])))
@@ -267,63 +457,59 @@ def table(self, x, header = False):
- - """ + from sage.misc.superseded import deprecation + deprecation(18292, 'use table() instead of html.table()') from table import table - table(x, header_row=header)._html_() + return table(x, header_row=header)._html_() + @old_and_deprecated_wrapper def iframe(self, url, height=400, width=800): r""" - Put an existing web page into a worksheet. + Generate an iframe HTML fragment INPUT: - - ``url`` -- a url string, either with or without URI scheme - (defaults to "http"). + - ``url`` -- string. A url, either with or without URI scheme + (defaults to "http"), or an absolute file path. + - ``height`` -- the number of pixels for the page height. Defaults to 400. + - ``width`` -- the number of pixels for the page width. Defaults to 800. OUTPUT: - - Opens the url in a worksheet. If the url is a regular web page it - will appear in the worksheet. This was originally intended to bring - GeoGebra worksheets into Sage, but it can be used for many other - purposes. + + A :class:`HtmlFragment` instance. EXAMPLES:: - sage: html.iframe("sagemath.org") - - sage: html.iframe("http://sagemath.org",30,40) - - sage: html.iframe("https://sagemath.org",30) - - sage: html.iframe("/home/admin/0/data/filename") - - sage: html.iframe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA' + sage: pretty_print(html.iframe("sagemath.org")) + + sage: pretty_print(html.iframe("http://sagemath.org",30,40)) + + sage: pretty_print(html.iframe("https://sagemath.org",30)) + + sage: pretty_print(html.iframe("/home/admin/0/data/filename")) + + sage: pretty_print(html.iframe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA' ... 'AUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBA' - ... 'AO9TXL0Y4OHwAAAABJRU5ErkJggg=="') - + ... 'AO9TXL0Y4OHwAAAABJRU5ErkJggg=="')) + + """ + if url.startswith('/'): + url = 'file://{0}'.format(url) + elif '://' not in url: + url = 'http://{0}'.format(url) + return HtmlFragment('' + .format(height, width, url)) - AUTHOR: - - Bruce Cohen (2011-06-14) - """ - if ":" not in url and not url.startswith('/'): - url = "http://" + url - string = ( '' % - (height, width, url) ) - return html(string) - -html = HTML() -# Ensure that html appear in the sphinx doc as a function -# so that the link :func:`html` is correctly set up. -html.__doc__ = HTML.__call__.__doc__ +html = HTMLFragmentFactory() +html.__doc__ = HTMLFragmentFactory.__call__.__doc__ diff --git a/src/sage/misc/latex.py b/src/sage/misc/latex.py index b87b771de6d..4b90cbd237d 100644 --- a/src/sage/misc/latex.py +++ b/src/sage/misc/latex.py @@ -2329,6 +2329,26 @@ def repr_lincomb(symbols, coeffs): sage: repr_lincomb([1,5,-3],[2,8/9,7]) '2\\cdot 1 + \\frac{8}{9}\\cdot 5 + 7\\cdot -3' + + Verify that :trac:`17299` (latex representation of modular symbols) + is fixed:: + + sage: x = EllipticCurve('64a1').modular_symbol_space(sign=1).basis()[0] + sage: from sage.misc.latex import repr_lincomb + sage: latex(x.modular_symbol_rep()) + \left\{\frac{-1}{3}, \frac{-1}{4}\right\} - \left\{\frac{1}{5}, \frac{1}{4}\right\} + + Verify that it works when the symbols are numbers:: + + sage: x = FormalSum([(1,2),(3,4)]) + sage: latex(x) + 2 + 3\cdot 4 + + Verify that it works when ``bv in CC`` raises an error:: + + sage: x = FormalSum([(1,'x'),(2,'y')]) + sage: latex(x) + \text{\texttt{x}} + 2\text{\texttt{y}} """ s = "" first = True @@ -2344,21 +2364,25 @@ def repr_lincomb(symbols, coeffs): if first: s += b else: - s += " + %s"%b + s += " + %s" % b else: coeff = coeff_repr(c) + if coeff == "-1": + coeff = "-" if first: coeff = str(coeff) else: - coeff = " + %s"%coeff + coeff = " + %s" % coeff # this is a hack: i want to say that if the symbol # happens to be a number, then we should put a # multiplication sign in try: if bv in CC: - s += "%s\cdot %s"%(coeff, b) + s += "%s\cdot %s" % (coeff, b) + else: + s += "%s%s" % (coeff, b) except Exception: - s += "%s%s"%(coeff, b) + s += "%s%s" % (coeff, b) first = False i += 1 if first: @@ -2537,7 +2561,7 @@ def latex_variable_name(x, is_fname=False): 'x_{\\ast}' TESTS:: - + sage: latex_variable_name('_C') # :trac:`16007` 'C' sage: latex_variable_name('_K1') diff --git a/src/sage/misc/method_decorator.py b/src/sage/misc/method_decorator.py index b37b556994f..7404847f581 100644 --- a/src/sage/misc/method_decorator.py +++ b/src/sage/misc/method_decorator.py @@ -12,12 +12,13 @@ class MethodDecorator(SageObject): def __init__(self, f): """ EXAMPLE:: + sage: from sage.misc.method_decorator import MethodDecorator sage: class Foo: - ... @MethodDecorator - ... def bar(self, x): - ... return x**2 - ... + ....: @MethodDecorator + ....: def bar(self, x): + ....: return x**2 + ....: sage: J = Foo() sage: J.bar diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index cce84eac047..8deb81cad68 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -141,6 +141,7 @@ def SAGE_TMP(): def SPYX_TMP(): """ EXAMPLES:: + sage: from sage.misc.misc import SPYX_TMP sage: SPYX_TMP l'.../temp/.../spyx' @@ -919,6 +920,7 @@ def compose(f, g): 3*x + 3 :: + sage: _ = function('f g') sage: _ = var ('x') sage: compose(f,g)(x) @@ -1720,7 +1722,33 @@ def random_sublist(X, s): return [a for a in X if random.random() <= s] +def some_tuples(elements, repeat, bound): + r""" + Return an iterator over at most ``bound`` number of ``repeat``-tuples of + ``elements``. + + TESTS:: + + sage: from sage.misc.misc import some_tuples + sage: l = some_tuples([0,1,2,3], 2, 3) + sage: l + + sage: len(list(l)) + 3 + + sage: l = some_tuples(range(50), 3, 10) + sage: len(list(l)) + 10 + .. TODO:: + + Currently, this only return an iterator over the first element of the + cartesian product. It would be smarter to return something more + "random like" as it is used in tests. However, this should remain + deterministic. + """ + from itertools import islice, product + return islice(product(elements, repeat=repeat), bound) def powerset(X): r""" diff --git a/src/sage/misc/mrange.py b/src/sage/misc/mrange.py index eb03223dbf6..711059bb601 100644 --- a/src/sage/misc/mrange.py +++ b/src/sage/misc/mrange.py @@ -608,3 +608,110 @@ def cartesian_product_iterator(X): [()] """ return xmrange_iter(X, tuple) + +def cantor_product(*args, **kwds): + r""" + Return an iterator over the product of the inputs along the diagonals a la + :wikipedia:`Cantor pairing `. + + INPUT: + + - a certain number of iterables + + - ``repeat`` -- an optional integer. If it is provided, the input is + repeated ``repeat`` times. + + EXAMPLES:: + + sage: from sage.misc.mrange import cantor_product + sage: list(cantor_product([0, 1], repeat=3)) + [(0, 0, 0), + (1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (1, 1, 0), + (1, 0, 1), + (0, 1, 1), + (1, 1, 1)] + sage: list(cantor_product([0, 1], [0, 1, 2, 3])) + [(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2), (0, 3), (1, 3)] + + Infinite iterators are valid input as well:: + + sage: from itertools import islice + sage: list(islice(cantor_product(ZZ, QQ), 14)) + [(0, 0), + (1, 0), + (0, 1), + (-1, 0), + (1, 1), + (0, -1), + (2, 0), + (-1, 1), + (1, -1), + (0, 1/2), + (-2, 0), + (2, 1), + (-1, -1), + (1, 1/2)] + + TESTS:: + + sage: C = cantor_product([0, 1], [0, 1, 2, 3], [0, 1, 2]) + sage: sum(1 for _ in C) == 2*4*3 + True + + sage: from itertools import count + sage: list(cantor_product([], count())) + [] + sage: list(cantor_product(count(), [], count())) + [] + + sage: list(cantor_product(count(), repeat=0)) + [()] + + sage: next(cantor_product(count(), repeat=-1)) + Traceback (most recent call last): + ... + ValueError: repeat argument cannot be negative + sage: next(cantor_product(count(), toto='hey')) + Traceback (most recent call last): + ... + TypeError: 'toto' is an invalid keyword argument for this function + """ + from itertools import count + from sage.combinat.integer_lists import IntegerListsLex + + m = len(args) # numer of factors + lengths = [None] * m # None or length of factors + data = [[] for _ in range(m)] # the initial slice of each factor + iterators = [iter(a) for a in args] # the iterators + repeat = int(kwds.pop('repeat', 1)) + if repeat == 0: + yield () + return + elif repeat < 0: + raise ValueError("repeat argument cannot be negative") + if kwds: + raise TypeError("'{}' is an invalid keyword argument for this function".format(next(kwds.iterkeys()))) + mm = m * repeat + + for n in count(0): + # try to add one more term to each bin + for i, a in enumerate(iterators): + if lengths[i] is None: + try: + data[i].append(next(a)) + except StopIteration: + assert len(data[i]) == n + if n == 0: + return + lengths[i] = n + + # iterate through what we have + ceiling = [n if lengths[i] is None else lengths[i]-1 for i in range(m)] * repeat + for v in IntegerListsLex(n, length=mm, ceiling=ceiling): + yield tuple(data[i%m][v[i]] for i in range(mm)) + + if all(l is not None for l in lengths) and repeat*sum(l-1 for l in lengths) == n: + return diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index 7d55080cf57..c0172b83415 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -1,187 +1,74 @@ r""" -Sage package management commands +Listing Sage packages -A Sage package has the extension .spkg. It is a tarball that is -(usually) bzip2 compressed that contains arbitrary data and an -spkg-install file. An Sage package typically has the following -components: +This module can be used to see which Sage packages are installed +and which packages are available for installation. +For more information about creating Sage packages, see +the "Packaging Third-Party Code" section of the +Sage Developer's Guide. -- spkg-install - shell script that is run to install the package +Actually installing the packages should be done via the command +line, using the following commands: -- Sage.txt - file that describes how the package was made, who - maintains it, etc. +- ``sage -i PACKAGE_NAME`` -- install the given package -- sage - directory with extra patched version of files that needed - during the install - - -Use the ``install_package`` command to install a new -package, and use ``optional_packages`` to list all -optional packages available on the central Sage server. The -``upgrade`` command upgrades all *standard* packages - -there is no auto-upgrade command for optional packages. - -All package management can also be done via the Sage command line. +- ``sage -f PACKAGE_NAME`` -- re-install the given package, even if it + was already installed """ -import os - -__installed_packages = None - - -def install_all_optional_packages(force=True, dry_run=False): - r""" - Install all available optional spkg's in the official Sage spkg - repository. Returns a list of all spkg's that *fail* to install. - - INPUT: - - - ``force`` -- bool (default: ``True``); whether to force - reinstall of spkg's that are already installed. - - - ``dry_run`` -- bool (default: ``False``); if ``True``, just list - the packages that would be installed in order, but don't - actually install them. - - OUTPUT: +#***************************************************************************** +# 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/ +#***************************************************************************** - list of strings - - .. NOTE:: - - This is designed mainly for testing purposes. This also - doesn't do anything with respect to dependencies -- the - packages are installed in alphabetical order. Dependency - issues will be dealt with in a future version. - - - AUTHOR: - - -- William Stein (2008-12) - - EXAMPLES:: +import os - sage: sage.misc.package.install_all_optional_packages(dry_run=True) # optional - internet - Installing ... - [] - """ - # Get list of all packages from the package server - installed, not_installed = optional_packages() - if force: - v = installed + not_installed - else: - v = not_installed - failed = [] - - # sort the list of packages in alphabetical order - v.sort() - - # install each one - for pkg in v: - try: - print("Installing {}...".format(pkg)) - if not dry_run: - # only actually do the install of the package if dry_run is False - install_package(pkg, force=force) - except ValueError as msg: - # An error occurred -- catch exception and record this in - # the list of failed installed. - print("*"*70) - print("FAILED to install '{}'".format(pkg)) - print("*"*70) - failed.append(pkg) - return failed - -def install_package(package=None, force=False): +def install_package(package=None, force=None): """ - Install a package or return a list of all packages that have been - installed into this Sage install. - - You must have an internet connection. Also, you will have to - restart Sage for the changes to take affect. + This function is obsolete. Run ``sage -i PKGNAME`` from a shell + to install a package. Use the function :func:`installed_packages` + to list all installed packages. - It is not needed to provide the version number. + TESTS:: - INPUT: + sage: install_package() + doctest:...: DeprecationWarning: use installed_packages() to list all installed packages + See http://trac.sagemath.org/16759 for details. + [...'atlas...'python...] + sage: install_package("autotools") + Traceback (most recent call last): + ... + NotImplementedError: installing Sage packages using 'install_package()' is obsolete. + Run 'sage -i autotools' from a shell prompt instead + """ + if package is not None: + # deprecation(16759, ...) + raise NotImplementedError("installing Sage packages using 'install_package()' is obsolete.\nRun 'sage -i {}' from a shell prompt instead".format(package)) - - ``package`` -- optional; if specified, install the - given package. If not, list all installed packages. + from sage.misc.superseded import deprecation + deprecation(16759, "use installed_packages() to list all installed packages") + return installed_packages() - - ``force`` -- boolean (default: ``False``); if ``True``, reinstall - the package given if it is already installed. (Otherwise, an already - installed package doesn't get reinstalled, as with 'sage -i ...'.) - EXAMPLES: +def installed_packages(): + """ + Return a list of all installed packages, with version numbers. - With no arguments, list the installed packages:: + EXAMPLES:: - sage: install_package() + sage: installed_packages() [...'atlas...'python...] - With an argument, install the named package:: - - sage: install_package('chomp') # not tested - Attempting to download package chomp-20100213.p2 - ... + .. seealso:: - IMPLEMENTATION: - - Calls 'sage -f ...' to (re)install the package if a package name is - given. If no package name is given, simply list the contents of - ``spkg/installed``. - - .. seealso:: :func:`optional_packages`, :func:`upgrade` + :func:`standard_packages`, :func:`optional_packages`, :func:`experimental_packages` """ - global __installed_packages - if os.uname()[0][:6] == 'CYGWIN' and package is not None: - print("install_package may not work correctly under Microsoft Windows") - print("since you can't change an opened file. Quit all") - print("instances of Sage and use 'sage -i {}' instead or".format(package)) - print("use the force option to install_package().") - - if package is None: - if __installed_packages is None: - import sage.env - __installed_packages = sorted(os.listdir(sage.env.SAGE_SPKG_INST)) - return __installed_packages - - - - import stopgap - stopgap.stopgap("The Sage function 'install_packages' is currently broken: it does not correctly install new packages. Please use 'sage -i {}' from a shell prompt instead.".format(package), 16759) - return - - - - # Get full package name / list of candidates: - if force: - # Also search packages already installed. - S = [P for P in standard_packages()[0] if P.startswith(package)] - O = [P for P in optional_packages()[0] if P.startswith(package)] - E = [P for P in experimental_packages()[0] if P.startswith(package)] - else: - S,O,E = [], [], [] - S.extend([P for P in standard_packages()[1] if P.startswith(package)]) - O.extend([P for P in optional_packages()[1] if P.startswith(package)]) - E.extend([P for P in experimental_packages()[1] if P.startswith(package)]) - L = S+O+E - if len(L) > 1: - if force: - print("Possible package names starting with '{}' are:".format(package)) - else: - print("Possible names of non-installed packages starting with '{}':".format(package)) - for P in L: - print(" ", P) - raise ValueError("There is more than one package name starting with '{}'. Please specify!".format(package)) - if len(L) == 0: - if not force: - if is_package_installed(package): - raise ValueError("Package is already installed. Try install_package('{}',force=True)".format(package)) - raise ValueError("There is no package name starting with '{}'.".format(package)) - # len(L) == 1, i.e. exactly one package matches the given one. - os.system('sage -f "{}"'.format(L[0])) - __installed_packages = None + from sage.env import SAGE_SPKG_INST + return sorted(os.listdir(SAGE_SPKG_INST)) def is_package_installed(package): @@ -201,7 +88,7 @@ def is_package_installed(package): Otherwise, installing "pillow" will cause this function to think that "pil" is installed, for example. """ - return any(p.split('-')[0] == package for p in install_package()) + return any(p.split('-')[0] == package for p in installed_packages()) def package_versions(package_type, local=False): r""" @@ -304,10 +191,8 @@ def standard_packages(): - NOT installed standard packages (as a list) - Use ``install_package(package_name)`` to install or - re-install a given package. - - .. seealso:: :func:`install_package`, :func:`upgrade` + Run ``sage -i package_name`` from a shell to install a given + package or ``sage -f package_name`` to re-install it. EXAMPLE:: @@ -333,11 +218,8 @@ def optional_packages(): - NOT installed optional packages (as a list) - - Use ``install_package(package_name)`` to install or - re-install a given package. - - .. seealso:: :func:`install_package`, :func:`upgrade` + Run ``sage -i package_name`` from a shell to install a given + package or ``sage -f package_name`` to re-install it. EXAMPLE:: @@ -362,11 +244,8 @@ def experimental_packages(): - NOT installed experimental packages (as a list) - - Use ``install_package(package_name)`` to install or - re-install a given package. - - .. seealso:: :func:`install_package`, :func:`upgrade` + Run ``sage -i package_name`` from a shell to install a given + package or ``sage -f package_name`` to re-install it. EXAMPLE:: @@ -379,42 +258,58 @@ def experimental_packages(): """ return _package_lists_from_sage_output('experimental') -################################################################# -# Upgrade to latest version of Sage -################################################################# - def upgrade(): """ - Download and build the latest version of Sage. + Obsolete function, run 'sage --upgrade' from a shell prompt instead. - You must have an internet connection. Also, you will have to - restart Sage for the changes to take affect. + TESTS:: - This upgrades to the latest version of core packages (optional - packages are not automatically upgraded). + sage: upgrade() + Traceback (most recent call last): + ... + NotImplementedError: upgrading Sage using 'upgrade()' is obsolete. + Run 'sage --upgrade' from a shell prompt instead + """ + # deprecation(16759, ..) + raise NotImplementedError("upgrading Sage using 'upgrade()' is obsolete.\nRun 'sage --upgrade' from a shell prompt instead") - This will not work on systems that don't have a C compiler. - .. seealso:: :func:`install_package`, :func:`optional_packages` +class PackageNotFoundError(RuntimeError): """ - global __installed_packages - if os.uname()[0][:6] == 'CYGWIN': - print("Upgrade may not work correctly under Microsoft Windows") - print("since you can't change an opened file. Quit all") - print("instances of Sage and use 'sage -upgrade' instead.") - return - - os.system('sage -upgrade') - __installed_packages = None - print("You should quit and restart Sage now.") - - -def package_mesg(package_name): - mesg = 'To install the package %s type install_package("%s")\n'%(package_name, package_name) - mesg += 'at the sage prompt. Note, the version number might\n' - mesg += 'change; if so, type optional_packages() to see a list \n' - mesg += 'of possibilities. All this requires an internet connection.' - mesg += 'For more help, type optional_packages?' - return mesg + This class defines the exception that should be raised when a + function, method, or class cannot detect a Sage package that it + depends on. + + This exception should be raised with a single argument, namely + the name of the package. + + When an ``PackageNotFoundError`` is raised, this means one of the + following: + - The required optional package is not installed. + + - The required optional package is installed, but the relevant + interface to that package is unable to detect the package. + + EXAMPLES:: + + sage: from sage.misc.package import PackageNotFoundError + sage: raise PackageNotFoundError("my_package") + Traceback (most recent call last): + ... + PackageNotFoundError: the package 'my_package' was not found. You can install it by running 'sage -i my_package' in a shell + """ + def __str__(self): + """ + Return the actual error message. + + EXAMPLES:: + + sage: from sage.misc.package import PackageNotFoundError + sage: str(PackageNotFoundError("my_package")) + "the package 'my_package' was not found. You can install it by running 'sage -i my_package' in a shell" + """ + return ("the package {0!r} was not found. " + "You can install it by running 'sage -i {0}' in a shell" + .format(self.args[0])) diff --git a/src/sage/misc/randstate.pxd b/src/sage/misc/randstate.pxd index 71a3903ce61..434dad3a5f2 100644 --- a/src/sage/misc/randstate.pxd +++ b/src/sage/misc/randstate.pxd @@ -1,5 +1,11 @@ from sage.libs.gmp.types cimport gmp_randstate_t +# The c_random() method on randstate objects gives a value +# 0 <= n <= SAGE_RAND_MAX +cdef extern from *: + int SAGE_RAND_MAX "(0x7fffffff)" # 2^31 - 1 + + cdef class randstate: cdef gmp_randstate_t gmp_state cdef object _seed diff --git a/src/sage/misc/rest_index_of_methods.py b/src/sage/misc/rest_index_of_methods.py index 6c74764b983..c928f036c62 100644 --- a/src/sage/misc/rest_index_of_methods.py +++ b/src/sage/misc/rest_index_of_methods.py @@ -7,7 +7,9 @@ {INDEX_OF_FUNCTIONS} """ -def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): +from sage.misc.sageinspect import _extract_embedded_position + +def gen_rest_table_index(list_of_entries, names=None, sort=True, only_local_functions=True): r""" Return a ReST table describing a list of functions. @@ -24,6 +26,10 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): deprecated or those starting with an underscore. In the case of a class, note that inherited methods are not displayed. + - ``names`` -- a dictionary associating a name to a function. Takes + precedence over the automatically computed name for the functions. Only + used when ``list_of_entries`` is a list. + - ``sort`` (boolean; ``True``) -- whether to sort the list of methods lexicographically. @@ -32,6 +38,12 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): will be filtered out. This can be useful to disable for making indexes of e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + .. WARNING:: + + The ReST tables returned by this function use '@' as a delimiter for + cells. This can cause trouble if the first sentence in the documentation + of a function contains the '@' character. + EXAMPLE:: sage: from sage.misc.rest_index_of_methods import gen_rest_table_index @@ -39,9 +51,9 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): .. csv-table:: :class: contentstable :widths: 30, 70 - :delim: | + :delim: @ - :func:`~sage.graphs.generators.smallgraphs.PetersenGraph` | The Petersen Graph is a named graph that consists of 10 vertices... + :func:`~sage.graphs.generators.smallgraphs.PetersenGraph` @ The Petersen Graph is a named graph that consists of 10 vertices... The table of a module:: @@ -49,9 +61,14 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): .. csv-table:: :class: contentstable :widths: 30, 70 - :delim: | + :delim: @ + + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` | Return a ReST table describing a list of functions... The table of a class:: @@ -59,13 +76,26 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): .. csv-table:: :class: contentstable :widths: 30, 70 - :delim: | + :delim: @ ... - :meth:`~sage.graphs.graph.Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string. + :meth:`~sage.graphs.graph.Graph.sparse6_string` @ Returns the sparse6 representation of the graph as an ASCII string. ... TESTS: + When the first sentence of the docstring spans over several lines:: + + sage: def a(): + ....: r''' + ....: Here is a very very very long sentence + ....: that spans on several lines. + ....: + ....: EXAMP... + ....: ''' + ....: print "hey" + sage: 'Here is a very very very long sentence that spans on several lines' in gen_rest_table_index([a]) + True + The inherited methods do not show up:: sage: gen_rest_table_index(sage.combinat.posets.lattices.FiniteLatticePoset).count('\n') < 50 @@ -83,64 +113,197 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): .. csv-table:: :class: contentstable :widths: 30, 70 - :delim: | + :delim: @ + + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` | Return a ReST table describing a list of functions... sage: print gen_rest_table_index(sage.misc.rest_index_of_methods, only_local_functions=False) .. csv-table:: :class: contentstable :widths: 30, 70 - :delim: | + :delim: @ + + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + A function that is imported into a class under a different name is listed + under its 'new' name:: + + sage: 'cliques_maximum' in gen_rest_table_index(Graph) + True + sage: 'all_max_cliques`' in gen_rest_table_index(Graph) + False """ import inspect + if names is None: + names = {} # If input is a class/module, we list all its non-private and methods/functions if (inspect.isclass(list_of_entries) or inspect.ismodule(list_of_entries)): root = list_of_entries - def local_filter(f,name): - if only_local_functions: - return inspect.getmodule(root) == inspect.getmodule(f) - else: - return inspect.isclass(list_of_entries) or not (f is gen_rest_table_index) - list_of_entries = [getattr(root,name) for name,f in root.__dict__.items() if - (not name.startswith('_') and # private functions - not hasattr(f,'trac_number') and # deprecated functions - not inspect.isclass(f) and # classes - local_filter(f,name) # possibly filter imported functions - )] + list_of_entries,names = list_of_subfunctions(root, only_local_functions=only_local_functions) + + fname = lambda x:names.get(x,getattr(x,"__name__","")) assert isinstance(list_of_entries,list) s = (".. csv-table::\n" " :class: contentstable\n" " :widths: 30, 70\n" - " :delim: |\n\n") + " :delim: @\n\n") if sort: - list_of_entries.sort(key=lambda x:getattr(x,'__name__','')) + list_of_entries.sort(key=fname) for e in list_of_entries: if inspect.ismethod(e): - link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+e.__name__+"`" + link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+fname(e)+"`" elif inspect.isfunction(e): - link = ":func:`~"+str(e.__module__)+"."+str(e.__name__)+"`" + link = ":func:`~"+str(e.__module__)+"."+fname(e)+"`" else: continue + # Extract lines injected by cython + doc = e.__doc__ + doc_tmp = _extract_embedded_position(doc) + if doc_tmp: + doc = doc_tmp[0] + # Descriptions of the method/function - if e.__doc__: - desc = e.__doc__.splitlines() - desc = desc[0] if desc[0] else desc[1] + if doc: + desc = doc.split('\n\n')[0] # first paragraph + desc = " ".join([x.strip() for x in desc.splitlines()]) # concatenate lines + desc = desc.strip() # remove leading spaces else: desc = "NO DOCSTRING" - s += " {} | {}\n".format(link,desc.lstrip()) + s += " {} @ {}\n".format(link,desc.lstrip()) return s+'\n' +def list_of_subfunctions(root, only_local_functions=True): + r""" + Returns the functions (resp. methods) of a given module (resp. class) with their names. + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + OUTPUT: + + A pair ``(list,dict)`` where ``list`` is a list of function/methods and + ``dict`` associates to every function/method the name under which it appears + in ``root``. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + import inspect + if inspect.ismodule(root): + ismodule = True + elif inspect.isclass(root): + ismodule = False + superclasses = inspect.getmro(root)[1:] + else: + raise ValueError("'root' must be a module or a class.") + + def local_filter(f,name): + if only_local_functions: + if ismodule: + return inspect.getmodule(root) == inspect.getmodule(f) + else: + return not any(hasattr(s,name) for s in superclasses) + else: + return inspect.isclass(root) or not (f is gen_rest_table_index) + + functions = {getattr(root,name):name for name,f in root.__dict__.items() if + (not name.startswith('_') and # private functions + not hasattr(f,'trac_number') and # deprecated functions + not inspect.isclass(f) and # classes + callable(f) and # e.g. GenericGraph.graphics_array_defaults + local_filter(f,name)) # possibly filter imported functions + } + return functions.keys(),functions + +def gen_thematic_rest_table_index(root,additional_categories=None,only_local_functions=True): + r""" + Return a ReST string of thematically sorted function (or methods) of a module (or class). + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``additional_categories`` -- a dictionary associating a category (given as + a string) to a function's name. Can be used when the decorator + :func:`doc_index` does not work on a function. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import gen_thematic_rest_table_index, list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + from collections import defaultdict + if additional_categories is None: + additional_categories = {} + + functions,names = list_of_subfunctions(root,only_local_functions=only_local_functions) + theme_to_function = defaultdict(list) + for f in functions: + theme_to_function[getattr(f,"doc_index",additional_categories.get(f,"Unsorted"))].append(f) + s = ["**"+theme+"**\n\n"+gen_rest_table_index(list_of_functions,names=names) + for theme, list_of_functions in sorted(theme_to_function.items())] + return "\n\n".join(s) + +def doc_index(name): + r""" + Attribute an index name to a function. + + This decorator can be applied to a function/method in order to specify in + which index it must appear, in the index generated by + :func:`gen_thematic_rest_table_index`. + + INPUT: + + - ``name`` -- a string, which will become the title of the index in which + this function/method will appear. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import doc_index + sage: @doc_index("Wouhouuuuu") + ....: def a(): + ....: print "Hey" + sage: a.doc_index + 'Wouhouuuuu' + """ + def hey(f): + setattr(f,"doc_index",name) + return f + return hey + __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index([gen_rest_table_index])) diff --git a/src/sage/misc/sage_unittest.py b/src/sage/misc/sage_unittest.py index f2a6d1d9e54..ba4327caffc 100644 --- a/src/sage/misc/sage_unittest.py +++ b/src/sage/misc/sage_unittest.py @@ -527,12 +527,12 @@ def some_elements(self, S=None): sage: list(tester.some_elements()) [0, 1, 2, 3, 4] - sage: C = CartesianProduct(Z, Z, Z, Z) + sage: C = cartesian_product([Z]*4) sage: len(C) 390625 sage: tester = InstanceTester(C, elements = C, max_runs=4) sage: list(tester.some_elements()) - [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]] + [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)] """ if S is None: if self._elements is None: @@ -573,7 +573,9 @@ def _test_pickling(self, **options): sage: from sage.misc.sage_unittest import PythonObjectWithTests sage: PythonObjectWithTests(int(1))._test_pickling() - SEE ALSO: :func:`dumps` :func:`loads` + .. SEEALSO:: + + :func:`dumps`, :func:`loads` """ tester = instance_tester(self, **options) from sage.misc.all import loads, dumps diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 0419400dbf3..926b4ae9812 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -70,6 +70,7 @@ (r'\\mapsto', ' |--> '), (r'\\lvert', '|'), (r'\\rvert', '|'), + (r'\\mid', '|'), ] nonmath_substitutes = [ ('\\_','_'), @@ -673,7 +674,7 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', if 'ignore_case' in kwds: ignore_case = kwds['ignore_case'] else: - ignore_case = False + ignore_case = True if 'multiline' in kwds: multiline = kwds['multiline'] else: @@ -822,7 +823,7 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', extra5='', **kwds): r""" Search Sage library source code for lines containing ``string``. - The search is case-sensitive. + The search is case-insensitive by default. INPUT: @@ -837,8 +838,8 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', regular expression, and it might have unexpected results if used with regular expressions. - - ``ignore_case`` (optional, default False) - if True, perform a - case-insensitive search + - ``ignore_case`` (optional, default True) - if False, perform a + case-sensitive search - ``multiline`` (optional, default False) - if True, search more than one line at a time. In this case, print any matching file @@ -872,8 +873,8 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', The ``string`` and ``extraN`` arguments are treated as regular expressions, as is ``path_re``, and errors will be raised if they - are invalid. The matches will be case-sensitive unless - ``ignore_case`` is True. + are invalid. The matches will be case-insensitive unless + ``ignore_case`` is False. .. note:: @@ -924,18 +925,18 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', sage: print search_src(" fetch\(", "def", "pyx", interact=False) # random # long time matrix/matrix0.pyx: cdef fetch(self, key): - As noted above, the search is case-sensitive, but you can make it - case-insensitive with the 'ignore_case' key word:: + As noted above, the search is case-insensitive, but you can make it + case-sensitive with the 'ignore_case' key word:: sage: s = search_src('Matrix', path_re='matrix', interact=False); s.find('x') > 0 True sage: s = search_src('MatRiX', path_re='matrix', interact=False); s.find('x') > 0 - False - - sage: s = search_src('MatRiX', path_re='matrix', interact=False, ignore_case=True); s.find('x') > 0 True + sage: s = search_src('MatRiX', path_re='matrix', interact=False, ignore_case=False); s.find('x') > 0 + False + Searches are by default restricted to single lines, but this can be changed by setting ``multiline`` to be True. In the following, since ``search_src(string, interact=False)`` returns a string with @@ -964,7 +965,7 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', misc/sagedoc.py:... print search_src(" fetch\(", "def", "pyx", interact=False) # random # long time misc/sagedoc.py:... s = search_src('Matrix', path_re='matrix', interact=False); s.find('x') > 0 misc/sagedoc.py:... s = search_src('MatRiX', path_re='matrix', interact=False); s.find('x') > 0 - misc/sagedoc.py:... s = search_src('MatRiX', path_re='matrix', interact=False, ignore_case=True); s.find('x') > 0 + misc/sagedoc.py:... s = search_src('MatRiX', path_re='matrix', interact=False, ignore_case=False); s.find('x') > 0 misc/sagedoc.py:... len(search_src('log', 'derivative', interact=False).splitlines()) < 40 misc/sagedoc.py:... len(search_src('log', 'derivative', interact=False, multiline=True).splitlines()) > 70 misc/sagedoc.py:... print search_src('^ *sage[:] .*search_src\(', interact=False) # long time @@ -997,7 +998,7 @@ def search_doc(string, extra1='', extra2='', extra3='', extra4='', extra5='', **kwds): """ Search Sage HTML documentation for lines containing ``string``. The - search is case-sensitive. + search is case-insensitive by default. The file paths in the output are relative to ``$SAGE_DOC/output``. @@ -1035,7 +1036,7 @@ def search_def(name, extra1='', extra2='', extra3='', extra4='', extra5='', **kwds): r""" Search Sage library source code for function definitions containing - ``name``. The search is case sensitive. + ``name``. The search is case-insensitive by default. INPUT: same as for :func:`search_src`. diff --git a/src/sage/misc/six.py b/src/sage/misc/six.py index 9f2861ac4a8..15eee19d256 100644 --- a/src/sage/misc/six.py +++ b/src/sage/misc/six.py @@ -1,3 +1,7 @@ +""" +Python 2 and 3 Compatibility +""" + from six import * def with_metaclass(meta, *bases): diff --git a/src/sage/misc/superseded.py b/src/sage/misc/superseded.py index 6c13f92a80a..c676ee410c2 100644 --- a/src/sage/misc/superseded.py +++ b/src/sage/misc/superseded.py @@ -274,12 +274,13 @@ class DeprecatedFunctionAlias(object): - Florent Hivert (2009-11-23), with the help of Mike Hansen. - Luca De Feo (2011-07-11), printing the full module path when different from old path """ - def __init__(self, trac_number, func, module): + def __init__(self, trac_number, func, module, instance = None, unbound = None): r""" TESTS:: sage: from sage.misc.superseded import deprecated_function_alias sage: g = deprecated_function_alias(13109, number_of_partitions) + sage: from sage.misc.superseded import deprecated_function_alias sage: g.__doc__ 'Deprecated: Use :func:`number_of_partitions` instead.\nSee :trac:`13109` for details.\n\n' """ @@ -290,7 +291,8 @@ def __init__(self, trac_number, func, module): pass # Cython classes don't have __dict__ self.func = func self.trac_number = trac_number - self.instance = None # for use with methods + self.instance = instance # for use with methods + self.unbound = unbound self.__module__ = module if isinstance(func, type(deprecation)): sphinxrole = "func" @@ -315,7 +317,8 @@ def __name__(self): sage: class cls(object): ....: def new_meth(self): return 42 ....: old_meth = deprecated_function_alias(13109, new_meth) - ....: + sage: cls.old_meth.__name__ + 'old_meth' sage: cls().old_meth.__name__ 'old_meth' @@ -344,11 +347,12 @@ def is_class(gc_ref): is_python_class = '__module__' in gc_ref or '__package__' in gc_ref is_cython_class = '__new__' in gc_ref return is_python_class or is_cython_class - for ref in gc.get_referrers(self): - if is_class(ref): + search_for = self if (self.unbound is None) else self.unbound + for ref in gc.get_referrers(search_for): + if is_class(ref) and ref is not self.__dict__: ref_copy = copy.copy(ref) for key, val in ref_copy.iteritems(): - if val is self: + if val is search_for: return key raise AttributeError("The name of this deprecated function can not be determined") @@ -387,9 +391,27 @@ def __get__(self, inst, cls = None): sage: obj = cls() sage: obj.old_meth.instance is obj True + + :trac:`19125`:: + + sage: from sage.misc.superseded import deprecated_function_alias + sage: class A: + ....: def __init__(self, x): + ....: self.x = x + ....: def f(self, y): + ....: return self.x+y + ....: g = deprecated_function_alias(42, f) + sage: a1 = A(1) + sage: a2 = A(2) + sage: a1.g(a2.g(0)) + doctest:...: DeprecationWarning: g is deprecated. Please use f instead. + See http://trac.sagemath.org/42 for details. + 3 + sage: a1.f(a2.f(0)) + 3 + """ - self.instance = inst - return self + return self if (inst is None) else DeprecatedFunctionAlias(self.trac_number, self.func, self.__module__, instance = inst, unbound = self) def deprecated_function_alias(trac_number, func): diff --git a/src/sage/misc/table.py b/src/sage/misc/table.py index 719c9668a91..521b60714e3 100644 --- a/src/sage/misc/table.py +++ b/src/sage/misc/table.py @@ -10,6 +10,7 @@ - John H. Palmieri (2012-11) """ +from cStringIO import StringIO from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method @@ -165,10 +166,14 @@ class table(SageObject): | 4 | 5 | 60 | +-----+---+----+ - To print HTML, use either ``table(...)._html_()`` or ``html(table(...))``:: + To generate HTML you should use ``html(table(...))`` but that + doesn't work :trac:`18292`; A workaround is :: - sage: html(table([["$x$", "$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]], header_row=True, frame=True)) - + sage: output = table([["$x$", "$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]], + ....: header_row=True, frame=True)._html_() + sage: type(output) + + sage: print(output)
@@ -195,7 +200,6 @@ class table(SageObject):
- It is an error to specify both ``rows`` and ``columns``:: @@ -228,7 +232,11 @@ class table(SageObject): TESTS:: - sage: TestSuite(table([["$x$", "$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]], header_row=True, frame=True)).run() + sage: TestSuite(table([["$x$", "$\sin(x)$"]] + + ....: [(x,n(sin(x), digits=2)) for x in [0..3]], + ....: header_row=True, frame=True)).run() + + .. automethod:: _rich_repr_ """ def __init__(self, rows=None, columns=None, header_row=False, header_column=False, frame=False, align='left'): @@ -451,6 +459,23 @@ def _repr_(self): s += self._str_table_row(row, header_row=False) return s.strip("\n") + def _rich_repr_(self, display_manager, **kwds): + """ + Rich Output Magic Method + + See :mod:`sage.repl.rich_output` for details. + + EXAMPLES:: + + sage: from sage.repl.rich_output import get_display_manager + sage: dm = get_display_manager() + sage: t = table([1, 2, 3]) + sage: t._rich_repr_(dm) # the doctest backend does not suppot html + """ + OutputHtml = display_manager.types.OutputHtml + if OutputHtml in display_manager.supported_output(): + return OutputHtml(self._html_()) + def _str_table_row(self, row, header_row=False): r""" String representation of a row of a table. Used by the @@ -528,6 +553,10 @@ def _latex_(self): dollar signs are not automatically added, so tables can include both plain text and mathematics. + OUTPUT: + + String. + EXAMPLES:: sage: from sage.misc.table import table @@ -605,11 +634,16 @@ def _html_(self): visible effect in the Sage notebook, depending on the version of the notebook. + OUTPUT: + + A :class:`~sage.misc.html.HtmlFragment` instance. + EXAMPLES:: sage: T = table([[r'$\sin(x)$', '$x$', 'text'], [1,34342,3], [identity_matrix(2),5,6]]) sage: T._html_() - + + sage: print(T._html_())
@@ -634,9 +668,10 @@ def _html_(self):
- - Note that calling ``html(table(...))`` has the same effect as ``table(...)._html_()`:: + Note that calling ``html(table(...))`` will have the same + effect as ``table(...)._html_()` after the deprecation period + in :trac:`18292`:: sage: T = table([["$x$", "$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]], header_row=True, frame=True) sage: T @@ -651,8 +686,7 @@ def _html_(self): +-----+-----------+ | 3 | 0.14 | +-----+-----------+ - sage: html(T) - + sage: print(html(T))
@@ -679,7 +713,6 @@ def _html_(self):
- """ import types from itertools import cycle @@ -689,41 +722,52 @@ def _html_(self): frame = 'border="1"' else: frame = '' - + s = StringIO() if len(rows) > 0: - # If the table has < 100 rows, don't truncate the output in the notebook - if len(rows) <= 100: - print("\n
\n\n".format(frame)) - else: - print("\n
\n
\n".format(frame)) - + s.writelines([ + # If the table has < 100 rows, don't truncate the output in the notebook + '
\n' if len(rows) <= 100 else '
' , + '
\n'.format(frame), + '\n', + ]) # First row: if header_row: - print("") - self._html_table_row(rows[0], header=header_row) - print("") + s.write('\n') + self._html_table_row(s, rows[0], header=header_row) + s.write('\n') rows = rows[1:] # Other rows: for row_class, row in zip(cycle(["row-a", "row-b"]), rows): - print("".format(row_class)) - self._html_table_row(row, header=False) - print("") - print("\n
\n
\n") - - def _html_table_row(self, row, header=False): + s.write('\n'.format(row_class)) + self._html_table_row(s, row, header=False) + s.write('\n') + s.write('\n\n') + from sage.misc.html import HtmlFragment + return HtmlFragment(s.getvalue()) + + def _html_table_row(self, file, row, header=False): r""" - Print the items of a list as one row of an HTML table. Used by - the :meth:`_html_` method. + Write table row + + Helper method used by the :meth:`_html_` method. INPUTS: - - ``row`` - a list with the same number of entries as each row + - ``file`` -- file-like object. The table row data will be + written to it. + + - ``row`` -- a list with the same number of entries as each row of the table. - - ``header`` (default False) - if True, treat this as a header - row, using ```` instead of ````. - Strings get printed verbatim unless they seem to be LaTeX + - ``header`` -- bool (default False). If True, treat this as a + header row, using ```` instead of ````. + + OUTPUT: + + This method returns nothing. All output is written to ``file``. + + Strings are written verbatim unless they seem to be LaTeX code, in which case they are enclosed in a ``script`` tag appropriate for MathJax. Sage objects are printed using their LaTeX representations. @@ -731,7 +775,10 @@ def _html_table_row(self, row, header=False): EXAMPLES:: sage: T = table([['a', 'bb', 'ccccc'], [10, -12, 0], [1, 2, 3]]) - sage: T._html_table_row(['a', 2, '$x$']) + sage: import StringIO + sage: s = StringIO.StringIO() + sage: T._html_table_row(s, ['a', 2, '$x$']) + sage: print(s.getvalue()) a @@ -746,27 +793,27 @@ def _html_table_row(self, row, header=False): elif not isinstance(row, (list, tuple)): row = [row] - column_tag = "%s" if header else "%s" + column_tag = "%s\n" if header else "%s\n" if self._options['header_column']: - first_column_tag = "%s" if header else "%s" + first_column_tag = '%s\n' if header else '%s\n' else: first_column_tag = column_tag # First entry of row: entry = row[0] if isinstance(entry, Graphics): - print(first_column_tag % entry.show(linkmode = True)) + file.write(first_column_tag % entry.show(linkmode = True)) elif isinstance(entry, str): - print(first_column_tag % math_parse(entry)) + file.write(first_column_tag % math_parse(entry)) else: - print(first_column_tag % ('' % latex(entry))) + file.write(first_column_tag % ('' % latex(entry))) # Other entries: for column in xrange(1,len(row)): if isinstance(row[column], Graphics): - print(column_tag % row[column].show(linkmode = True)) + file.write(column_tag % row[column].show(linkmode = True)) elif isinstance(row[column], str): - print(column_tag % math_parse(row[column])) + file.write(column_tag % math_parse(row[column])) else: - print(column_tag % ('' % latex(row[column]))) + file.write(column_tag % ('' % latex(row[column]))) diff --git a/src/sage/misc/temporary_file.py b/src/sage/misc/temporary_file.py index 6424a738c0d..57bbdf0041a 100644 --- a/src/sage/misc/temporary_file.py +++ b/src/sage/misc/temporary_file.py @@ -6,7 +6,7 @@ - Volker Braun, Jeroen Demeyer (2012-10-18): move these functions here from sage/misc/misc.py and make them secure, see :trac:`13579`. -- Jeroen Demeyer (2013-03-17): add class:`atomic_write`, +- Jeroen Demeyer (2013-03-17): add :class:`atomic_write`, see :trac:`14292`. """ diff --git a/src/sage/modular/arithgroup/arithgroup_element.pyx b/src/sage/modular/arithgroup/arithgroup_element.pyx index 3402a75d254..46021d08082 100644 --- a/src/sage/modular/arithgroup/arithgroup_element.pyx +++ b/src/sage/modular/arithgroup/arithgroup_element.pyx @@ -137,19 +137,19 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): yield self.__x[1,1] def __repr__(self): - """ - Return the string representation of self. + r""" + Return the string representation of ``self``. EXAMPLES:: sage: Gamma1(5)([6,1,5,1]).__repr__() '[6 1]\n[5 1]' """ - return "%s"%self.__x + return "%s" % self.__x def _latex_(self): - """ - Return latex representation of self. + r""" + Return latex representation of ``self``. EXAMPLES:: diff --git a/src/sage/modular/arithgroup/arithgroup_generic.py b/src/sage/modular/arithgroup/arithgroup_generic.py index d91e912770f..aa88edc54c8 100644 --- a/src/sage/modular/arithgroup/arithgroup_generic.py +++ b/src/sage/modular/arithgroup/arithgroup_generic.py @@ -1315,6 +1315,7 @@ def sturm_bound(self, weight=2): - ``weight`` - an integer `\geq 2` (default: 2) EXAMPLES:: + sage: Gamma0(11).sturm_bound(2) 2 sage: Gamma0(389).sturm_bound(2) diff --git a/src/sage/modular/arithgroup/arithgroup_perm.py b/src/sage/modular/arithgroup/arithgroup_perm.py index fbd61acb09f..e4a98cc247b 100644 --- a/src/sage/modular/arithgroup/arithgroup_perm.py +++ b/src/sage/modular/arithgroup/arithgroup_perm.py @@ -1350,9 +1350,9 @@ def is_congruence(self): generalized level (if `G` is odd). Then: - if `N` is odd, `G` is congruence if and only if the relation - + .. math:: - + (L R^{-1} L)^2 = (R^2 L^{1/2})^3 holds, where `1/2` is understood as the multiplicative inverse of 2 diff --git a/src/sage/modular/arithgroup/congroup_gamma.py b/src/sage/modular/arithgroup/congroup_gamma.py index 7787298ffcc..fd82443a00c 100644 --- a/src/sage/modular/arithgroup/congroup_gamma.py +++ b/src/sage/modular/arithgroup/congroup_gamma.py @@ -127,7 +127,8 @@ def index(self): \prod_{\substack{p \mid N \\ \text{$p$ prime}}}\left(p^{3e}-p^{3e-2}\right). - EXAMPLE:: + EXAMPLES:: + sage: [Gamma(n).index() for n in [1..19]] [1, 6, 24, 48, 120, 144, 336, 384, 648, 720, 1320, 1152, 2184, 2016, 2880, 3072, 4896, 3888, 6840] sage: Gamma(32041).index() diff --git a/src/sage/modular/arithgroup/congroup_gamma0.py b/src/sage/modular/arithgroup/congroup_gamma0.py index 6d046af9c8b..079ac2b8c45 100644 --- a/src/sage/modular/arithgroup/congroup_gamma0.py +++ b/src/sage/modular/arithgroup/congroup_gamma0.py @@ -224,7 +224,7 @@ def divisor_subgroups(self): Return the subgroups of SL2Z of the form Gamma0(M) that contain this subgroup, i.e. those for M a divisor of N. - EXAMPLE:: + EXAMPLES:: sage: Gamma0(24).divisor_subgroups() [Modular Group SL(2,Z), @@ -542,19 +542,19 @@ def nu3(self): def index(self): r""" - Return the index of self in the full modular group. This is given by + Return the index of self in the full modular group. + + This is given by .. math:: N \prod_{\substack{p \mid N \\ \text{$p$ prime}}}\left(1 + \frac{1}{p}\right). - EXAMPLE:: + EXAMPLES:: + sage: [Gamma0(n).index() for n in [1..19]] [1, 3, 4, 6, 6, 12, 8, 12, 12, 18, 12, 24, 14, 24, 24, 24, 18, 36, 20] sage: Gamma0(32041).index() 32220 """ return prod([p**e + p**(e-1) for (p,e) in self.level().factor()]) - - - diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 09487983532..388e48eec28 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -109,9 +109,11 @@ def _normalize_H(H, level): Normalize representatives for a given subgroup H of the units modulo level. - NOTE: This function does *not* make any attempt to find a minimal - set of generators for H. It simply normalizes the inputs for use - in hashing. + .. NOTE:: + + This function does *not* make any attempt to find a minimal + set of generators for H. It simply normalizes the inputs for use + in hashing. EXAMPLES:: @@ -137,8 +139,8 @@ def _normalize_H(H, level): H.remove(1) return H -class GammaH_class(CongruenceSubgroup): +class GammaH_class(CongruenceSubgroup): r""" The congruence subgroup `\Gamma_H(N)` for some subgroup `H \trianglelefteq (\ZZ / N\ZZ)^\times`, which is the subgroup of `{\rm @@ -147,7 +149,7 @@ class GammaH_class(CongruenceSubgroup): TESTS: - We test calculation of various invariants of the group: :: + We test calculation of various invariants of the group:: sage: GammaH(33,[2]).projective_index() 96 @@ -164,7 +166,7 @@ class GammaH_class(CongruenceSubgroup): sage: Gamma1(23).genus() 12 - We calculate the dimensions of some modular forms spaces: :: + We calculate the dimensions of some modular forms spaces:: sage: GammaH(33,[2]).dimension_cusp_forms(2) 5 @@ -175,7 +177,7 @@ class GammaH_class(CongruenceSubgroup): sage: GammaH(32079, [21676]).dimension_cusp_forms(20) 180266112 - We can sometimes show that there are no weight 1 cusp forms: :: + We can sometimes show that there are no weight 1 cusp forms:: sage: GammaH(20, [9]).dimension_cusp_forms(1) 0 @@ -183,8 +185,9 @@ class GammaH_class(CongruenceSubgroup): def __init__(self, level, H, Hlist=None): r""" - The congruence subgroup `\Gamma_H(N)`. The subgroup H - must be input as a list. + The congruence subgroup `\Gamma_H(N)`. + + The subgroup `H` must be given as a list. EXAMPLES:: @@ -483,13 +486,16 @@ def _coset_reduction_data_first_coord(G): of the reduction step (the first coordinate). INPUT: - G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). + + G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). OUTPUT: - A list v such that - v[u] = (min(u*h: h in H), - gcd(u,N) , - an h such that h*u = min(u*h: h in H)). + + A list v such that + + v[u] = (min(u*h: h in H), + gcd(u,N) , + an h such that h*u = min(u*h: h in H)). EXAMPLES:: @@ -558,11 +564,13 @@ def _coset_reduction_data_second_coord(G): of the reduction step (the second coordinate). INPUT: - self + + self OUTPUT: - a dictionary v with keys the divisors of N such that v[d] - is the subgroup {h in H : h = 1 (mod N/d)}. + + a dictionary v with keys the divisors of N such that v[d] + is the subgroup {h in H : h = 1 (mod N/d)}. EXAMPLES:: @@ -624,19 +632,24 @@ def _reduce_coset(self, uu, vv): Compute a canonical form for a given Manin symbol. INPUT: + Two integers (uu,vv) that define an element of `(Z/NZ)^2`. - uu -- an integer - vv -- an integer + + - uu -- an integer + - vv -- an integer OUTPUT: - pair of integers that are equivalent to (uu,vv). - NOTE: We do *not* require that gcd(uu,vv,N) = 1. If the gcd is - not 1, we return (0,0). + pair of integers that are equivalent to (uu,vv). + + .. NOTE:: + + We do *not* require that gcd(uu,vv,N) = 1. If the gcd is + not 1, we return (0,0). EXAMPLES: - An example at level 9.:: + An example at level 9:: sage: G = GammaH(9,[7]); G Congruence Subgroup Gamma_H(9) with H generated by [7] @@ -735,6 +748,7 @@ def reduce_cusp(self, c): def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. + Returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. @@ -745,9 +759,13 @@ def _reduce_cusp(self, c): Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index 4aa40d4c29a..7c436629471 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -29,8 +29,14 @@ #include #include "farey.hpp" -#include "sage/modular/arithgroup/farey_symbol.h" +// The following functions are defined in farey_symbols.h, but direct inclusion +// of this file breaks compilation on cygwin, see trac #13336. +extern "C" long convert_to_long(PyObject *); +extern "C" PyObject *convert_to_Integer(mpz_class); +extern "C" PyObject *convert_to_rational(mpq_class); +extern "C" PyObject *convert_to_cusp(mpq_class); +extern "C" PyObject *convert_to_SL2Z(SL2Z); using namespace std; diff --git a/src/sage/modular/arithgroup/farey.pxd b/src/sage/modular/arithgroup/farey.pxd index 74a5b85d5e3..307c6e275bb 100644 --- a/src/sage/modular/arithgroup/farey.pxd +++ b/src/sage/modular/arithgroup/farey.pxd @@ -1,30 +1,14 @@ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# 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/ #***************************************************************************** -cdef extern from 'gmpxx.h': - cdef cppclass mpz_class: - mpz_class() - mpz_class(int i) - mpz_class(mpz_t z) - mpz_class(mpz_class) - mpz_t get_mpz_t() - mpz_class operator%(mpz_class, mpz_class) - cdef cppclass mpq_class: - mpq_class() - mpz_t get_num_mpz_t() - mpz_t get_den_mpz_t() +from sage.libs.gmpxx cimport * cdef extern from "sage/modular/arithgroup/sl2z.hpp": cppclass cpp_SL2Z "SL2Z": diff --git a/src/sage/modular/arithgroup/farey_symbol.pyx b/src/sage/modular/arithgroup/farey_symbol.pyx index 46994a1777e..153e879c0ed 100644 --- a/src/sage/modular/arithgroup/farey_symbol.pyx +++ b/src/sage/modular/arithgroup/farey_symbol.pyx @@ -1,3 +1,4 @@ +# distutils: language = c++ r""" Farey Symbol for arithmetic subgroups of `{\rm PSL}_2(\ZZ)` @@ -8,26 +9,21 @@ AUTHORS: based on the *KFarey* package by Chris Kurth. Implemented as C++ module for speed. """ + #***************************************************************************** # Copyright (C) 2011 Hartmut Monien # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** include 'sage/ext/interrupt.pxi' include 'sage/ext/cdefs.pxi' -include "farey.pxd" - +from .farey cimport * import sage.rings.arith from sage.rings.all import CC, RR from sage.rings.integer cimport Integer diff --git a/src/sage/modular/cusps_nf.py b/src/sage/modular/cusps_nf.py index 780d20916d2..b8f9ccdd105 100644 --- a/src/sage/modular/cusps_nf.py +++ b/src/sage/modular/cusps_nf.py @@ -638,6 +638,7 @@ def _repr_(self): String representation of this cusp. EXAMPLES:: + sage: k. = NumberField(x^2 + 1) sage: c = NFCusp(k, a, 2); c Cusp [a: 2] of Number Field in a with defining polynomial x^2 + 1 @@ -799,9 +800,8 @@ def __cmp__(self, right): sage: k. = NumberField(x^3 + x + 1) sage: kCusps = NFCusps(k) - Comparing with infinity: + Comparing with infinity:: - :: sage: c = kCusps((a,2)) sage: d = kCusps(oo) sage: c < d @@ -809,9 +809,7 @@ def __cmp__(self, right): sage: kCusps(oo) < d False - Comparison as elements of the number field: - - :: + Comparison as elements of the number field:: sage: kCusps(2/3) < kCusps(5/2) False diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index bc97fb98b2f..6bb3cf40cc9 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -69,6 +69,7 @@ from sage.categories.map import Map from sage.rings.rational_field import is_RationalField from sage.rings.complex_field import is_ComplexField +from sage.rings.qqbar import is_AlgebraicField from sage.rings.ring import is_Ring from sage.misc.cachefunc import cached_method @@ -874,7 +875,6 @@ def galois_orbit(self, sort=True): v.sort() return v - def gauss_sum(self, a=1): r""" Return a Gauss sum associated to this Dirichlet character. @@ -883,16 +883,13 @@ def gauss_sum(self, a=1): .. math:: - g_a(\chi) = \sum_{r \in \ZZ/m\ZZ} \chi(r)\,\zeta^{ar}, - + g_a(\chi) = \sum_{r \in \ZZ/m\ZZ} \chi(r)\,\zeta^{ar}, - where `m` is the modulus of `\chi` and - `\zeta` is a primitive `m^{th}` root of unity, i.e., - `\zeta` is ``self.parent().zeta()``. + where `m` is the modulus of `\chi` and `\zeta` is a primitive + `m^{th}` root of unity. FACTS: If the modulus is a prime `p` and the character is - nontrivial, then the Gauss sum has absolute value - `\sqrt{p}`. + nontrivial, then the Gauss sum has absolute value `\sqrt{p}`. CACHING: Computed Gauss sums are *not* cached with this character. @@ -915,23 +912,43 @@ def gauss_sum(self, a=1): -zeta156^46 + zeta156^45 + zeta156^42 + zeta156^41 + 2*zeta156^40 + zeta156^37 - zeta156^36 - zeta156^34 - zeta156^33 - zeta156^31 + 2*zeta156^30 + zeta156^28 - zeta156^24 - zeta156^22 + zeta156^21 + zeta156^20 - zeta156^19 + zeta156^18 - zeta156^16 - zeta156^15 - 2*zeta156^14 - zeta156^10 + zeta156^8 + zeta156^7 + zeta156^6 + zeta156^5 - zeta156^4 - zeta156^2 - 1 sage: factor(norm(e.gauss_sum())) 13^24 + + TESTS: + + The field of algebraic numbers is supported (:trac:`19056`):: + + sage: G = DirichletGroup(7, QQbar) + sage: G[1].gauss_sum() + -2.440133358345538? + 1.022618791871794?*I + + Check that :trac:`19060` is fixed:: + + sage: K. = CyclotomicField(8) + sage: G = DirichletGroup(13, K) + sage: chi = G([z^2]) + sage: chi.gauss_sum() + zeta52^22 + zeta52^21 + zeta52^19 - zeta52^16 + zeta52^15 + zeta52^14 + zeta52^12 - zeta52^11 - zeta52^10 - zeta52^7 - zeta52^5 + zeta52^4 """ G = self.parent() K = G.base_ring() - if is_ComplexField(K): - return self.gauss_sum_numerical() - if not (number_field.is_CyclotomicField(K) or is_RationalField(K)): - raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, or a complex field.") - g = 0 + chi = self m = G.modulus() - L = rings.CyclotomicField(arith.lcm(m,G.zeta_order())) - zeta = L.gen(0) - n = zeta.multiplicative_order() - zeta = zeta ** (n // m) - if a != 1: - zeta = zeta**a - z = 1 - for c in self.values()[1:]: + if is_ComplexField(K): + return self.gauss_sum_numerical(a=a) + elif is_AlgebraicField(K): + L = K + zeta = L.zeta(m) + elif number_field.is_CyclotomicField(K) or is_RationalField(K): + chi = chi.minimize_base_ring() + n = arith.lcm(m, G.zeta_order()) + L = rings.CyclotomicField(n) + zeta = L.gen(0) ** (n // m) + else: + raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, QQbar, or a complex field") + zeta = zeta ** a + g = L.zero() + z = L.one() + for c in chi.values()[1:]: z *= zeta g += L(c)*z return g @@ -943,22 +960,18 @@ def gauss_sum_numerical(self, prec=53, a=1): INPUT: + - ``prec`` -- integer (default: 53), *bits* of precision - - ``prec`` - integer (default: 53), *bits* of precision - - - ``a`` - integer, as for gauss_sum. - + - ``a`` -- integer, as for :meth:`gauss_sum`. The Gauss sum associated to `\chi` is .. math:: - g_a(\chi) = \sum_{r \in \ZZ/m\ZZ} \chi(r)\,\zeta^{ar}, - + g_a(\chi) = \sum_{r \in \ZZ/m\ZZ} \chi(r)\,\zeta^{ar}, - where `m` is the modulus of `\chi` and - `\zeta` is a primitive `m^{th}` root of unity, i.e., - `\zeta` is ``self.parent().zeta()``. + where `m` is the modulus of `\chi` and `\zeta` is a primitive + `m^{th}` root of unity. EXAMPLES:: @@ -986,26 +999,32 @@ def gauss_sum_numerical(self, prec=53, a=1): 3.60555127546... sage: sqrt(13.0) 3.60555127546399 + + TESTS: + + The field of algebraic numbers is supported (:trac:`19056`):: + + sage: G = DirichletGroup(7, QQbar) + sage: G[1].gauss_sum_numerical() + -2.44013335834554 + 1.02261879187179*I """ G = self.parent() K = G.base_ring() - if not (number_field.is_CyclotomicField(K) or is_RationalField(K) - or is_ComplexField(K)): - raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, or a complex field.") - if is_ComplexField(K): phi = lambda t : t CC = K - else: + elif is_AlgebraicField(K): + from sage.rings.complex_field import ComplexField + CC = ComplexField(prec) + phi = CC.coerce_map_from(K) + elif number_field.is_CyclotomicField(K) or is_RationalField(K): phi = K.complex_embedding(prec) CC = phi.codomain() - - g = 0 - m = G.modulus() - zeta = CC.zeta(m) - if a != 1: - zeta = zeta**a - z = 1 + else: + raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, QQbar, or a complex field") + zeta = CC.zeta(G.modulus()) ** a + g = CC.zero() + z = CC.one() for c in self.values()[1:]: z *= zeta g += phi(c)*z diff --git a/src/sage/modular/hecke/module.py b/src/sage/modular/hecke/module.py index 294e18957b7..81b61fde28e 100644 --- a/src/sage/modular/hecke/module.py +++ b/src/sage/modular/hecke/module.py @@ -979,6 +979,7 @@ def decomposition(self, bound=None, anemic=True, height_guess=1, sort_by_basis = ] TESTS:: + sage: M = ModularSymbols(1000,2,sign=1).new_subspace().cuspidal_subspace() sage: M.decomposition(3, sort_by_basis = True) [ diff --git a/src/sage/modular/hecke/submodule.py b/src/sage/modular/hecke/submodule.py index 4f1e0408352..eadc3815c5a 100644 --- a/src/sage/modular/hecke/submodule.py +++ b/src/sage/modular/hecke/submodule.py @@ -166,6 +166,7 @@ def __cmp__(self, other): other, and -1 otherwise. EXAMPLES:: + sage: M = ModularSymbols(12,6) sage: S = sage.modular.hecke.submodule.HeckeSubmodule(M, M.cuspidal_submodule().free_module()) sage: T = sage.modular.hecke.submodule.HeckeSubmodule(M, M.new_submodule().free_module()) diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index b85a6573941..afd26301a54 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -466,17 +466,19 @@ def q_expansion(self, prec=None): O(q^1) sage: f.q_expansion(0) O(q^0) + sage: f.q_expansion(-1) + Traceback (most recent call last): + ... + ValueError: prec (= -1) must be non-negative """ if prec is None: prec = self.parent().prec() prec = rings.Integer(prec) - if prec < 0: - raise ValueError("prec (=%s) must be at least 0"%prec) try: current_prec, f = self.__q_expansion except AttributeError: current_prec = 0 - f = self.parent()._q_expansion_ring()(0, -1) + f = self.parent()._q_expansion_ring()(0, 0) if current_prec == prec: return f diff --git a/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx b/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx index 17b9be9610b..2eb5b37faf7 100644 --- a/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx +++ b/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx @@ -1,5 +1,5 @@ -include "../../ext/cdefs.pxi" -include "../../ext/interrupt.pxi" +include "sage/ext/stdsage.pxi" +include "sage/ext/interrupt.pxi" from sage.rings.fast_arith cimport arith_llong cdef arith_llong arith = arith_llong() @@ -7,14 +7,8 @@ cdef arith_llong arith = arith_llong() from sage.rings.all import ZZ, PowerSeriesRing from sage.rings.arith import kronecker_symbol -cdef extern from *: - double ceil(double) - double floor(double) - double sqrt(double) - void* calloc(long, int) - void* malloc(long) - void free(void*) - void memcpy(void* dst, void* src, long s) +from libc.math cimport ceil, floor, sqrt +from libc.string cimport memcpy cpdef to_series(L, var): @@ -74,7 +68,7 @@ def bqf_theta_series(Q, long bound, var=None): a, b, c = Q cdef long* terms = bqf_theta_series_c(NULL, bound, a, b, c) L = [terms[i] for i from 0 <= i <= bound] - free(terms) + sage_free(terms) return to_series(L, var) @@ -88,9 +82,7 @@ cdef long* bqf_theta_series_c(long* terms, long bound, long a, long b, long c) e raise ValueError("Not positive definite.") xmax = ceil(2 * sqrt((c * bound) / (4 * a * c - b * b))) if terms == NULL: - terms = calloc((1 + bound), sizeof(long)) - if terms == NULL: - raise MemoryError + terms = check_calloc(1 + bound, sizeof(long)) sig_on() for x from -xmax <= x <= xmax: @@ -156,12 +148,15 @@ def gross_zagier_L_series(an_list, Q, long N, long u, var=None): cdef long D = b * b - 4 * a * c cdef long i, m, n, me, j cdef long* con_terms = bqf_theta_series_c(NULL, bound - 1, a, b, c) - cdef long* terms = malloc(sizeof(long) * bound) - if terms == NULL: - free(con_terms) - raise MemoryError + cdef long* terms = NULL + try: + terms = check_allocarray(bound, sizeof(long)) + except MemoryError: + sage_free(con_terms) + raise i = 0 for an in an_list: + sig_check() con_terms[i] = con_terms[i] / u * an i += 1 sig_on() @@ -176,7 +171,7 @@ def gross_zagier_L_series(an_list, Q, long N, long u, var=None): j += m * m n += 1 sig_off() - L = [terms[i] for i from 0 <= i < bound - 1] - free(con_terms) - free(terms) + L = [terms[i] for i in range(bound - 1)] + sage_free(con_terms) + sage_free(terms) return to_series(L, var) diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index 4fbfa0f15a7..c6b9a09a129 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -962,14 +962,14 @@ def _has_natural_inclusion_map_to(self, right): return f.parent()(e) == f raise NotImplementedError - def has_coerce_map_from_impl(self, from_par): + def _coerce_map_from_(self, from_par): """ Code to make ModularFormsSpace work well with coercion framework. EXAMPLES:: sage: M = ModularForms(22,2) - sage: M.has_coerce_map_from_impl(M.cuspidal_subspace()) + sage: M.has_coerce_map_from(M.cuspidal_subspace()) True sage: M.has_coerce_map_from(ModularForms(22,4)) False diff --git a/src/sage/modular/modform_hecketriangle/abstract_ring.py b/src/sage/modular/modform_hecketriangle/abstract_ring.py index 6577462c05f..f5ba198020a 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_ring.py +++ b/src/sage/modular/modform_hecketriangle/abstract_ring.py @@ -18,13 +18,11 @@ from sage.rings.all import FractionField, PolynomialRing, PowerSeriesRing, ZZ, QQ, infinity from sage.algebras.free_algebra import FreeAlgebra -from sage.rings.arith import bernoulli, sigma from sage.structure.parent import Parent from sage.misc.cachefunc import cached_method -from hecke_triangle_groups import HeckeTriangleGroup -from constructor import FormsRing, FormsSpace, rational_type +from constructor import FormsRing, FormsSpace from series_constructor import MFSeriesConstructor @@ -132,9 +130,9 @@ def _latex_(self): from sage.misc.latex import latex return "\\mathcal{{ {} }}_{{n={}}}({})".format(self._analytic_type.latex_space_name(), self._group.n(), latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced/converted into this forms ring. + Return ``el`` coerced/converted into this forms ring. EXAMPLES:: @@ -152,14 +150,36 @@ def _element_constructor_(self, x): False sage: MR(el).parent() == MR True + + sage: el = MR.Delta().full_reduce() + sage: MRinf = ModularFormsRing(n=infinity) + sage: MRinf(el) + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3)/4096 + sage: el.parent() + CuspForms(n=3, k=12, ep=1) over Integer Ring + sage: MRinf(el).parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - x = self._rat_field(x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = self._rat_field(el._rat) + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) else: - x = self._rat_field(x) - return self.element_class(self, x) + el = self._rat_field(el) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -171,6 +191,8 @@ def _coerce_map_from_(self, S): sage: MR1 = QuasiWeakModularFormsRing(base_ring=CC) sage: MR2 = ModularFormsRing() sage: MR3 = CuspFormsRing() + sage: MR4 = ModularFormsRing(n=infinity) + sage: MR5 = ModularFormsRing(n=4) sage: MR3.has_coerce_map_from(MR2) False sage: MR1.has_coerce_map_from(MR2) @@ -181,6 +203,10 @@ def _coerce_map_from_(self, S): False sage: MR1.has_coerce_map_from(ZZ) True + sage: MR4.has_coerce_map_from(MR2) + True + sage: MR4.has_coerce_map_from(MR5) + False sage: from sage.modular.modform_hecketriangle.space import ModularForms, CuspForms sage: MF2 = ModularForms(k=6, ep=-1) @@ -189,14 +215,19 @@ def _coerce_map_from_(self, S): True sage: MR2.has_coerce_map_from(MF3) True + sage: MR4.has_coerce_map_from(MF2) + True """ from space import FormsSpace_abstract + from functors import _common_subgroup if ( isinstance(S, FormsRing_abstract)\ - and self._group == S._group\ + and self._group == _common_subgroup(self._group, S._group)\ and self._analytic_type >= S._analytic_type\ and self.base_ring().has_coerce_map_from(S.base_ring()) ): return True + elif isinstance(S, FormsRing_abstract): + return False elif isinstance(S, FormsSpace_abstract): raise RuntimeError( "This case should not occur." ) # return self._coerce_map_from_(S.graded_ring()) @@ -747,11 +778,11 @@ def diff_alg(self): sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: ModularFormsRing().diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} sage: from sage.modular.modform_hecketriangle.space import CuspForms sage: CuspForms(k=12, base_ring=AA).diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} """ # We only use two operators for now which do not involve 'd', so for performance diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 0cc399ad054..76f28b569b4 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -122,9 +122,9 @@ def _latex_(self): from sage.misc.latex import latex return "{}_{{ n={} }}({},\ {})({})".format(self._analytic_type.latex_space_name(), self._group.n(), self._weight, self._ep, latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced into this forms space. + Return ``el`` coerced into this forms space. EXAMPLES:: @@ -141,6 +141,14 @@ def _element_constructor_(self, x): sage: MF(Delta).parent() == MF True + sage: E2 = MF.E2() + sage: e2 = QuasiWeakModularForms(n=infinity, k=2, ep=-1)(E2) + sage: e2 + 1 - 24*q^2 - 72*q^4 + O(q^5) + sage: e2.parent() + QuasiWeakModularForms(n=+Infinity, k=2, ep=-1) over Integer Ring + sage: e2.as_ring_element() + (-f_i + 3*E2)/2 sage: MF(x^3) 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5) sage: MF(x^3).parent() == MF @@ -226,35 +234,49 @@ def _element_constructor_(self, x): """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - return self.element_class(self, x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = el._rat + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) + return self.element_class(self, el) # This assumes that the series corresponds to a _weakly # holomorphic_ (quasi) form. It also assumes that the form is # holomorphic at -1 for n=infinity (this assumption however # can be changed in construct_form # resp. construct_quasi_form)) - P = parent(x) + P = parent(el) if is_LaurentSeriesRing(P) or is_PowerSeriesRing(P): if (self.is_modular()): - return self.construct_form(x) + return self.construct_form(el) else: - return self.construct_quasi_form(x) - if is_FreeModuleElement(x) and (self.module() is P or self.ambient_module() is P): - return self.element_from_ambient_coordinates(x) - if (not self.is_ambient()) and (isinstance(x, list) or isinstance(x, tuple) or is_FreeModuleElement(x)) and len(x) == self.rank(): + return self.construct_quasi_form(el) + if is_FreeModuleElement(el) and (self.module() is P or self.ambient_module() is P): + return self.element_from_ambient_coordinates(el) + if (not self.is_ambient()) and (isinstance(el, list) or isinstance(el, tuple) or is_FreeModuleElement(el)) and len(el) == self.rank(): try: - return self.element_from_coordinates(x) + return self.element_from_coordinates(el) except (ArithmeticError, TypeError): pass if self.ambient_module() and self.ambient_module().has_coerce_map_from(P): - return self.element_from_ambient_coordinates(self.ambient_module()(x)) - if (isinstance(x,list) or isinstance(x, tuple)) and len(x) == self.degree(): + return self.element_from_ambient_coordinates(self.ambient_module()(el)) + if (isinstance(el,list) or isinstance(el, tuple)) and len(el) == self.degree(): try: - return self.element_from_ambient_coordinates(x) + return self.element_from_ambient_coordinates(el) except (ArithmeticError, TypeError): pass - return self.element_class(self, x) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -268,6 +290,8 @@ def _coerce_map_from_(self, S): sage: MF3 = ModularForms(n=4, k=24, ep=-1) sage: MF4 = CuspForms(n=4, k=0, ep=1) sage: MF5 = ZeroForm(n=4, k=10, ep=-1) + sage: MF6 = QuasiWeakModularForms(n=3, k=24, ep=1) + sage: MF7 = QuasiWeakModularForms(n=infinity, k=24, ep=1) sage: subspace1 = MF3.subspace([MF3.gen(0), MF3.gen(1)]) sage: subspace2 = MF3.subspace([MF3.gen(2)]) sage: subspace3 = MF3.subspace([MF3.gen(0), MF3.gen(0)+MF3.gen(2)]) @@ -282,6 +306,10 @@ def _coerce_map_from_(self, S): False sage: MF1.has_coerce_map_from(ZZ) True + sage: MF7.has_coerce_map_from(MF6) + True + sage: MF7.has_coerce_map_from(MF2) + False sage: MF3.has_coerce_map_from(subspace1) True sage: subspace1.has_coerce_map_from(MF3) diff --git a/src/sage/modular/modform_hecketriangle/functors.py b/src/sage/modular/modform_hecketriangle/functors.py index fc3001c58ac..f4e7c3e0c2b 100644 --- a/src/sage/modular/modform_hecketriangle/functors.py +++ b/src/sage/modular/modform_hecketriangle/functors.py @@ -92,6 +92,35 @@ def _get_base_ring(ring, var_name="d"): return base_ring +def _common_subgroup(group1, group2): + r""" + Return a common (Hecke triangle) subgroup of both given groups + ``group1`` and ``group2`` if it exists. Otherwise return ``None``. + + EXAMPLES:: + + sage: from sage.modular.modform_hecketriangle.functors import _common_subgroup + sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup + sage: _common_subgroup(HeckeTriangleGroup(n=3), HeckeTriangleGroup(n=infinity)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=infinity), HeckeTriangleGroup(n=3)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=infinity)) is None + True + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=4)) + Hecke triangle group for n = 4 + """ + + if group1 == group2: + return group1 + elif (group1.n() == 3) and (group2.n() == infinity): + return group2 + elif (group1.n() == infinity) and (group2.n() == 3): + return group1 + else: + return None + + def ConstantFormsSpaceFunctor(group): r""" Construction functor for the space of constant forms. @@ -206,7 +235,7 @@ def __call__(self, R): else: return ambient_space - def __str__(self): + def _repr_(self): r""" Return the string representation of ``self``. @@ -388,7 +417,7 @@ def __call__(self, R): merged_functor = self.merge(ConstantFormsSpaceFunctor(self._group)) return merged_functor(R) - def __str__(self): + def _repr_(self): r""" Return the string representation of ``self``. @@ -458,19 +487,21 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None analytic_type = self._analytic_type + other._analytic_type if (self._k == other._k) and (self._ep == other._ep): - return FormsSpaceFunctor(analytic_type, self._group, self._k, self._ep) + return FormsSpaceFunctor(analytic_type, group, self._k, self._ep) else: - return FormsRingFunctor(analytic_type, self._group, True) + return FormsRingFunctor(analytic_type, group, True) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" @@ -577,7 +608,7 @@ def __call__(self, R): merged_functor = self.merge(ConstantFormsSpaceFunctor(self._group)) return merged_functor(R) - def __str__(self): + def _repr_(self): r""" Return the string representation of ``self``. @@ -647,17 +678,19 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom & other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index c1a05835135..f6e669d6a63 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -713,6 +713,12 @@ def _add_(self,other): 2 - 32*q + 736*q^2 - 896*q^3 + 6368*q^4 + O(q^5) sage: (MF.E4() + MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() + MF.E4()*MF.E6() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E4^2*f_i)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ return self.parent()(self._rat+other._rat) @@ -763,6 +769,12 @@ def _sub_(self,other): 64*q - 512*q^2 + 1792*q^3 - 4096*q^4 + O(q^5) sage: (MF.E4() - MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() - MF.E4() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 - 4096*E4)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ #reduce at the end? See example "sage: ((E4+E6)-E6).parent()" @@ -838,6 +850,12 @@ def _mul_(self,other): q + 8*q^2 + 12*q^3 - 64*q^4 + O(q^5) sage: (MF.E4()*MF.f_inf()).parent() ModularForms(n=+Infinity, k=8, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).E2()*MF.E6() + sage: el + 1 - 8*q - 272*q^2 - 1760*q^3 - 2560*q^4 + O(q^5) + sage: el.parent() + QuasiModularForms(n=+Infinity, k=8, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat*other._rat) @@ -918,6 +936,12 @@ def _div_(self,other): 1/2 - 4*q - 236*q^2 - 2128*q^3 + 49428*q^4 + O(q^5) sage: (MF.f_i()/(MF.E4() + MF.f_i()^2)).parent() MeromorphicModularForms(n=+Infinity, k=-2, ep=-1) over Integer Ring + + sage: el = ModularForms(n=3).E2()/MF.E2() + sage: el + 1 + 8*q + 48*q^2 + 480*q^3 + 4448*q^4 + O(q^5) + sage: el.parent() + QuasiMeromorphicModularForms(n=+Infinity, k=0, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat/other._rat) @@ -1722,6 +1746,7 @@ def q_expansion_vector(self, min_exp = None, max_exp = None, prec = None, **kwar determined by the corresponding Laurent series coefficients. EXAMPLES:: + sage: from sage.modular.modform_hecketriangle.graded_ring import WeakModularFormsRing sage: f = WeakModularFormsRing(red_hom=True).j_inv()^3 sage: f.q_expansion(prec=3) diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py index d879b6a02d3..135ad6032ed 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py @@ -36,7 +36,7 @@ def coerce_AA(p): sage: from sage.modular.modform_hecketriangle.hecke_triangle_group_element import coerce_AA sage: p = (791264*AA(2*cos(pi/8))^2 - 463492).sqrt() sage: AA(p)._exact_field() - Number Field in a with defining polynomial y^16 ... with a in ... + Number Field in a with defining polynomial y^8 ... with a in ... sage: coerce_AA(p)._exact_field() Number Field in a with defining polynomial y^4 - 1910*y^2 - 3924*y + 681058 with a in 39.710518724...? """ diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py index 732f7fea7ba..0a2b54c0a6d 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py @@ -149,6 +149,7 @@ def element_repr_method(self, method=None): ``block``: Same as ``conj`` but the conjugation matrix is specified as well. EXAMPLES:: + sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(5) sage: G.element_repr_method() @@ -541,7 +542,7 @@ def dvalue(self): sage: HeckeTriangleGroup(6).dvalue() 1/108 sage: HeckeTriangleGroup(10).dvalue() - e^(2*euler_gamma - pi*sec(1/10*pi) + psi(4/5) + psi(7/10)) + e^(2*euler_gamma - 2*pi/sqrt(1/2*sqrt(5) + 5/2) + psi(4/5) + psi(7/10)) sage: HeckeTriangleGroup(infinity).dvalue() 1/64 """ diff --git a/src/sage/modular/modform_hecketriangle/readme.py b/src/sage/modular/modform_hecketriangle/readme.py index 3f39d16b300..418c2a175db 100644 --- a/src/sage/modular/modform_hecketriangle/readme.py +++ b/src/sage/modular/modform_hecketriangle/readme.py @@ -2,8 +2,8 @@ Overview of Hecke triangle groups and modular forms for Hecke triangle groups AUTHORS: -- Jonas Jermann (2013): initial version +- Jonas Jermann (2013): initial version Hecke triangle groups and elements: @@ -460,7 +460,7 @@ - **Block decomposition of elements:** - For each group element a very specfic conjugacy representative + For each group element a very specific conjugacy representative can be obtained. For hyperbolic and parabolic elements the representative is a product ``V(j)``-matrices. They all have non-negative trace and the number of factors is called @@ -1093,7 +1093,7 @@ - **Theta subgroup:** The Hecke triangle group corresponding to ``n=infinity`` is also completely supported. In particular the (special) behavior around - the cusp ``-1`` is considered and can be specfied. + the cusp ``-1`` is considered and can be specified. EXAMPLES:: @@ -1146,6 +1146,15 @@ sage: MF.q_basis(m=-1, order_1=-1, min_exp=-1) q^-1 - 203528/7*q^5 + O(q^6) + Elements with respect to the full group are automatically coerced + to elements of the Theta subgroup if necessary:: + + sage: el = QuasiMeromorphicModularFormsRing(n=3).Delta().full_reduce() + E2 + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E2)/4096 + sage: el.parent() + QuasiModularFormsRing(n=+Infinity) over Integer Ring + - **Determine exact coefficients from numerical ones:** There is some experimental support for replacing numerical coefficients with diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 5ef072af57a..d59a2e6475e 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -531,7 +531,7 @@ def _action_on_modular_symbols(self, g): INPUT: - ``g`` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers + `g` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers defining a `2\times2` integer matrix. OUTPUT: @@ -539,10 +539,10 @@ def _action_on_modular_symbols(self, g): (matrix) The matrix of the action of `g` on this Modular Symbol space, with respect to the standard basis. - .. note:: + .. NOTE:: - Use _matrix_of_operator_on_modular_symbols for more general - operators. + Use ``_matrix_of_operator_on_modular_symbols`` for more general + operators. EXAMPLES:: @@ -810,7 +810,7 @@ def modular_symbol_sum(self, x, check=True): The sum `\sum_{i=0}^{k-2} a_i [ i, \alpha, \beta ]` as an element of this modular symbol space. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(11,4) sage: R.=QQ[] @@ -2787,7 +2787,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(100,2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -2898,19 +2898,15 @@ def boundary_space(self): def _hecke_image_of_ith_basis_vector(self, n, i): """ Return `T_n(e_i)`, where `e_i` is the - `i`th basis vector of this ambient space. + `i` th basis vector of this ambient space. INPUT: - - - ``n`` - an integer which should be prime. - + - ``n`` -- an integer which should be prime. OUTPUT: - - - ``modular symbol`` - element of this ambient space - + - ``modular symbol`` -- element of this ambient space EXAMPLES:: @@ -3100,7 +3096,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(Gamma1(22),2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -3807,16 +3803,13 @@ def _hecke_images(self, i, v): INPUT: + - ``i`` -- nonnegative integer - - ``i`` - nonnegative integer - - - ``v`` - a list of positive integer - + - ``v`` -- a list of positive integer OUTPUT: - - - ``matrix`` - whose rows are the Hecke images + - ``matrix`` -- whose rows are the Hecke images EXAMPLES:: @@ -3829,8 +3822,6 @@ def _hecke_images(self, i, v): [ 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0] [ 0 1 0 2 0 -1 1 1 0 0 0 0 0 0 0] [ 0 1 1 -1 -1 0 -1 1 1 0 1 2 0 -2 2] - - """ if self.weight() != 2: raise NotImplementedError("hecke images only implemented when the weight is 2") diff --git a/src/sage/modular/modsym/manin_symbol.pyx b/src/sage/modular/modsym/manin_symbol.pyx index 6e4e6351c8b..d4da00430ad 100644 --- a/src/sage/modular/modsym/manin_symbol.pyx +++ b/src/sage/modular/modsym/manin_symbol.pyx @@ -436,13 +436,13 @@ def _print_polypart(i, j): EXAMPLES:: - sage: from sage.modular.modsym.manin_symbol import _print_polypart - sage: _print_polypart(2,3) - 'X^2*Y^3' - sage: _print_polypart(2,0) - 'X^2' - sage: _print_polypart(0,1) - 'Y' + sage: from sage.modular.modsym.manin_symbol import _print_polypart + sage: _print_polypart(2,3) + 'X^2*Y^3' + sage: _print_polypart(2,0) + 'X^2' + sage: _print_polypart(0,1) + 'Y' """ if i > 1: xpart = "X^%s"%i diff --git a/src/sage/modular/modsym/modular_symbols.py b/src/sage/modular/modsym/modular_symbols.py index b13170bf0b4..96aec36e734 100644 --- a/src/sage/modular/modsym/modular_symbols.py +++ b/src/sage/modular/modsym/modular_symbols.py @@ -117,7 +117,7 @@ def __getitem__(self, j): return [self.__alpha, self.__beta][j] def _latex_(self): - """ + r""" Return Latex representation of this modular symbol. EXAMPLES:: diff --git a/src/sage/modular/modsym/p1list.pyx b/src/sage/modular/modsym/p1list.pyx index 1ab36b2a6b5..20acee773f0 100644 --- a/src/sage/modular/modsym/p1list.pyx +++ b/src/sage/modular/modsym/p1list.pyx @@ -764,8 +764,8 @@ cdef class P1List: return sage.modular.modsym.p1list._make_p1list, (self.__N, ) def __getitem__(self, n): - """ - Standard indexing/slicing function for the class P1List. + r""" + Standard indexing/slicing function for the class ``P1List``. EXAMPLES:: diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index 7bf69c3fa9d..b1a844a79e4 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -2318,15 +2318,16 @@ def _matrix_of_galois_action(self, t, P): INPUT: - - `t` -- integer - - `P` -- list of cusps + - `t` -- integer - EXAMPLES:: + - `P` -- list of cusps + + EXAMPLES: We compute the matrix of the element of the Galois group associated to 5 and 31 for level 32. In the first case the Galois action is trivial, and in the second it is - nontrivial.:: + nontrivial. :: sage: M = ModularSymbols(32) sage: P = [c for c in Gamma0(32).cusps() if not c.is_infinity()] diff --git a/src/sage/modular/overconvergent/genus0.py b/src/sage/modular/overconvergent/genus0.py index 15994f99036..0b73c8f0c9c 100644 --- a/src/sage/modular/overconvergent/genus0.py +++ b/src/sage/modular/overconvergent/genus0.py @@ -742,6 +742,7 @@ def _coerce_from_ocmf(self, f): obviously nonsense. EXAMPLES:: + sage: M = OverconvergentModularForms(3, 0, 1/2) sage: MM = M.base_extend(Qp(3)) sage: R. = Qp(3)[[]]; f = MM(q + O(q^2)); f diff --git a/src/sage/modular/overconvergent/weightspace.py b/src/sage/modular/overconvergent/weightspace.py index 3ee2366b00b..a1f65f55d25 100644 --- a/src/sage/modular/overconvergent/weightspace.py +++ b/src/sage/modular/overconvergent/weightspace.py @@ -443,7 +443,7 @@ def one_over_Lvalue(self): sage: pAdicWeightSpace(11)(3).one_over_Lvalue() Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero sage: pAdicWeightSpace(11)(0).one_over_Lvalue() 0 sage: type(_) @@ -566,6 +566,20 @@ def chi(self): """ return self._chi + def __hash__(self): + r""" + TESTS:: + + sage: w = pAdicWeightSpace(23)(12, DirichletGroup(23, QQ).0) + sage: hash(w) + -2363716619315244394 # 64-bit + 470225558 # 32-bit + """ + if self._chi.is_trivial(): + return hash(self._k) + else: + return hash( (self._k,self._chi.modulus(),self._chi) ) + def _repr_(self): r""" String representation of self. diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 3fe7309097d..6aed0417dac 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -910,6 +910,7 @@ def _compute_hecke_matrix_directly(self, n, B=None, sparse=False): - ``sparse`` -- bool (default: False); whether matrix should be sparse EXAMPLES:: + sage: B = BrandtModule(37) sage: t = B._compute_hecke_matrix_directly(2); t [1 1 1] @@ -1225,6 +1226,7 @@ def _brandt_series_vectors(self, prec=None): Return Brandt series coefficient vectors out to precision *at least* prec. EXAMPLES:: + sage: B = BrandtModule(37, use_cache=False) sage: B._brandt_series_vectors(5) [[(1/2, 1, 1, 2, 1), (1/2, 0, 1, 1, 3), (1/2, 0, 1, 1, 3)], diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index 2b4f43bb14f..749f67a3991 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -343,8 +343,28 @@ def vector(self): try: return self.__vector except AttributeError: self.__vector = self.parent().coordinate_vector(self, reduce=True) + self.__vector.set_immutable() return self.__vector + def __hash__(self): + r""" + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: hash(x) + 3713081631933328131 # 64-bit + 1298787075 # 32-bit + + sage: A = AdditiveAbelianGroup([3]) + sage: hash(A.an_element()) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self.vector()) + def _vector_(self, base_ring=None): """ Support for conversion to vectors. @@ -367,10 +387,21 @@ def _vector_(self, base_ring=None): (1, 3) sage: vector(CDF, x) (1.0, 3.0) + + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: vector(x).is_mutable() + True + sage: vector(CDF,x).is_mutable() + True """ v = self.vector() if base_ring is None or v.base_ring() is base_ring: - return v + return v.__copy__() else: return v.change_ring(base_ring) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 85db5e993d1..c688167722d 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -654,7 +654,8 @@ class FreeModule_generic(Module): def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None, category=None): """ - Create the free module of given rank over the given base_ring. + Create the free module of given rank ``rank`` over the given base + ring ``base_ring``. INPUT: @@ -672,10 +673,11 @@ def __init__(self, base_ring, rank, degree, sparse=False, - ``category`` -- category (default: None) If ``base_ring`` is a field, then the default category is the - category of vector spaces over that field; otherwise it is the - category of free modules over that ring. In addition, the - category is intersected with the category of finite enumerated - sets if the ring is finite or the rank is 0. + category of finite-dimensional vector spaces over that field; + otherwise it is the category of finite-dimensional free modules + over that ring. In addition, the category is intersected with the + category of finite enumerated sets if the ring is finite or the + rank is 0. EXAMPLES:: @@ -683,17 +685,22 @@ def __init__(self, base_ring, rank, degree, sparse=False, Ambient free module of rank 3 over the integral domain Multivariate Polynomial Ring in x0, x1, x2 over Rational Field sage: FreeModule(GF(7),3).category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids and quotients of semigroups) sage: V = QQ^4; V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) sage: V = GF(5)**20; V.category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids + and quotients of semigroups) sage: FreeModule(ZZ,3).category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets + and metric spaces) sage: (QQ^0).category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) TESTS:: @@ -734,7 +741,7 @@ def __init__(self, base_ring, rank, degree, sparse=False, if category is None: from sage.categories.all import FreeModules - category = FreeModules(base_ring.category()) + category = FreeModules(base_ring.category()).FiniteDimensional() super(FreeModule_generic, self).__init__(base_ring, category=category) self.__coordinate_ring = coordinate_ring @@ -903,7 +910,7 @@ def _an_element_(self): def _element_constructor_(self, x, coerce=True, copy=True, check=True): r""" - Create an element of this free module from x. + Create an element of this free module from ``x``. The ``coerce`` and ``copy`` arguments are passed on to the underlying element constructor. If @@ -968,7 +975,7 @@ def _element_constructor_(self, x, coerce=True, copy=True, check=True): def is_submodule(self, other): """ - Return True if self is a submodule of other. + Return ``True`` if ``self`` is a submodule of ``other``. EXAMPLES:: @@ -1002,11 +1009,9 @@ def is_submodule(self, other): sage: M.is_submodule(N) True - Since basis() is not implemented in general, submodule testing does - not work for all PID's. However, trivial cases are already used - (and useful) for coercion, e.g. - - :: + Since :meth:`basis` is not implemented in general, submodule + testing does not work for all PID's. However, trivial cases are + already used (and useful) for coercion, e.g. :: sage: QQ(1/2) * vector(ZZ['x']['y'],[1,2,3,4]) (1/2, 1, 3/2, 2) @@ -3067,10 +3072,10 @@ def _Hom_(self, Y, category): sage: type(H) sage: H - Set of Morphisms from Vector space of dimension 2 over - Rational Field to Ambient free module of rank 3 over the - principal ideal domain Integer Ring in Category of vector - spaces with basis over quotient fields + Set of Morphisms from Vector space of dimension 2 over Rational Field + to Ambient free module of rank 3 over the principal ideal domain Integer Ring + in Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ if Y.base_ring().is_field(): import vector_space_homspace diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 0fb54c234ae..6a63c1f3427 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -2077,23 +2077,33 @@ cdef class FreeModuleElement(Vector): # abstract base class def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14]) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] - In some cases when copy=False, we get back a dangerous reference:: + In some cases, when ``copy=False``, we get back a dangerous + reference:: sage: v = vector({0:5, 2:3/7}, sparse=True) sage: v.dict(copy=False) @@ -2110,6 +2120,8 @@ cdef class FreeModuleElement(Vector): # abstract base class e[i] = c return e + monomial_coefficients = dict + ############################# # Plotting ############################# @@ -2538,7 +2550,7 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: u.cross_product(v) Traceback (most recent call last): ... - ArithmeticError: Cross product only defined for vectors of length three or seven, not (7 and 3) + TypeError: Cross product only defined for vectors of length three or seven, not (7 and 3) REFERENCES: @@ -2568,7 +2580,92 @@ cdef class FreeModuleElement(Vector): # abstract base class l[0]*r[2] - l[2]*r[0] + l[1]*r[5] - l[5]*r[1] + l[3]*r[4] - l[4]*r[3]]) else: - raise ArithmeticError("Cross product only defined for vectors of length three or seven, not (%s and %s)"%(len(l),len(r))) + raise TypeError("Cross product only defined for vectors of length three or seven, not (%s and %s)"%(len(l),len(r))) + + def cross_product_matrix(self): + r""" + Return the matrix which describes a cross product + between ``self`` and some other vector. + + This operation is sometimes written using the `hat operator`_. + It is only defined for vectors of length 3 or 7. + For a vector `v` the cross product matrix `\hat{v}` + is a matrix which satisfies `\hat{v} \cdot w = v \times w` + and also `w \cdot \hat{v} = w \times v` for all vectors `w`. + The basis vectors are assumed to be orthonormal. + + .. _hat operator: http://en.wikipedia.org/wiki/Hat_operator#Cross_product + + OUTPUT: + + The cross product matrix of this vector. + + EXAMPLES:: + + sage: v = vector([1, 2, 3]) + sage: vh = v.cross_product_matrix() + sage: vh + [ 0 -3 2] + [ 3 0 -1] + [-2 1 0] + sage: w = random_vector(3, x=1, y=100) + sage: vh*w == v.cross_product(w) + True + sage: w*vh == w.cross_product(v) + True + sage: vh.is_alternating() + True + + TESTS:: + + sage: F = GF(previous_prime(2^32)) + sage: v = random_vector(F, 3) + sage: w = random_vector(F, 3) + sage: vh = v.cross_product_matrix() + sage: vh*w == v.cross_product(w) + True + sage: w*vh == w.cross_product(v) + True + sage: vh.is_alternating() + True + sage: v = random_vector(F, 7) + sage: w = random_vector(F, 7) + sage: vh = v.cross_product_matrix() + sage: vh*w == v.cross_product(w) + True + sage: w*vh == w.cross_product(v) + True + sage: vh.is_alternating() + True + sage: random_vector(F, 5).cross_product_matrix() + Traceback (most recent call last): + ... + TypeError: Cross product only defined for vectors of length three or seven, not 5 + """ + from sage.matrix.matrix_space import MatrixSpace + rank = self.parent().rank() + R = self.base_ring() + zero = R.zero() + if rank == 3: + MS = MatrixSpace(R, rank, rank, sparse=self.is_sparse()) + s = self.list(copy=False) + return MS([ + [ zero, -s[2], s[1]], + [ s[2], zero, -s[0]], + [-s[1], s[0], zero]]) + elif rank == 7: + MS = MatrixSpace(R, rank, rank, sparse=self.is_sparse()) + s = self.list(copy=False) + return MS([ + [ zero, -s[3], -s[6], s[1], -s[5], s[4], s[2]], + [ s[3], zero, -s[4], -s[0], s[2], -s[6], s[5]], + [ s[6], s[4], zero, -s[5], -s[1], s[3], -s[0]], + [-s[1], s[0], s[5], zero, -s[6], -s[2], s[4]], + [ s[5], -s[2], s[1], s[6], zero, -s[0], -s[3]], + [-s[4], s[6], -s[3], s[2], s[0], zero, -s[1]], + [-s[2], -s[5], s[0], -s[4], s[3], s[1], zero]]) + else: + raise TypeError("Cross product only defined for vectors of length three or seven, not {}".format(rank)) def pairwise_product(self, right): """ @@ -4495,8 +4592,21 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): sage: w = vector(R, [], sparse=True) sage: parent(v._dot_product_coerce_(w)) Univariate Polynomial Ring in x over Real Double Field + + TESTS: + + Check that :trac:`19377` is fixed:: + + sage: w = vector(ZZ, (1,2,3), sparse=False) + sage: v = vector(ZZ, (1,2,3), sparse=True) + sage: v._dot_product_coerce_(w) + 14 """ - cdef dict e = (right)._entries + cdef dict e + try: + e = (right)._entries + except TypeError: + e = right.dict() z = left.base_ring().zero() if left.base_ring() is not right.base_ring(): z *= right.base_ring().zero() @@ -4736,30 +4846,41 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14], sparse=True) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] """ if copy: return dict(self._entries) else: return self._entries + monomial_coefficients = dict + def list(self, copy=True): """ - Return list of elements of self. + Return list of elements of ``self``. INPUT: diff --git a/src/sage/modules/free_module_homspace.py b/src/sage/modules/free_module_homspace.py index 5d674f304e9..b455db03d87 100644 --- a/src/sage/modules/free_module_homspace.py +++ b/src/sage/modules/free_module_homspace.py @@ -27,11 +27,12 @@ sage: V2 = FreeModule(IntegerRing(),2) sage: H = Hom(V3,V2) sage: H - Set of Morphisms from Ambient free module of rank 3 over the - principal ideal domain Integer Ring to Ambient free module - of rank 2 over the principal ideal domain Integer Ring in - Category of modules with basis over (euclidean domains and - infinite enumerated sets) + Set of Morphisms from Ambient free module of rank 3 over + the principal ideal domain Integer Ring + to Ambient free module of rank 2 + over the principal ideal domain Integer Ring + in Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: B = H.basis() sage: len(B) 6 diff --git a/src/sage/modules/vector_callable_symbolic_dense.py b/src/sage/modules/vector_callable_symbolic_dense.py index 09e4e27e47a..186d6a9e0f3 100644 --- a/src/sage/modules/vector_callable_symbolic_dense.py +++ b/src/sage/modules/vector_callable_symbolic_dense.py @@ -62,6 +62,7 @@ def _repr_(self): Returns the string representation of the vector EXAMPLES:: + sage: f(u,v,w) = (2*u+v,u-w,w^2+u) sage: f (u, v, w) |--> (2*u + v, u - w, w^2 + u) @@ -84,6 +85,7 @@ def _latex_(self): Returns the latex representation of the vector EXAMPLES:: + sage: f(u,v,w) = (2*u+v,u-w,w^2+u) sage: f (u, v, w) |--> (2*u + v, u - w, w^2 + u) diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index 6de2afe134d..17f1d211f2f 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -215,7 +215,7 @@ def __init__(self, domain, codomain=None, category=None, affine=False): # The category infrastructure handles this automatically for # parents with a single element class. But for now we still # need to do it by hand here, since H may have many different - # element classes:: + # element classes if not issubclass(self.__class__, H._abstract_element_class): self.__class__ = H.__make_element_class__(self.__class__) @@ -380,10 +380,11 @@ def __call__(self, *args): x = args[self._position] assert(x.parent() is self.domain()) + mc = x.monomial_coefficients(copy=False) if self._is_module_with_basis_over_same_base_ring: - return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in args[self._position] ) + return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in mc.iteritems() ) else: - return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in args[self._position]), self._zero) + return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in mc.iteritems() ), self._zero) # As per the specs of Map, we should in fact implement _call_. # However we currently need to abuse Map.__call__ (which strict diff --git a/src/sage/monoids/automatic_semigroup.py b/src/sage/monoids/automatic_semigroup.py index da5cf127bc6..21a159e487f 100644 --- a/src/sage/monoids/automatic_semigroup.py +++ b/src/sage/monoids/automatic_semigroup.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- """ +Automatic Semigroups + Semigroups defined by generators living in an ambient semigroup and represented by an automaton. AUTHORS: @@ -133,11 +135,11 @@ class AutomaticSemigroup(UniqueRepresentation, Parent): ([], [2], (2, 'left')), ([], [2], (2, 'right'))] sage: map(sorted, M.j_classes()) - [[[], [2]], [[1, 1], [1]]] + [[[1], [1, 1]], [[], [2]]] sage: M.j_classes_of_idempotents() - [[[]], [[1, 1]]] + [[[1, 1]], [[]]] sage: M.j_transversal_of_idempotents() - [[], [1, 1]] + [[1, 1], []] sage: map(attrcall('pseudo_order'), M.list()) [[1, 0], [3, 1], [2, 0], [2, 1]] diff --git a/src/sage/monoids/free_abelian_monoid_element.py b/src/sage/monoids/free_abelian_monoid_element.py index c63ea6638a3..fa48ad89b50 100644 --- a/src/sage/monoids/free_abelian_monoid_element.py +++ b/src/sage/monoids/free_abelian_monoid_element.py @@ -1,5 +1,5 @@ """ -Abelian monoid elements +Abelian Monoid Elements AUTHORS: diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index ec1a7a08c7c..9872b16c232 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -1,5 +1,5 @@ """ -Monoid Elements +Elements of Free Monoids AUTHORS: @@ -80,6 +80,23 @@ def __init__(self, F, x, check=True): # TODO: should have some other checks here... raise TypeError("Argument x (= %s) is of the wrong type."%x) + def __hash__(self): + r""" + TESTS:: + + sage: R. = FreeMonoid(2) + sage: hash(x) + 1914282862589934403 # 64-bit + 139098947 # 32-bit + sage: hash(y) + 2996819001369607946 # 64-bit + 13025034 # 32-bit + sage: hash(x*y) + 7114093379175463612 # 64-bit + 2092317372 # 32-bit + """ + return hash(tuple(self._element_list)) + def __iter__(self): """ Returns an iterator which yields tuples of variable and exponent. diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index ff0163622fa..e4772f34910 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -20,6 +20,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import MonoidElement from sage.structure.indexed_generators import IndexedGenerators +from sage.structure.sage_object import op_EQ, op_NE, py_rich_to_bool from sage.combinat.dict_addition import dict_addition from sage.categories.monoids import Monoids @@ -200,11 +201,11 @@ def __iter__(self): """ return ((self.parent().gen(index), exp) for (index,exp) in self._sorted_items()) - def __eq__(self, y): - """ - Check equality. + def _richcmp_(self, other, op): + r""" + Comparisons - EXAMPLES:: + TESTS:: sage: F = FreeMonoid(index_set=ZZ) sage: a,b,c,d,e = [F.gen(i) for i in range(5)] @@ -214,28 +215,6 @@ def __eq__(self, y): True sage: a*b*c^3*b*d == (a*b*c)*(c^2*b*d) True - - sage: F = FreeAbelianMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] - sage: a == a - True - sage: a*e == e*a - True - sage: a*b*c^3*b*d == a*d*(b^2*c^2)*c - True - """ - if not isinstance(y, IndexedMonoidElement): - return y == 1 and not self._monomial - return y.parent() is self.parent() and y._monomial == self._monomial - - def __ne__(self, y): - """ - Check inequality. - - EXAMPLES:: - - sage: F = FreeMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] sage: a != b True sage: a*b != b*a @@ -243,23 +222,6 @@ def __ne__(self, y): sage: a*b*c^3*b*d != (a*b*c)*(c^2*b*d) False - sage: F = FreeAbelianMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] - sage: a != b - True - sage: a*b != a*a - True - sage: a*b*c^3*b*d != a*d*(b^2*c^2)*c - False - """ - return not self.__eq__(y) - - def __lt__(self, y): - """ - Check less than. - - EXAMPLES:: - sage: F = FreeMonoid(index_set=ZZ) sage: a,b,c,d,e = [F.gen(i) for i in range(5)] sage: a < b @@ -270,55 +232,37 @@ def __lt__(self, y): False sage: a^2*b < a*b*b True - """ - if not isinstance(y, IndexedMonoidElement): - return False - return self.to_word_list() < y.to_word_list() - - def __gt__(self, y): - """ - Check less than. - - EXAMPLES:: - - sage: F = FreeMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] sage: b > a True sage: a*b > b*a False sage: a*b > a*a True - """ - if not isinstance(y, IndexedMonoidElement): - return False - return y.__lt__(self) - - def __le__(self, y): - """ - Check less than or equals. - - EXAMPLES:: - - sage: F = FreeMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] sage: a*b <= b*a True - """ - return self.__eq__(y) or self.__lt__(y) - - def __ge__(self, y): - """ - Check greater than or equals. - - EXAMPLES:: - - sage: F = FreeMonoid(index_set=ZZ) - sage: a,b,c,d,e = [F.gen(i) for i in range(5)] sage: a*b <= b*a True + + sage: FA = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [FA.gen(i) for i in range(5)] + sage: a == a + True + sage: a*e == e*a + True + sage: a*b*c^3*b*d == a*d*(b^2*c^2)*c + True + sage: a != b + True + sage: a*b != a*a + True + sage: a*b*c^3*b*d != a*d*(b^2*c^2)*c + False """ - return self.__eq__(y) or self.__gt__(y) + if op == op_EQ: + return self._monomial == other._monomial + elif op == op_NE: + return self._monomial != other._monomial + return py_rich_to_bool(op, cmp(self.to_word_list(), other.to_word_list())) def support(self): """ @@ -427,6 +371,19 @@ def __init__(self, F, x): """ IndexedMonoidElement.__init__(self, F, tuple(map(tuple, x))) + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: hash(F ([(1,2),(0,1)]) ) + 2401565693828035651 # 64-bit + 1164080195 # 32-bit + sage: hash(F ([(0,2),(1,1)]) ) + -3359280905493236379 # 64-bit + -1890405019 # 32-bit + """ + return hash(self._monomial) def _sorted_items(self): """ @@ -542,6 +499,20 @@ def _sorted_items(self): pass return v + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: hash( F([(0,1), (2,2)]) ) + 8087055352805725849 # 64-bit + 250091161 # 32-bit + sage: hash( F([(2,1)]) ) + 5118585357534560720 # 64-bit + 1683816912 # 32-bit + """ + return hash(frozenset(self._monomial.items())) + def _mul_(self, other): """ Multiply ``self`` by ``other``. diff --git a/src/sage/numerical/backends/coin_backend.pxd b/src/sage/numerical/backends/coin_backend.pxd index c1f3ff64d2b..4c9c8dde3f9 100644 --- a/src/sage/numerical/backends/coin_backend.pxd +++ b/src/sage/numerical/backends/coin_backend.pxd @@ -118,6 +118,17 @@ cdef extern from "coin/OsiSolverInterface.hpp": # miscellaneous double getInfinity() + # info about basis status + void getBasisStatus(int * cstat, int * rstat) + int setBasisStatus(int * cstat, int * rstat) + + # Enable Simplex + void enableSimplexInterface(bool doingPrimal) + + # Get tableau + void getBInvARow(int row, double* z, double * slack) + void getBInvACol(int col, double* vec) + cdef extern from "coin/CbcModel.hpp": cdef cppclass CbcModel: # default constructor @@ -174,3 +185,7 @@ cdef class CoinBackend(GenericBackend): cdef str prob_name cpdef CoinBackend copy(self) + cpdef get_basis_status(self) + cpdef int set_basis_status(self, list cstat, list rstat) except -1 + cpdef get_binva_row(self, int i) + cpdef get_binva_col(self, int j) diff --git a/src/sage/numerical/backends/coin_backend.pyx b/src/sage/numerical/backends/coin_backend.pyx index de31d043eb4..c2d1f45a20c 100644 --- a/src/sage/numerical/backends/coin_backend.pyx +++ b/src/sage/numerical/backends/coin_backend.pyx @@ -446,7 +446,7 @@ cdef class CoinBackend(GenericBackend): """ cdef int i, c cdef int m = len(constraints) - cdef int * rows = sage_malloc(m * sizeof(int *)) + cdef int * rows = check_malloc(m * sizeof(int *)) cdef int nrows = self.si.getNumRows() for i in xrange(m): @@ -701,8 +701,8 @@ cdef class CoinBackend(GenericBackend): """ cdef int n = len(indices) - cdef int * c_indices = sage_malloc(n*sizeof(int)) - cdef double * c_values = sage_malloc(n*sizeof(double)) + cdef int * c_indices = check_malloc(n*sizeof(int)) + cdef double * c_values = check_malloc(n*sizeof(double)) cdef int i for 0<= i< n: @@ -789,7 +789,7 @@ cdef class CoinBackend(GenericBackend): .. NOTE:: - Has no meaning unless ``solve`` has been called before. + Has no meaning unless ``solve`` or ``set_basis_status`` has been called before. EXAMPLE:: @@ -816,7 +816,7 @@ cdef class CoinBackend(GenericBackend): .. NOTE:: - Has no meaning unless ``solve`` has been called before. + Has no meaning unless ``solve`` or ``set_basis_status`` has been called before. EXAMPLE:: @@ -1209,3 +1209,361 @@ cdef class CoinBackend(GenericBackend): p.prob_name = self.prob_name return p + + cpdef get_basis_status(self): + """ + Retrieve status information for column and row variables. + + This method returns status as integer codes: + + * 0: free + * 1: basic + * 2: nonbasic at upper bound + * 3: nonbasic at lower bound + + OUTPUT: + + - ``cstat`` -- The status of the column variables + + - ``rstat`` -- The status of the row variables + + .. NOTE:: + + Logical variables associated with rows are all assumed to have +1 + coefficients, so for a <= constraint the logical will be at lower + bound if the constraint is tight. + + Behaviour is undefined unless ``solve`` or ``set_basis_status`` + has been called before. + + + EXAMPLE:: + + sage: from sage.numerical.backends.generic_backend import get_solver + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(2) # optional - cbc + 1 + sage: p.add_linear_constraint([(0, 2), (1, 3)], None, 6) # optional - cbc + sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) # optional - cbc + sage: p.set_objective([1, 1], 7) # optional - cbc + sage: p.solve() # optional - cbc + 0 + sage: p.get_basis_status() # optional - cbc + ([1, 1], [3, 3]) + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(2) # optional - cbc + 1 + sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) # optional - cbc + sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) # optional - cbc + sage: p.set_objective([1, 1]) # optional - cbc + sage: p.solve() # optional - cbc + 0 + sage: p.get_basis_status() # optional - cbc + ([3, 1], [1, 3]) + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(3) # optional - cbc + 2 + sage: p.add_linear_constraint(zip([0, 1, 2], [8, 6, 1]), None, 48) # optional - cbc + sage: p.add_linear_constraint(zip([0, 1, 2], [4, 2, 1.5]), None, 20) # optional - cbc + sage: p.add_linear_constraint(zip([0, 1, 2], [2, 1.5, 0.5]), None, 8) # optional - cbc + sage: p.set_objective([60, 30, 20]) # optional - cbc + sage: p.solve() # optional - cbc + 0 + sage: p.get_basis_status() # optional - cbc + ([1, 3, 1], [1, 3, 3]) + + + sage: lp = MixedIntegerLinearProgram(solver='Coin') # optional - cbc + sage: v = lp.new_variable(nonnegative=True) # optional - cbc + sage: x,y,z = v[0], v[1], v[2] # optional - cbc + sage: lp.add_constraint(8*x + 6*y + z, max = 48) # optional - cbc + sage: lp.add_constraint(4*x + 2*y + 1.5*z, max = 20) # optional - cbc + sage: lp.add_constraint(2*x + 1.5*y + 0.5*z, max = 8) # optional - cbc + sage: lp.set_objective(60*x + 30*y + 20*z) # optional - cbc + sage: lp_coin = lp.get_backend() # optional - cbc + sage: lp_coin.solve() # optional - cbc + 0 + sage: lp_coin.get_basis_status() # optional - cbc + ([1, 3, 1], [1, 3, 3]) + + """ + cdef int n = self.model.solver().getNumCols() + cdef int m = self.model.solver().getNumRows() + cdef int * c_cstat = check_malloc(n * sizeof(int)) + cdef int * c_rstat = check_malloc(m * sizeof(int)) + cdef list cstat + cdef list rstat + # enableSimplexInterface must be set to use getBasisStatus(). + # See projects.coin-or.org/Osi/ticket/84 + self.model.solver().enableSimplexInterface(True) + try: + sig_on() # To catch SIGABRT + self.model.solver().getBasisStatus(c_cstat, c_rstat) + sig_off() + except RuntimeError: # corresponds to SIGABRT + raise MIPSolverException('CBC : Signal sent, getBasisStatus() fails') + else: + cstat = [c_cstat[j] for j in range(n)] + rstat = [c_rstat[j] for j in range(m)] + return (cstat, rstat) + finally: + sage_free(c_cstat) + sage_free(c_rstat) + + cpdef int set_basis_status(self, list cstat, list rstat) except -1: + """ + Set the status of column and row variables + and update the basis factorization and solution. + + This method returns status as integer codes: + + INPUT: + + - ``cstat`` -- The status of the column variables + + - ``rstat`` -- The status of the row variables + + .. NOTE:: + + Status information should be coded as: + + * 0: free + * 1: basic + * 2: nonbasic at upper bound + * 3: nonbasic at lower bound + + Logical variables associated with rows are all assumed to have +1 + coefficients, so for a <= constraint the logical will be at lower + bound if the constraint is tight. + + OUTPUT: + + Returns 0 if all goes well, 1 if something goes wrong. + + EXAMPLES:: + + sage: from sage.numerical.backends.generic_backend import get_solver + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(2) # optional - cbc + 1 + sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) # optional - cbc + sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) # optional - cbc + sage: p.set_objective([1, 1]) # optional - cbc + + sage: p.set_basis_status([3, 3], [1, 1]) # optional - cbc + 0 + sage: p.get_objective_value() # optional - cbc + 0.0 + sage: p.set_basis_status([1, 3], [1, 3]) # optional - cbc + 0 + sage: p.get_objective_value() # optional - cbc + 2.0 + sage: p.set_basis_status([3, 1], [1, 3]) # optional - cbc + 0 + sage: p.get_objective_value() # optional - cbc + 3.0 + sage: p.get_basis_status() # optional - cbc + ([3, 1], [1, 3]) + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(3) # optional - cbc + 2 + sage: p.add_linear_constraint(zip([0, 1, 2], [8, 6, 1]), None, 48) # optional - cbc + sage: p.add_linear_constraint(zip([0, 1, 2], [4, 2, 1.5]), None, 20) # optional - cbc + sage: p.add_linear_constraint(zip([0, 1, 2], [2, 1.5, 0.5]), None, 8) # optional - cbc + sage: p.set_objective([60, 30, 20]) # optional - cbc + sage: p.set_basis_status([3, 3, 3], [1, 1, 1]) # optional - cbc + 0 + sage: p.get_objective_value() # optional - cbc + 0.0 + sage: p.set_basis_status([1, 3, 3], [1, 1, 3]) # optional - cbc + 0 + sage: p.get_objective_value() # optional - cbc + 240.0 + sage: p.get_basis_status() # optional - cbc + ([1, 3, 3], [1, 1, 3]) + sage: p.set_basis_status([1, 3, 1], [1, 3, 2]) # optional - cbc + 0 + sage: p.get_basis_status() # optional - cbc + ([1, 3, 1], [1, 3, 3]) + sage: p.get_objective_value() # optional - cbc + 280.0 + """ + cdef int n = len(cstat) + cdef int m = len(rstat) + cdef int * c_cstat + cdef int * c_rstat + cdef int result + + # set up the model + cdef OsiSolverInterface * si = self.si + + cdef CbcModel * model + cdef int old_logLevel = self.model.logLevel() + + model = new CbcModel(si[0]) + del self.model + self.model = model + + #we immediately commit to the new model so that the user has access + #to it even when something goes wrong. + + model.setLogLevel(old_logLevel) + + # multithreading + import multiprocessing + model.setNumberThreads(multiprocessing.cpu_count()) + + if n != self.model.solver().getNumCols() or m != self.model.solver().getNumRows(): + raise ValueError("Must provide the status of every column and row variables") + c_cstat = check_malloc(n * sizeof(int)) + c_rstat = check_malloc(m * sizeof(int)) + for i in range(n): + c_cstat[i] = cstat[i] + for i in range(m): + c_rstat[i] = rstat[i] + # enableSimplexInterface must be set to use getBasisStatus(). + # See projects.coin-or.org/Osi/ticket/84 + self.model.solver().enableSimplexInterface(True) + try: + sig_on() # To catch SIGABRT + result = self.model.solver().setBasisStatus(c_cstat, c_rstat) + sig_off() + except RuntimeError: # corresponds to SIGABRT + raise MIPSolverException('CBC : Signal sent, setBasisStatus() fails') + else: + return result + finally: + sage_free(c_cstat) + sage_free(c_rstat) + + cpdef get_binva_row(self, int i): + """ + Return the i-th row of the tableau and the slacks. + + .. NOTE:: + + Has no meaning unless ``solve`` or ``set_basis_status`` + has been called before. + + EXAMPLES:: + + sage: from sage.numerical.backends.generic_backend import get_solver + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(2) # optional - cbc + 1 + sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) # optional - cbc + sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) # optional - cbc + sage: p.set_objective([1, 1]) # optional - cbc + + sage: p.set_basis_status([3, 3], [1, 1]) # optional - cbc + 0 + sage: p.get_binva_row(0) # optional - cbc + ([2.0, -3.0], [1.0, 0.0]) + sage: p.get_binva_row(1) # optional - cbc + ([3.0, 2.0], [0.0, 1.0]) + + sage: p.set_basis_status([1, 3], [1, 3]) # optional - cbc + 0 + sage: p.get_binva_row(0) # optional - cbc + ([0.0, -4.333333333333333], [1.0, -0.6666666666666666]) + sage: p.get_binva_row(1) # optional - cbc + ([1.0, 0.6666666666666666], [0.0, 0.3333333333333333]) + + sage: p.set_basis_status([3, 1], [1, 3]) # optional - cbc + 0 + sage: p.get_binva_row(0) # optional - cbc + ([6.5, 0.0], [1.0, 1.5]) + sage: p.get_binva_row(1) # optional - cbc + ([1.5, 1.0], [0.0, 0.5]) + + """ + cdef int n = self.model.solver().getNumCols() + cdef int m = self.model.solver().getNumRows() + if i < 0 or i >= m: + raise ValueError("i = %s. The i-th row of the tableau doesn't exist" % i) + + cdef double * c_slack = check_malloc(m * sizeof(double)) + cdef double * c_z = check_malloc(n * sizeof(double)) + cdef list slack + cdef list ithrow + # enableSimplexInterface must be set to use getBasisStatus(). + # See projects.coin-or.org/Osi/ticket/84 + self.model.solver().enableSimplexInterface(True) + try: + sig_on() # To catch SIGABRT + self.model.solver().getBInvARow(i, c_z, c_slack) + sig_off() + except RuntimeError: # corresponds to SIGABRT + raise MIPSolverException('CBC : Signal sent, getBinvARow() fails') + else: + slack = [c_slack[j] for j in range(m)] + ithrow = [c_z[j] for j in range(n)] + return (ithrow, slack) + finally: + sage_free(c_slack) + sage_free(c_z) + + cpdef get_binva_col(self, int j): + """ + Return the j-th column of the tableau. + + EXAMPLES:: + + sage: from sage.numerical.backends.generic_backend import get_solver + + sage: p = get_solver(solver = "Coin") # optional - cbc + sage: p.add_variables(2) # optional - cbc + 1 + sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) # optional - cbc + sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) # optional - cbc + sage: p.set_objective([1, 1]) # optional - cbc + + sage: p.set_basis_status([3, 3], [1, 1]) # optional - cbc + 0 + sage: p.get_binva_col(0) # optional - cbc + [2.0, 3.0] + sage: p.get_binva_col(1) # optional - cbc + [-3.0, 2.0] + + sage: p.set_basis_status([1, 3], [1, 3]) # optional - cbc + 0 + sage: p.get_binva_col(0) # optional - cbc + [-0.0, 1.0] + sage: p.get_binva_col(1) # optional - cbc + [-4.333333333333333, 0.6666666666666666] + + sage: p.set_basis_status([3, 1], [1, 3]) # optional - cbc + 0 + sage: p.get_binva_col(0) # optional - cbc + [6.5, 1.5] + sage: p.get_binva_col(1) # optional - cbc + [-0.0, 1.0] + """ + cdef int n = self.model.solver().getNumCols() + cdef int m = self.model.solver().getNumRows() + if j < 0 or j >= n + m: + # it seems that when n <= j < m+n, + # getBInvACol(j) is getBinvCol(j-n) + raise ValueError("j = %s. The j-th column of the tableau doesn't exist" % j) + + cdef double * c_vec = check_malloc(m * sizeof(double)) + cdef list jthcol + # enableSimplexInterface must be set to use getBasisStatus(). + # See projects.coin-or.org/Osi/ticket/84 + self.model.solver().enableSimplexInterface(True) + try: + sig_on() # To catch SIGABRT + self.model.solver().getBInvACol(j, c_vec) + sig_off() + except RuntimeError: # corresponds to SIGABRT + raise MIPSolverException('CBC : Signal sent, getBinvACol() fails') + else: + jthcol = [c_vec[i] for i in range(m)] + return jthcol + finally: + sage_free(c_vec) diff --git a/src/sage/numerical/backends/cplex_backend.pxd b/src/sage/numerical/backends/cplex_backend.pxd index 0a28b684fdf..27e720fcdaa 100644 --- a/src/sage/numerical/backends/cplex_backend.pxd +++ b/src/sage/numerical/backends/cplex_backend.pxd @@ -9,12 +9,16 @@ from sage.numerical.backends.generic_backend cimport GenericBackend cdef struct c_cpxlp +cdef extern from "stdio.h": + ctypedef struct FILE + FILE * fopen (const char * filename, const char * opentype) cdef class CPLEXBackend(GenericBackend): cdef bint _mixed cdef c_cpxlp * env cdef c_cpxlp * lp cdef current_sol + cdef str _logfilename cpdef CPLEXBackend copy(self) cdef extern from "cplex.h": @@ -92,6 +96,12 @@ cdef extern from "cplex.h": # Get the objective value int CPXgetobjval (c_cpxlp *, c_cpxlp *, double *) + # Get the best objective value (i.e., best lower/upper bound) + int CPXgetbestobjval (c_cpxlp *, c_cpxlp *, double *) + + # Get MIP relative gap + int CPXgetmiprelgap (c_cpxlp *, c_cpxlp *, double *) + # Add columns int CPXnewcols(c_cpxlp * env, c_cpxlp * lp, int, double *, double *, double *, char *, char **) @@ -191,6 +201,9 @@ cdef extern from "cplex.h": # sets the value of a string parameter int CPXsetstrparam(c_cpxlp * env, int paramid, char * value) + # sets the log stream file + int CPXsetlogfile (c_cpxlp * env, FILE * f) + # CONSTANTS int CPX_ON = 1 int CPX_PARAM_SCRIND = 1035 diff --git a/src/sage/numerical/backends/cplex_backend.pyx b/src/sage/numerical/backends/cplex_backend.pyx index 42cd67a57c4..4b8f5f5b32e 100644 --- a/src/sage/numerical/backends/cplex_backend.pyx +++ b/src/sage/numerical/backends/cplex_backend.pyx @@ -27,6 +27,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: + sage: from sage.numerical.mip import MixedIntegerLinearProgram # optional - CPLEX sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX """ @@ -44,6 +45,7 @@ cdef class CPLEXBackend(GenericBackend): self.set_sense(-1) self.obj_constant_term = 0.0 + self._logfilename = '' cpdef int add_variable(self, lower_bound=0.0, upper_bound=None, binary=False, continuous=False, integer=False, obj=0.0, name=None) except -1: """ @@ -72,7 +74,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -161,7 +163,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -229,7 +231,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -266,7 +268,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.is_maximization() # optional - CPLEX True @@ -290,7 +292,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional -- CPLEX sage: p.add_variable() # optional -- CPLEX 0 @@ -325,7 +327,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.problem_name("There once was a french fry") # optional - CPLEX sage: print p.problem_name() # optional - CPLEX @@ -363,7 +365,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(5) # optional - CPLEX 4 @@ -374,7 +376,8 @@ cdef class CPLEXBackend(GenericBackend): Constants in the objective function are respected:: sage: p = MixedIntegerLinearProgram(solver='CPLEX') # optional - CPLEX - sage: x,y = p[0], p[1] # optional - CPLEX + sage: var = p.new_variable(nonnegative=True) # optional - CPLEX + sage: x,y = var[0], var[1] # optional - CPLEX sage: p.add_constraint(2*x + 3*y, max = 6) # optional - CPLEX sage: p.add_constraint(3*x + 2*y, max = 6) # optional - CPLEX sage: p.set_objective(x + y + 7) # optional - CPLEX @@ -411,7 +414,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.set_verbosity(2) # optional - CPLEX @@ -436,7 +439,8 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: sage: p = MixedIntegerLinearProgram(solver='CPLEX')# optional - CPLEX - sage: x,y = p[0], p[1] # optional - CPLEX + sage: var = p.new_variable(nonnegative=True) # optional - CPLEX + sage: x,y = var[0], var[1] # optional - CPLEX sage: p.add_constraint(2*x + 3*y, max = 6) # optional - CPLEX sage: p.add_constraint(3*x + 2*y, max = 6) # optional - CPLEX sage: p.set_objective(x + y + 7) # optional - CPLEX @@ -469,7 +473,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(5) # optional - CPLEX 4 @@ -548,7 +552,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(5) # optional - CPLEX 4 @@ -643,7 +647,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(5) # optional - CPLEX 4 @@ -692,7 +696,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(5) # optional - CPLEX 4 @@ -740,7 +744,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variable() # optional - CPLEX 0 @@ -787,7 +791,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -836,19 +840,29 @@ cdef class CPLEXBackend(GenericBackend): the solution can not be computed for any reason (none exists, or the LP solver was not able to find it, etc...) - EXAMPLE:: + EXAMPLE: - sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CPLEX") # optional - CPLEX - sage: p.add_linear_constraints(5, 0, None) # optional - CPLEX - sage: p.add_col(range(5), range(5)) # optional - CPLEX - sage: p.solve() # optional - CPLEX - 0 - sage: p.objective_coefficient(0,1) # optional - CPLEX - sage: p.solve() # optional - CPLEX + A simple maximization problem:: + + sage: p = MixedIntegerLinearProgram(solver='CPLEX') # optional - CPLEX + sage: x = p.new_variable(integer=True, nonnegative=True) # optional - CPLEX + sage: p.add_constraint(2*x[0] + 3*x[1], max = 6) # optional - CPLEX + sage: p.add_constraint(3*x[0] + 2*x[1], max = 6) # optional - CPLEX + sage: p.set_objective(x[0] + x[1] + 7) # optional - CPLEX + sage: p.solve() # optional - CPLEX + 9.0 + + A problem without feasible solution:: + + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX + sage: p = get_solver(solver = "CPLEX") # optional - CPLEX + sage: p.add_linear_constraints(5, 0, None) # optional - CPLEX + sage: p.add_col(range(5), range(5)) # optional - CPLEX + sage: p.objective_coefficient(0,1) # optional - CPLEX + sage: p.solve() # optional - CPLEX Traceback (most recent call last): ... - MIPSolverException: ... + MIPSolverException: 'CPLEX: The primal has no feasible solution' """ cdef int status cdef int ptype @@ -888,7 +902,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(2) # optional - CPLEX 1 @@ -912,6 +926,89 @@ cdef class CPLEXBackend(GenericBackend): return value + self.obj_constant_term + cpdef best_known_objective_bound(self): + r""" + Return the value of the currently best known bound. + + This method returns the current best upper (resp. lower) bound on the + optimal value of the objective function in a maximization + (resp. minimization) problem. It is equal to the output of + :meth:`~MixedIntegerLinearProgram.get_objective_value` if the MILP found + an optimal solution, but it can differ if it was interrupted manually or + after a time limit (cf :meth:`MixedIntegerLinearProgram.solver_parameter`). + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + When using the ``CPX_PARAM_POPULATELIM`` parameter to get a pool of + solutions, the value can exceed the optimal solution value if + ``CPLEX`` has already solved the model to optimality but continues to + search for additional solutions. + + EXAMPLE:: + + sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX + sage: b = p.new_variable(binary=True) # optional - CPLEX + sage: for u,v in graphs.CycleGraph(5).edges(labels=False): # optional - CPLEX + ....: p.add_constraint(b[u]+b[v]<=1) # optional - CPLEX + sage: p.set_objective(p.sum(b[x] for x in range(5))) # optional - CPLEX + sage: p.solve() # optional - CPLEX + 2.0 + sage: pb = p.get_backend() # optional - CPLEX + sage: pb.get_objective_value() # optional - CPLEX + 2.0 + sage: pb.best_known_objective_bound() # optional - CPLEX + 2.0 + """ + cdef int status + cdef double value + status = CPXgetbestobjval (self.env, self.lp, &value) + check(status) + + return value + self.obj_constant_term + + cpdef get_relative_objective_gap(self): + r""" + Return the relative objective gap of the best known solution. + + For a minimization problem, this value is computed by + `(\texttt{bestinteger} - \texttt{bestobjective}) / (1e-10 + + |\texttt{bestobjective}|)`, where ``bestinteger`` is the value returned + by :meth:`~MixedIntegerLinearProgram.get_objective_value` and + ``bestobjective`` is the value returned by + :meth:`MixedIntegerLinearProgram.best_known_objective_bound`. For a + maximization problem, the value is computed by `(\texttt{bestobjective} + - \texttt{bestinteger}) / (1e-10 + |\texttt{bestobjective}|)`. + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX + sage: b = p.new_variable(binary=True) # optional - CPLEX + sage: for u,v in graphs.CycleGraph(5).edges(labels=False): # optional - CPLEX + ....: p.add_constraint(b[u]+b[v]<=1) # optional - CPLEX + sage: p.set_objective(p.sum(b[x] for x in range(5))) # optional - CPLEX + sage: p.solve() # optional - CPLEX + 2.0 + sage: pb = p.get_backend() # optional - CPLEX + sage: pb.get_objective_value() # optional - CPLEX + 2.0 + sage: pb.get_best_objective_value() # optional - CPLEX + 2.0 + sage: pb.get_relative_objective_gap() # optional - CPLEX + 0.0 + """ + cdef int status + cdef double value + status = CPXgetmiprelgap (self.env, self.lp, &value) + check(status) + + return value + cpdef get_variable_value(self, int variable): r""" Returns the value of a variable given by the solver. @@ -922,7 +1019,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(2) # optional - CPLEX 1 @@ -955,7 +1052,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -973,7 +1070,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.nrows() # optional - CPLEX 0 @@ -994,7 +1091,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_linear_constraints(1, 2, None, names=['Empty constraint 1']) # optional - CPLEX sage: p.row_name(0) # optional - CPLEX @@ -1027,7 +1124,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variable(name='I am a variable') # optional - CPLEX 0 @@ -1060,7 +1157,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -1096,7 +1193,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -1131,7 +1228,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.ncols() # optional - CPLEX 0 @@ -1165,7 +1262,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.is_maximization() # optional - CPLEX True @@ -1190,7 +1287,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variable() # optional - CPLEX 0 @@ -1205,7 +1302,8 @@ cdef class CPLEXBackend(GenericBackend): :trac:`14581`:: sage: P = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX - sage: x = P["x"] # optional - CPLEX + sage: var = P.new_variable(nonnegative=False) # optional - CPLEX + sage: x = var["x"] # optional - CPLEX sage: P.set_max(x, 0) # optional - CPLEX sage: P.get_max(x) # optional - CPLEX 0.0 @@ -1241,7 +1339,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variable() # optional - CPLEX 0 @@ -1256,7 +1354,8 @@ cdef class CPLEXBackend(GenericBackend): :trac:`14581`:: sage: P = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX - sage: x = P["x"] # optional - CPLEX + sage: var = P.new_variable(nonnegative=False) # optional - CPLEX + sage: x = var["x"] # optional - CPLEX sage: P.set_min(x, 5) # optional - CPLEX sage: P.set_min(x, 0) # optional - CPLEX sage: P.get_min(x) # optional - CPLEX @@ -1288,7 +1387,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(2) # optional - CPLEX 1 @@ -1312,7 +1411,7 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.add_variables(2) # optional - CPLEX 1 @@ -1332,9 +1431,9 @@ cdef class CPLEXBackend(GenericBackend): EXAMPLE:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = MixedIntegerLinearProgram(solver = "CPLEX") # optional - CPLEX - sage: b = p.new_variable() # optional - CPLEX + sage: b = p.new_variable(nonnegative=True) # optional - CPLEX sage: p.add_constraint(b[1] + b[2] <= 6) # optional - CPLEX sage: p.set_objective(b[1] + b[2]) # optional - CPLEX sage: copy(p).solve() # optional - CPLEX @@ -1367,20 +1466,84 @@ cdef class CPLEXBackend(GenericBackend): .. NOTE:: The list of available parameters is available at - :meth:`sage.numerical.mip.MixedIntegerlinearProgram.solver_parameter` + :meth:`sage.numerical.mip.MixedIntegerLinearProgram.solver_parameter` - EXAMPLE:: + EXAMPLE: + + Set a computation time limit:: - sage: from sage.numerical.backends.generic_backend import get_solver + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX sage: p = get_solver(solver = "CPLEX") # optional - CPLEX sage: p.solver_parameter("timelimit", 60) # optional - CPLEX sage: p.solver_parameter("timelimit") # optional - CPLEX 60.0 + + Set the logfile (no log file by default):: + + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX + sage: p = get_solver(solver = "CPLEX") # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '' + sage: p.solver_parameter("logfile", '/dev/null') # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '/dev/null' + + Disable the logfile:: + + sage: from sage.numerical.backends.generic_backend import get_solver # optional - CPLEX + sage: p = get_solver(solver = "CPLEX") # optional - CPLEX + sage: p.solver_parameter("logfile", '') # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '' + + TEST: + + Print the logfile's content (through :class:`MixedIntegerLinearProgram`):: + + sage: filename = tmp_filename(ext='.txt') # optional - CPLEX + sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX + sage: b = p.new_variable(binary=True) # optional - CPLEX + sage: for u,v in graphs.CycleGraph(5).edges(labels=False): # optional - CPLEX + ....: p.add_constraint(b[u]+b[v]<=1) # optional - CPLEX + sage: p.set_objective(p.sum(b[x] for x in range(5))) # optional - CPLEX + sage: p.solver_parameter("logfile", filename) # optional - CPLEX + sage: p.solve() # optional - CPLEX + 2.0 + sage: with open(filename,'r') as f: # optional - CPLEX + ....: print f.read() # optional - CPLEX + Found incumbent of value ... + Reduced MIP has 5 rows, 5 columns, and 10 nonzeros. + ... + Elapsed time = ... sec. (... ticks, tree = ... MB, solutions = ...) + + Bad name:: + + sage: p.solver_parameter("Heyyyyyyyyyyy Joeeeeeeeee !!",-4) # optional - CPLEX + Traceback (most recent call last): + ... + ValueError: This parameter is not available. """ cdef int intv cdef double doublev cdef char * strv + # Specific action for log file + cdef FILE *ff + if name.lower() == "logfile": + if value is None: # Return logfile name + return self._logfilename + elif not value: # Close current logfile and disable logs + check( CPXsetlogfile(self.env, NULL) ) + self._logfilename = '' + else: # Set log file to logfilename + ff = fopen(value, "a") + if not ff: + raise ValueError("Unable to append file {}.".format(value)) + check( CPXsetlogfile(self.env, ff) ) + self._logfilename = value + return + + # If the name has to be translated to a CPLEX parameter ID if name == "timelimit": name = "CPX_PARAM_TILIM" @@ -1392,28 +1555,29 @@ cdef class CPLEXBackend(GenericBackend): # Type of the parameter. Can be INT (1), Double(2) or String(3) cdef int paramtype - CPXgetparamtype(self.env, paramid, ¶mtype) + check(CPXgetparamtype(self.env, paramid, ¶mtype)) if value is None: if paramtype == 1: - CPXgetintparam(self.env, paramid, &intv) + check(CPXgetintparam(self.env, paramid, &intv)) return intv elif paramtype == 2: - CPXgetdblparam(self.env, paramid, &doublev) + check(CPXgetdblparam(self.env, paramid, &doublev)) return doublev else: strv = sage_malloc(500*sizeof(char)) - CPXgetstrparam(self.env, paramid, strv) + status = CPXgetstrparam(self.env, paramid, strv) s = str(strv) sage_free(strv) + check(status) return s else: if paramtype == 1: - CPXsetintparam(self.env, paramid, value) + check(CPXsetintparam(self.env, paramid, value)) elif paramtype == 2: - CPXsetdblparam(self.env, paramid, value) + check(CPXsetdblparam(self.env, paramid, value)) else: - CPXsetstrparam(self.env, paramid, value) + check(CPXsetstrparam(self.env, paramid, value)) def __dealloc__(self): r""" diff --git a/src/sage/numerical/backends/generic_backend.pxd b/src/sage/numerical/backends/generic_backend.pxd index f604f76f2f6..7c127ea55b6 100644 --- a/src/sage/numerical/backends/generic_backend.pxd +++ b/src/sage/numerical/backends/generic_backend.pxd @@ -21,6 +21,8 @@ cdef class GenericBackend: cpdef add_linear_constraints(self, int number, lower_bound, upper_bound, names=*) cpdef int solve(self) except -1 cpdef get_objective_value(self) + cpdef best_known_objective_bound(self) + cpdef get_relative_objective_gap(self) cpdef get_variable_value(self, int variable) cpdef bint is_maximization(self) cpdef write_lp(self, char * name) diff --git a/src/sage/numerical/backends/generic_backend.pyx b/src/sage/numerical/backends/generic_backend.pyx index fd042640c05..dbf536daffd 100644 --- a/src/sage/numerical/backends/generic_backend.pyx +++ b/src/sage/numerical/backends/generic_backend.pyx @@ -495,6 +495,76 @@ cdef class GenericBackend: raise NotImplementedError() + cpdef best_known_objective_bound(self): + r""" + Return the value of the currently best known bound. + + This method returns the current best upper (resp. lower) bound on the + optimal value of the objective function in a maximization + (resp. minimization) problem. It is equal to the output of + :meth:`get_objective_value` if the MILP found an optimal solution, but + it can differ if it was interrupted manually or after a time limit (cf + :meth:`solver_parameter`). + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: p = MixedIntegerLinearProgram(solver="Nonexistent_LP_solver") # optional - Nonexistent_LP_solver + sage: b = p.new_variable(binary=True) # optional - Nonexistent_LP_solver + sage: for u,v in graphs.CycleGraph(5).edges(labels=False): # optional - Nonexistent_LP_solver + ....: p.add_constraint(b[u]+b[v]<=1) # optional - Nonexistent_LP_solver + sage: p.set_objective(p.sum(b[x] for x in range(5))) # optional - Nonexistent_LP_solver + sage: p.solve() # optional - Nonexistent_LP_solver + 2.0 + sage: pb = p.get_backend() # optional - Nonexistent_LP_solver + sage: pb.get_objective_value() # optional - Nonexistent_LP_solver + 2.0 + sage: pb.best_known_objective_bound() # optional - Nonexistent_LP_solver + 2.0 + """ + raise NotImplementedError() + + + cpdef get_relative_objective_gap(self): + r""" + Return the relative objective gap of the best known solution. + + For a minimization problem, this value is computed by + `(\texttt{bestinteger} - \texttt{bestobjective}) / (1e-10 + + |\texttt{bestobjective}|)`, where ``bestinteger`` is the value returned + by :meth:`~MixedIntegerLinearProgram.get_objective_value` and + ``bestobjective`` is the value returned by + :meth:`~MixedIntegerLinearProgram.best_known_objective_bound`. For a + maximization problem, the value is computed by `(\texttt{bestobjective} + - \texttt{bestinteger}) / (1e-10 + |\texttt{bestobjective}|)`. + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: p = MixedIntegerLinearProgram(solver="Nonexistent_LP_solver") # optional - Nonexistent_LP_solver + sage: b = p.new_variable(binary=True) # optional - Nonexistent_LP_solver + sage: for u,v in graphs.CycleGraph(5).edges(labels=False): # optional - Nonexistent_LP_solver + ....: p.add_constraint(b[u]+b[v]<=1) # optional - Nonexistent_LP_solver + sage: p.set_objective(p.sum(b[x] for x in range(5))) # optional - Nonexistent_LP_solver + sage: p.solve() # optional - Nonexistent_LP_solver + 2.0 + sage: pb = p.get_backend() # optional - Nonexistent_LP_solver + sage: pb.get_objective_value() # optional - Nonexistent_LP_solver + 2.0 + sage: pb.get_best_objective_value() # optional - Nonexistent_LP_solver + 2.0 + sage: pb.get_relative_objective_gap() # optional - Nonexistent_LP_solver + 0.0 + """ + raise NotImplementedError() + + cpdef get_variable_value(self, int variable): """ Return the value of a variable given by the solver. diff --git a/src/sage/numerical/backends/glpk_backend.pxd b/src/sage/numerical/backends/glpk_backend.pxd index 8250274d67c..6fd49adbbfb 100644 --- a/src/sage/numerical/backends/glpk_backend.pxd +++ b/src/sage/numerical/backends/glpk_backend.pxd @@ -15,6 +15,8 @@ cdef extern from "float.h": cdef double DBL_MAX cdef extern from "glpk.h": + ctypedef struct c_glp_tree "glp_tree": + pass ctypedef struct c_glp_prob "glp_prob": pass ctypedef struct c_glp_iocp "glp_iocp": @@ -35,6 +37,8 @@ cdef extern from "glpk.h": int out_dly int presolve int binarize + void (*cb_func)(c_glp_tree *T, void *info) # callback function + void *cb_info # callback function input ctypedef struct c_glp_smcp "glp_smcp": int msg_lev int meth @@ -111,6 +115,7 @@ cdef extern from "glpk.h": void glp_create_index(c_glp_prob *lp) int glp_get_prim_stat(c_glp_prob *lp) + int glp_get_status(c_glp_prob *lp) int glp_mip_status(c_glp_prob *lp) int glp_set_mat_row(c_glp_prob *lp, int, int, int *, double * ) int glp_set_mat_col(c_glp_prob *lp, int, int, int *, double * ) @@ -121,6 +126,9 @@ cdef extern from "glpk.h": double glp_get_obj_coef(c_glp_prob *lp, int) int glp_get_obj_dir(c_glp_prob *lp) void glp_copy_prob(c_glp_prob *dst, c_glp_prob *src, int names) + double glp_ios_mip_gap(c_glp_tree *T) + int glp_ios_best_node(c_glp_tree *tree) + double glp_ios_node_bound(c_glp_tree *T, int p) # constants @@ -175,8 +183,11 @@ cdef extern from "glpk.h": int GLP_EOBJUL int GLP_EITLIM int GLP_ETMLIM - int GLP_EOPFS - int GLP_EODFS + int GLP_ENOPFS + int GLP_ENODFS + int GLP_EROOT + int GLP_ESTOP + int GLP_EMIPGAP int GLP_UNDEF int GLP_OPT @@ -208,13 +219,21 @@ cdef extern from "glpk.h": int GLP_NF # non-basic free (unbounded) variable int GLP_NS # non-basic fixed variable +# search_tree_data_t: +# +# This structure stores the data gathered by the callback function while the +# search tree is explored. +ctypedef struct search_tree_data_t: + double mip_gap + double best_bound + cdef class GLPKBackend(GenericBackend): cdef c_glp_prob * lp cdef c_glp_iocp * iocp cdef c_glp_smcp * smcp cdef int simplex_or_intopt + cdef search_tree_data_t search_tree_data cpdef GLPKBackend copy(self) - cpdef int print_ranges(self, char * filename = *) except -1 cpdef double get_row_dual(self, int variable) cpdef double get_col_dual(self, int variable) diff --git a/src/sage/numerical/backends/glpk_backend.pyx b/src/sage/numerical/backends/glpk_backend.pyx index 3d7e2606ff7..35a49121cc5 100644 --- a/src/sage/numerical/backends/glpk_backend.pyx +++ b/src/sage/numerical/backends/glpk_backend.pyx @@ -37,6 +37,9 @@ cdef class GLPKBackend(GenericBackend): glp_init_smcp(self.smcp) self.iocp = sage_malloc(sizeof(c_glp_iocp)) glp_init_iocp(self.iocp) + + self.iocp.cb_func = glp_callback # callback function + self.iocp.cb_info = &(self.search_tree_data) # callback data self.iocp.presolve = GLP_ON self.set_verbosity(0) self.obj_constant_term = 0.0 @@ -754,6 +757,28 @@ cdef class GLPKBackend(GenericBackend): """ Solve the problem. + Sage uses GLPK's implementation of the branch-and-cut algorithm + (``glp_intopt``) to solve the mixed-integer linear program. + This algorithm can be requested explicitly by setting the solver + parameter "simplex_or_intopt" to "intopt_only". + (If all variables are continuous, the algorithm reduces to solving the + linear program by the simplex method.) + + EXAMPLE:: + + sage: lp = MixedIntegerLinearProgram(solver = 'GLPK', maximization = False) + sage: x, y = lp[0], lp[1] + sage: lp.add_constraint(-2*x + y <= 1) + sage: lp.add_constraint(x - y <= 1) + sage: lp.add_constraint(x + y >= 2) + sage: lp.set_objective(x + y) + sage: lp.set_integer(x) + sage: lp.set_integer(y) + sage: lp.solve() + 2.0 + sage: lp.get_values([x, y]) + [1.0, 1.0] + .. NOTE:: This method raises ``MIPSolverException`` exceptions when @@ -782,7 +807,7 @@ cdef class GLPKBackend(GenericBackend): Here, "catastrophic" can mean either "infinite loop" or segmentation fault. Upstream considers this behavior "essentially innate" to their design, and suggests - preprocessing it with ``glpk_simplex`` first. + preprocessing it with ``glp_simplex`` first. Thus, if you suspect that your system is infeasible, set the ``preprocessing`` option first. @@ -803,10 +828,10 @@ cdef class GLPKBackend(GenericBackend): sage: lp.solve() Traceback (most recent call last): ... - MIPSolverException: 'GLPK : Simplex cannot find a feasible solution' + MIPSolverException: 'GLPK : Problem has no feasible solution' - The user can ask sage to solve via ``simplex`` or ``intopt``. - The default solver is ``intopt``, so we get integer solutions. + If we switch to "simplex_only", the integrality constraints are ignored, + and we get an optimal solution to the continuous relaxation. EXAMPLE:: @@ -818,21 +843,16 @@ cdef class GLPKBackend(GenericBackend): sage: lp.set_objective(x + y) sage: lp.set_integer(x) sage: lp.set_integer(y) - sage: lp.solve() - 2.0 - sage: lp.get_values([x, y]) - [1.0, 1.0] - - If we switch to ``simplex``, we get continuous solutions. - - EXAMPLE:: - sage: lp.solver_parameter("simplex_or_intopt", "simplex_only") # use simplex only sage: lp.solve() 2.0 sage: lp.get_values([x, y]) [1.5, 0.5] + If one solves a linear program and wishes to access dual information + (`get_col_dual` etc.) or tableau data (`get_row_stat` etc.), + one needs to switch to "simplex_only" before solving. + GLPK also has an exact rational simplex solver. The only access to data is via double-precision floats, however. It reconstructs rationals from doubles and also provides results @@ -844,8 +864,7 @@ cdef class GLPKBackend(GenericBackend): sage: lp.solve() glp_exact: 3 rows, 2 columns, 6 non-zeros GNU MP bignum library is being used - * 5: objval = 2 (0) - * 5: objval = 2 (0) + ... OPTIMAL SOLUTION FOUND 2.0 sage: lp.get_values([x, y]) @@ -881,52 +900,98 @@ cdef class GLPKBackend(GenericBackend): sage: lp.solve() == test # yes, we want an exact comparison here glp_exact: 1 rows, 1 columns, 1 non-zeros GNU MP bignum library is being used - * 0: objval = 0 (0) - * 1: objval = 9.00719925474095e+15 (0) + ... OPTIMAL SOLUTION FOUND True sage: lp.get_values(x) == test # yes, we want an exact comparison here True + Below we test that GLPK backend can detect unboundedness in + "simplex_only" mode (:trac:`18838`). + + EXAMPLES:: + + sage: lp = MixedIntegerLinearProgram(maximization=True, solver = "GLPK") + sage: lp.set_objective(lp[0]) + sage: lp.solver_parameter("simplex_or_intopt", "simplex_only") + sage: lp.solve() + Traceback (most recent call last): + ... + MIPSolverException: 'GLPK : Problem has unbounded solution' + sage: lp.set_objective(lp[1]) + sage: lp.solver_parameter("primal_v_dual", "GLP_DUAL") + sage: lp.solve() + Traceback (most recent call last): + ... + MIPSolverException: 'GLPK : Problem has unbounded solution' + sage: lp.solver_parameter("simplex_or_intopt", "simplex_then_intopt") + sage: lp.solve() + Traceback (most recent call last): + ... + MIPSolverException: 'GLPK : The LP (relaxation) problem has no dual feasible solution' + sage: lp.solver_parameter("simplex_or_intopt", "intopt_only") + sage: lp.solve() + Traceback (most recent call last): + ... + MIPSolverException: 'GLPK : The LP (relaxation) problem has no dual feasible solution' + sage: lp.set_max(lp[1],5) + sage: lp.solve() + 5.0 + + Solving a LP within the acceptable gap. No exception is raised, even if + the result is not optimal. To do this, we try to compute the maximum + number of disjoint balls (of diameter 1) in a hypercube:: + + sage: g = graphs.CubeGraph(9) + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: p.solver_parameter("mip_gap_tolerance",100) + sage: b = p.new_variable(binary=True) + sage: p.set_objective(p.sum(b[v] for v in g)) + sage: for v in g: + ....: p.add_constraint(b[v]+p.sum(b[u] for u in g.neighbors(v)) <= 1) + sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution + sage: p.solve() # rel tol 100 + 1 + + Same, now with a time limit:: + + sage: p.solver_parameter("mip_gap_tolerance",1) + sage: p.solver_parameter("timelimit",0.01) + sage: p.solve() # rel tol 1 + 1 """ - cdef int status + cdef int solve_status + cdef int solution_status + global solve_status_msg + global solution_status_msg + if (self.simplex_or_intopt == glp_simplex_only or self.simplex_or_intopt == glp_simplex_then_intopt or self.simplex_or_intopt == glp_exact_simplex_only): if self.simplex_or_intopt == glp_exact_simplex_only: - status = glp_exact(self.lp, self.smcp) + solve_status = glp_exact(self.lp, self.smcp) else: - status = glp_simplex(self.lp, self.smcp) - status = glp_get_prim_stat(self.lp) - if status == GLP_OPT or status == GLP_FEAS: - pass - elif status == GLP_UNDEF or status == GLP_NOFEAS: - raise MIPSolverException("GLPK : Simplex cannot find a feasible solution") - - if (self.simplex_or_intopt != glp_simplex_only - and self.simplex_or_intopt != glp_exact_simplex_only): - sig_str('GLPK : Signal sent, try preprocessing option') - status = glp_intopt(self.lp, self.iocp) - sig_off() - # this is necessary to catch errors when certain options are enabled, e.g. tm_lim - if status == GLP_ETMLIM: raise MIPSolverException("GLPK : The time limit was reached") - elif status == GLP_EITLIM: raise MIPSolverException("GLPK : The iteration limit was reached") - - status = glp_mip_status(self.lp) - if status == GLP_OPT: - pass - elif status == GLP_UNDEF: - raise MIPSolverException("GLPK : Solution is undefined") - elif status == GLP_FEAS: - raise MIPSolverException("GLPK : Feasible solution found, while optimality has not been proven") - elif status == GLP_INFEAS: - raise MIPSolverException("GLPK : Solution is infeasible") - elif status == GLP_UNBND: - raise MIPSolverException("GLPK : Problem has unbounded solution") - elif status == GLP_NOFEAS: - raise MIPSolverException("GLPK : There is no feasible integer solution to this Linear Program") - + solve_status = glp_simplex(self.lp, self.smcp) + solution_status = glp_get_status(self.lp) + + if ((self.simplex_or_intopt == glp_intopt_only) + or (self.simplex_or_intopt == glp_simplex_then_intopt) and (solution_status != GLP_UNDEF) and (solution_status != GLP_NOFEAS)): + sig_str('GLPK : Signal sent, try preprocessing option') + solve_status = glp_intopt(self.lp, self.iocp) + sig_off() + solution_status = glp_mip_status(self.lp) + + if solution_status == GLP_OPT: + pass + elif (solution_status == GLP_FEAS) and (solve_status == GLP_ETMLIM or solve_status == GLP_EITLIM \ + or solve_status == GLP_EMIPGAP or solve_status == GLP_EOBJLL or solve_status == GLP_EOBJUL): + # no exception when time limit or iteration limit or mip gap tolerances or objective limits reached. + pass + elif solution_status == GLP_UNDEF: + raise MIPSolverException("GLPK : "+solve_status_msg.get(solve_status, "unknown error during call to GLPK : "+str(solve_status))) + else: + raise MIPSolverException("GLPK : "+solution_status_msg.get(solution_status, "unknown error during call to GLPK : "+str(solution_status))) return 0 cpdef get_objective_value(self): @@ -960,6 +1025,81 @@ cdef class GLPKBackend(GenericBackend): else: return glp_get_obj_val(self.lp) + cpdef best_known_objective_bound(self): + r""" + Return the value of the currently best known bound. + + This method returns the current best upper (resp. lower) bound on the + optimal value of the objective function in a maximization + (resp. minimization) problem. It is equal to the output of + :meth:`get_objective_value` if the MILP found an optimal solution, but + it can differ if it was interrupted manually or after a time limit (cf + :meth:`solver_parameter`). + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: g = graphs.CubeGraph(9) + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: p.solver_parameter("mip_gap_tolerance",100) + sage: b = p.new_variable(binary=True) + sage: p.set_objective(p.sum(b[v] for v in g)) + sage: for v in g: + ....: p.add_constraint(b[v]+p.sum(b[u] for u in g.neighbors(v)) <= 1) + sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution + sage: p.solve() # rel tol 100 + 1.0 + sage: backend = p.get_backend() + sage: backend.best_known_objective_bound() # random + 48.0 + """ + return self.search_tree_data.best_bound + + cpdef get_relative_objective_gap(self): + r""" + Return the relative objective gap of the best known solution. + + For a minimization problem, this value is computed by + `(\texttt{bestinteger} - \texttt{bestobjective}) / (1e-10 + + |\texttt{bestobjective}|)`, where ``bestinteger`` is the value returned + by :meth:`get_objective_value` and ``bestobjective`` is the value + returned by :meth:`best_known_objective_bound`. For a maximization + problem, the value is computed by `(\texttt{bestobjective} - + \texttt{bestinteger}) / (1e-10 + |\texttt{bestobjective}|)`. + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: g = graphs.CubeGraph(9) + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: p.solver_parameter("mip_gap_tolerance",100) + sage: b = p.new_variable(binary=True) + sage: p.set_objective(p.sum(b[v] for v in g)) + sage: for v in g: + ....: p.add_constraint(b[v]+p.sum(b[u] for u in g.neighbors(v)) <= 1) + sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution + sage: p.solve() # rel tol 100 + 1.0 + sage: backend = p.get_backend() + sage: backend.get_relative_objective_gap() # random + 46.99999999999999 + + TESTS: + + Just make sure that the variable *has* been defined, and is not just + undefined:: + + sage: backend.get_relative_objective_gap() > 1 + True + """ + return self.search_tree_data.mip_gap + cpdef get_variable_value(self, int variable): """ Returns the value of a variable given by the solver. @@ -1583,7 +1723,7 @@ cdef class GLPKBackend(GenericBackend): sage: p.solver_parameter("timelimit_intopt") 60000 - If you don't care for an integer answer, you can ask for an lp + If you don't care for an integer answer, you can ask for an LP relaxation instead. The default solver performs integer optimization, but you can switch to the standard simplex algorithm through the ``glp_simplex_or_intopt`` parameter. @@ -1608,7 +1748,7 @@ cdef class GLPKBackend(GenericBackend): sage: lp.get_values([x,y]) [1.5, 0.5] - You can get glpk to spout all sorts of information at you. + You can get GLPK to spout all sorts of information at you. The default is to turn this off, but sometimes (debugging) it's very useful:: sage: lp.solver_parameter(backend.glp_simplex_or_intopt, backend.glp_simplex_then_intopt) @@ -2274,6 +2414,29 @@ cdef class GLPKBackend(GenericBackend): sage_free(self.iocp) sage_free(self.smcp) +cdef void glp_callback(c_glp_tree* tree, void* info): + r""" + A callback routine called by glp_intopt + + This function fills the ``search_tree_data`` structure of a ``GLPKBackend`` + object. It is fed to ``glp_intopt`` (which calls it while the search tree is + being built) through ``iocp.cb_func``. + + INPUT: + + - ``tree`` -- a pointer toward ``c_glp_tree``, which is GLPK's search tree + + - ``info`` -- a ``void *`` to let the function know *where* it should store + the data we need. The value of ``info`` is equal to the one stored in + iocp.cb_info. + + """ + cdef search_tree_data_t * data = info + data.mip_gap = glp_ios_mip_gap(tree) + + cdef int node_id = glp_ios_best_node(tree) + data.best_bound = glp_ios_node_bound(tree, node_id) + # parameter names cdef enum solver_parameter_names: @@ -2490,3 +2653,29 @@ solver_parameter_values = { 'GLP_NOFEAS' : GLP_NOFEAS } + +cdef dict solve_status_msg = { + GLP_EBADB: "The initial basis specified in the problem object is invalid", + GLP_ESING: "The basis matrix corresponding to the initial basis is singular within the working precision", + GLP_ECOND: "The basis matrix corresponding to the initial basis is ill-conditioned, i.e. its condition number is too large", + GLP_EBOUND: "Some variables (auxiliary or structural) have incorrect bounds", + GLP_EFAIL: "Solver failure", + GLP_EOBJLL: "The objective lower limit has been reached", + GLP_EOBJUL: "The objective upper limit has been reached", + GLP_EITLIM: "The iteration limit has been exceeded", + GLP_ETMLIM: "The time limit has been exceeded", + GLP_ENOPFS: "The LP (relaxation) problem has no primal feasible solution", + GLP_ENODFS: "The LP (relaxation) problem has no dual feasible solution", + GLP_EROOT: "Optimal basis for initial LP relaxation is not provided", + GLP_ESTOP: "The search was prematurely terminated by application", + GLP_EMIPGAP: "The relative mip gap tolerance has been reached", +} + +cdef dict solution_status_msg = { + GLP_UNDEF: "Solution is undefined", + GLP_FEAS: "Feasible solution found, while optimality has not been proven", + GLP_INFEAS: "Solution is infeasible", + GLP_NOFEAS: "Problem has no feasible solution", + GLP_OPT: "Solution is optimal", + GLP_UNBND: "Problem has unbounded solution", +} diff --git a/src/sage/numerical/backends/glpk_graph_backend.pyx b/src/sage/numerical/backends/glpk_graph_backend.pyx index 539548bc8c3..d32f1825bfb 100644 --- a/src/sage/numerical/backends/glpk_graph_backend.pyx +++ b/src/sage/numerical/backends/glpk_graph_backend.pyx @@ -907,7 +907,7 @@ cdef class GLPKGraphBackend(object): - ``u`` -- The name (as ``str``) of the tail vertex of the edge - ``v`` -- The name (as ``str``) of the tail vertex of the edge - ``params`` -- ``params`` -- An optional ``dict`` containing the edge - parameters (see meth:``add_edge``). If this parameter + parameters (see :meth:``add_edge``). If this parameter is not provided, all edges connecting ``u`` and ``v`` are deleted. Otherwise only edges with matching parameters are deleted. diff --git a/src/sage/numerical/backends/gurobi_backend.pyx b/src/sage/numerical/backends/gurobi_backend.pyx index ee0e046749f..195edc66826 100644 --- a/src/sage/numerical/backends/gurobi_backend.pyx +++ b/src/sage/numerical/backends/gurobi_backend.pyx @@ -1117,6 +1117,8 @@ cdef class GurobiBackend(GenericBackend): if name == "timelimit": name = "TimeLimit" + elif name.lower() == "logfile": + name = "LogFile" try: t = parameters_type[name] diff --git a/src/sage/numerical/interactive_simplex_method.py b/src/sage/numerical/interactive_simplex_method.py index 5a2a544cdd1..917e6605400 100644 --- a/src/sage/numerical/interactive_simplex_method.py +++ b/src/sage/numerical/interactive_simplex_method.py @@ -21,6 +21,8 @@ - Andrey Novoseltsev (2013-03-16): initial version. +- Matthias Koeppe, Peijun Xiao (2015-07-05): allow different output styles. + EXAMPLES: Most of the module functionality is demonstrated on the following problem. @@ -416,6 +418,136 @@ def variable(R, v): raise ValueError("cannot interpret given data as a variable") +available_styles = { + "UAlberta": { + "primal decision": "x", + "primal slack": "x", + "dual decision": "y", + "dual slack": "y", + "primal objective": "z", + "dual objective": "z", + "auxiliary objective": "w", + }, + "Vanderbei": { + "primal decision": "x", + "primal slack": "w", + "dual decision": "y", + "dual slack": "z", + "primal objective": "zeta", + "dual objective": "xi", + "auxiliary objective": "xi", + }, + } + +current_style = 'UAlberta' + +def default_variable_name(variable): + r""" + Return default variable name for the current :func:`style`. + + INPUT: + + - ``variable`` - a string describing requested name + + OUTPUT: + + - a string with the requested name for current style + + EXAMPLES:: + + sage: sage.numerical.interactive_simplex_method.default_variable_name("primal slack") + 'x' + sage: sage.numerical.interactive_simplex_method.style('Vanderbei') + 'Vanderbei' + sage: sage.numerical.interactive_simplex_method.default_variable_name("primal slack") + 'w' + sage: sage.numerical.interactive_simplex_method.style('UAlberta') + 'UAlberta' + """ + return available_styles[current_style][variable] + +def style(new_style=None): + r""" + Set or get the current style of problems and dictionaries. + + INPUT: + + - ``new_style`` -- a string or ``None`` (default) + + OUTPUT: + + - a string with current style (same as ``new_style`` if it was given) + + If the input is not recognized as a valid style, a ``ValueError`` exception + is raised. + + Currently supported styles are: + + - 'UAlberta' (default): Follows the style used in the Math 373 course + on Mathematical Programming and Optimization at the University of + Alberta, Edmonton, Canada; based on Chvatal's book. + + - Objective functions of dictionaries are printed at the bottom. + + Variable names default to + + - `z` for primal objective + + - `z` for dual objective + + - `w` for auxiliary objective + + - `x_1, x_2, \dots, x_n` for primal decision variables + + - `x_{n+1}, x_{n+2}, \dots, x_{n+m}` for primal slack variables + + - `y_1, y_2, \dots, y_m` for dual decision variables + + - `y_{m+1}, y_{m+2}, \dots, y_{m+n}` for dual slack variables + + - 'Vanderbei': Follows the style of Robert Vanderbei's textbook, + Linear Programming -- Foundations and Extensions. + + - Objective functions of dictionaries are printed at the top. + + Variable names default to + + - `zeta` for primal objective + + - `xi` for dual objective + + - `xi` for auxiliary objective + + - `x_1, x_2, \dots, x_n` for primal decision variables + + - `w_1, w_2, \dots, w_m` for primal slack variables + + - `y_1, y_2, \dots, y_m` for dual decision variables + + - `z_1, z_2, \dots, z_n` for dual slack variables + + EXAMPLES:: + + sage: sage.numerical.interactive_simplex_method.style() + 'UAlberta' + sage: sage.numerical.interactive_simplex_method.style('Vanderbei') + 'Vanderbei' + sage: sage.numerical.interactive_simplex_method.style('Doesntexist') + Traceback (most recent call last): + ... + ValueError: Style must be one of: UAlberta, Vanderbei + sage: sage.numerical.interactive_simplex_method.style('UAlberta') + 'UAlberta' + """ + global current_style + if new_style is not None: + if new_style not in available_styles: + raise ValueError("Style must be one of: {}".format( + ", ".join(available_styles.keys()))) + current_style = new_style + return current_style + + class InteractiveLPProblem(SageObject): r""" Construct an LP (Linear Programming) problem. @@ -450,14 +582,14 @@ class InteractiveLPProblem(SageObject): - ``problem_type`` -- (default: ``"max"``) a string specifying the problem type: ``"max"``, ``"min"``, ``"-max"``, or ``"-min"`` - - ``prefix`` -- (default: parameter ``x`` if it was given as a string, - string ``"x"`` otherwise) a string giving the base name of automatically - created variables in :meth:`standard_form` - - ``base_ring`` -- (default: the fraction field of a common ring for all input coefficients) a field to which all input coefficients will be converted + - ``is_primal`` -- (default: ``True``) whether this problem is primal or + dual: each problem is of course dual to its own dual, this flag is mostly + for internal use and affects default variable names only + EXAMPLES: We will construct the following problem: @@ -497,7 +629,7 @@ class InteractiveLPProblem(SageObject): def __init__(self, A, b, c, x="x", constraint_type="<=", variable_type="", problem_type="max", - prefix="x", base_ring=None): + base_ring=None, is_primal=True): r""" See :class:`InteractiveLPProblem` for documentation. @@ -528,7 +660,6 @@ def __init__(self, A, b, c, x="x", if c.degree() != n: raise ValueError("A and c have incompatible dimensions") if isinstance(x, str): - prefix = x x = ["{}{:d}".format(x, i) for i in range(1, n+1)] else: x = [str(_) for _ in x] @@ -567,7 +698,7 @@ def __init__(self, A, b, c, x="x", raise ValueError("unknown problem type") self._problem_type = problem_type - self._prefix = prefix + self._is_primal = is_primal def __eq__(self, other): r""" @@ -600,8 +731,7 @@ def __eq__(self, other): self._problem_type == other._problem_type and self._is_negative == other._is_negative and self._constraint_types == other._constraint_types and - self._variable_types == other._variable_types and - self._prefix == other._prefix) + self._variable_types == other._variable_types) def _latex_(self): r""" @@ -841,9 +971,8 @@ def dual(self, y=None): INPUT: - - ``y`` -- (default: ``"x"`` if the prefix of ``self`` is ``"y"``, - ``"y"`` otherwise) a vector of dual decision variables or a string - giving the base name + - ``y`` -- (default: depends on :func:`style`) + a vector of dual decision variables or a string giving the base name OUTPUT: @@ -860,11 +989,25 @@ def dual(self, y=None): True sage: DP.dual(["C", "B"]) == P True + + TESTS:: + + sage: DP.standard_form().objective_name() + -z + sage: sage.numerical.interactive_simplex_method.style("Vanderbei") + 'Vanderbei' + sage: P.dual().standard_form().objective_name() + -xi + sage: sage.numerical.interactive_simplex_method.style("UAlberta") + 'UAlberta' + sage: P.dual().standard_form().objective_name() + -z """ A, c, b, x = self.Abcx() A = A.transpose() if y is None: - y = "x" if self._prefix == "y" else "y" + y = default_variable_name( + "dual decision" if self.is_primal() else "primal decision") problem_type = "min" if self._problem_type == "max" else "max" constraint_type = [] for vt in self._variable_types: @@ -889,7 +1032,8 @@ def dual(self, y=None): if self._is_negative: problem_type = "-" + problem_type return InteractiveLPProblem(A, b, c, y, - constraint_type, variable_type, problem_type) + constraint_type, variable_type, problem_type, + is_primal=not self.is_primal()) @cached_method def feasible_set(self): @@ -970,6 +1114,29 @@ def is_feasible(self): """ return self._solve()[1] is not None + def is_primal(self): + r""" + Check if we consider this problem to be primal or dual. + + This distinction affects only some automatically chosen variable names. + + OUTPUT: + + - boolean + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = InteractiveLPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.is_primal() + True + sage: P.dual().is_primal() + False + """ + return self._is_primal + def n_constraints(self): r""" Return the number of constraints of ``self``, i.e. `m`. @@ -1256,9 +1423,14 @@ def plot_feasible_set(self, xmin=None, xmax=None, ymin=None, ymax=None, result.set_aspect_ratio(1) return result - def standard_form(self): + def standard_form(self, objective_name=None): r""" Construct the LP problem in standard form equivalent to ``self``. + + INPUT: + + - ``objective_name`` -- a string or a symbolic expression for the + objective used in dictionaries, default depends on :func:`style` OUTPUT: @@ -1308,13 +1480,20 @@ def standard_form(self): A = column_matrix(newA) c = vector(newc) x = newx + + is_primal = self.is_primal() + if objective_name is None: + objective_name = default_variable_name( + "primal objective" if is_primal else "dual objective") + objective_name = SR(objective_name) is_negative = self._is_negative if self._problem_type == "min": is_negative = not is_negative c = - c + objective_name = - objective_name problem_type = "-max" if is_negative else "max" return InteractiveLPProblemStandardForm(A, b, c, x, problem_type, - self._prefix, self._prefix + "0") + is_primal=is_primal, objective_name=objective_name) # Aliases for the standard notation A = constraint_coefficients @@ -1324,6 +1503,7 @@ def standard_form(self): m = n_constraints n = n_variables + class InteractiveLPProblemStandardForm(InteractiveLPProblem): r""" Construct an LP (Linear Programming) problem in standard form. @@ -1358,22 +1538,25 @@ class InteractiveLPProblemStandardForm(InteractiveLPProblem): - ``problem_type`` -- (default: ``"max"``) a string specifying the problem type: either ``"max"`` or ``"-max"`` - - ``slack_variables`` -- (default: same as ``x`` parameter, if it was given - as a string, otherwise string ``"x"``) a vector of slack variables or - a sting giving the base name + - ``slack_variables`` -- (default: depends on :func:`style`) + a vector of slack variables or a sting giving the base name - ``auxiliary_variable`` -- (default: same as ``x`` parameter with adjoined ``"0"`` if it was given as a string, otherwise ``"x0"``) the auxiliary name, expected to be the same as the first decision variable for auxiliary problems - - ``objective`` -- (default: ``"z"``) the objective variable (used for the - initial dictionary) - - ``base_ring`` -- (default: the fraction field of a common ring for all input coefficients) a field to which all input coefficients will be converted + - ``is_primal`` -- (default: ``True``) whether this problem is primal or + dual: each problem is of course dual to its own dual, this flag is mostly + for internal use and affects default variable names only + + - ``objective_name`` -- a string or a symbolic expression for the + objective used in dictionaries, default depends on :func:`style` + EXAMPLES:: sage: A = ([1, 1], [3, 1]) @@ -1394,10 +1577,10 @@ class InteractiveLPProblemStandardForm(InteractiveLPProblem): """ def __init__(self, A, b, c, x="x", problem_type="max", - slack_variables=None, auxiliary_variable=None, objective="z", - base_ring=None): + slack_variables=None, auxiliary_variable=None, + base_ring=None, is_primal=True, objective_name=None): r""" - See :class:`StandardFormLPP` for documentation. + See :class:`InteractiveLPProblemStandardForm` for documentation. TESTS:: @@ -1410,24 +1593,33 @@ def __init__(self, A, b, c, x="x", problem_type="max", if problem_type not in ("max", "-max"): raise ValueError("problems in standard form must be of (negative) " "maximization type") - super(InteractiveLPProblemStandardForm, self).__init__(A, b, c, x, - problem_type=problem_type, - constraint_type="<=", - variable_type=">=", - base_ring=base_ring) + super(InteractiveLPProblemStandardForm, self).__init__( + A, b, c, x, + problem_type=problem_type, + constraint_type="<=", + variable_type=">=", + base_ring=base_ring, + is_primal=is_primal) n, m = self.n(), self.m() if slack_variables is None: - slack_variables = self._prefix + slack_variables = default_variable_name( + "primal slack" if is_primal else "dual slack") if isinstance(slack_variables, str): + if style() == "UAlberta": + indices = range(n + 1, n + m + 1) + if style() == 'Vanderbei': + indices = range(1, m + 1) slack_variables = ["{}{:d}".format(slack_variables, i) - for i in range(n + 1, n + m + 1)] + for i in indices] else: - slack_variables = map(str, slack_variables) + slack_variables = list(map(str, slack_variables)) if len(slack_variables) != m: raise ValueError("wrong number of slack variables") if auxiliary_variable is None: - auxiliary_variable = self._prefix + "0" - names = [str(auxiliary_variable)] +[str(_) for _ in self.x()] + slack_variables + auxiliary_variable = x + "0" if isinstance(x, str) else "x0" + names = [str(auxiliary_variable)] + names.extend(map(str, self.x())) + names.extend(slack_variables) if names[0] == names[1]: names.pop(0) R = PolynomialRing(self.base_ring(), names, order="neglex") @@ -1435,11 +1627,19 @@ def __init__(self, A, b, c, x="x", problem_type="max", x = vector(R.gens()[-n-m:-m]) x.set_immutable() self._Abcx = self._Abcx[:-1] + (x, ) - self._objective = objective + if objective_name is None: + objective_name = default_variable_name( + "primal objective" if is_primal else "dual objective") + self._objective_name = SR(objective_name) - def auxiliary_problem(self): + def auxiliary_problem(self, objective_name=None): r""" Construct the auxiliary problem for ``self``. + + INPUT: + + - ``objective_name`` -- a string or a symbolic expression for the + objective used in dictionaries, default depends on :func:`style` OUTPUT: @@ -1474,10 +1674,12 @@ def auxiliary_problem(self): F = self.base_ring() A = column_matrix(F, [-1] * m).augment(self.A()) c = vector(F, [-1] + [0] * n) - return InteractiveLPProblemStandardForm(A, self.b(), c, X[:-m], - slack_variables=X[-m:], - auxiliary_variable=X[0], - objective="w") + if objective_name is None: + objective_name = default_variable_name("auxiliary objective") + return InteractiveLPProblemStandardForm( + A, self.b(), c, + X[:-m], slack_variables=X[-m:], auxiliary_variable=X[0], + objective_name=objective_name) def auxiliary_variable(self): r""" @@ -1638,7 +1840,7 @@ def feasible_dictionary(self, auxiliary_dictionary): v += cj * b[i] B = [self._R(_) for _ in B] N = [self._R(_) for _ in N] - return LPDictionary(A, b, c, v, B, N, self._objective) + return LPDictionary(A, b, c, v, B, N, self.objective_name()) def final_dictionary(self): r""" @@ -1733,7 +1935,8 @@ def initial_dictionary(self): A, b, c, x = self.Abcx() x = self._R.gens() m, n = self.m(), self.n() - return LPDictionary(A, b, c, 0, x[-m:], x[-m-n:-m], self._objective) + return LPDictionary(A, b, c, 0, x[-m:], x[-m-n:-m], + self.objective_name()) def inject_variables(self, scope=None, verbose=True): r""" @@ -1768,6 +1971,35 @@ def inject_variables(self, scope=None, verbose=True): except AttributeError: pass + def objective_name(self): + r""" + Return the objective name used in dictionaries for this problem. + + OUTPUT: + + - a symbolic expression + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = InteractiveLPProblemStandardForm(A, b, c) + sage: P.objective_name() + z + sage: sage.numerical.interactive_simplex_method.style("Vanderbei") + 'Vanderbei' + sage: P = InteractiveLPProblemStandardForm(A, b, c) + sage: P.objective_name() + zeta + sage: sage.numerical.interactive_simplex_method.style("UAlberta") + 'UAlberta' + sage: P = InteractiveLPProblemStandardForm(A, b, c, objective_name="custom") + sage: P.objective_name() + custom + """ + return self._objective_name + def revised_dictionary(self, *x_B): r""" Construct a revised dictionary for ``self``. @@ -2603,8 +2835,8 @@ class LPDictionary(LPAbstractDictionary): - ``basic_variables`` -- a list of basic variables `x_B` - ``nonbasic_variables`` -- a list of non-basic variables `x_N` - - - ``objective_variable`` -- an objective variable `z` + + - ``objective_name`` -- a "name" for the objective `z` OUTPUT: @@ -2642,7 +2874,8 @@ class LPDictionary(LPAbstractDictionary): """ def __init__(self, A, b, c, objective_value, - basic_variables, nonbasic_variables, objective_variable): + basic_variables, nonbasic_variables, + objective_name): r""" See :class:`LPDictionary` for documentation. @@ -2664,7 +2897,7 @@ def __init__(self, A, b, c, objective_value, c = copy(c) B = vector(basic_variables) N = vector(nonbasic_variables) - self._AbcvBNz = [A, b, c, objective_value, B, N, SR(objective_variable)] + self._AbcvBNz = [A, b, c, objective_value, B, N, SR(objective_name)] def __eq__(self, other): r""" @@ -2735,17 +2968,23 @@ def _latex_(self): lines.append(r"\renewcommand{\arraystretch}{1.5}") if generate_real_LaTeX: lines[-1] += r" \setlength{\arraycolsep}{0.125em}" -# else: -# lines[-1] += r"\require{color}" - lines.append(r"\begin{array}{|rcr%s|}" % ("cr"*len(N))) - lines.append(r"\hline") - for xi, bi, Ai in zip(B, b, A.rows()): - lines.append(_latex_product(-Ai,N, head=[xi, "=", bi], - drop_plus=False, allow_empty=True) + r"\\") - lines.append(r"\hline") - lines.append(_latex_product(c, N, head=[z, "=", v], - drop_plus=False, allow_empty=True) + r"\\") - lines.append(r"\hline") + relations = [_latex_product(-Ai,N, head=[xi, "=", bi], + drop_plus=False, allow_empty=True) + r"\\" + for xi, bi, Ai in zip(B, b, A.rows())] + objective = _latex_product(c, N, head=[z, "=", v], + drop_plus=False, allow_empty=True) + r"\\" + if style() == "UAlberta": + lines.append(r"\begin{array}{|rcr%s|}" % ("cr"*len(N))) + lines.append(r"\hline") + lines.extend(relations) + lines.append(r"\hline") + lines.append(objective) + lines.append(r"\hline") + if style() == "Vanderbei": + lines.append(r"\begin{array}{rcr%s}" % ("cr"*len(N))) + lines.append(objective) + lines.append(r"\hline") + lines.extend(relations) lines.append(r"\end{array}") latex.add_package_to_preamble_if_available("color") if self._entering is not None: @@ -2758,7 +2997,11 @@ def _latex_(self): lines[i] = "&".join(line) if self._leaving is not None: # Highlight the leaving variable row - l = tuple(B).index(self._leaving) + 3 + l = tuple(B).index(self._leaving) + if style() == "UAlberta": + l += 3 + if style() == "Vanderbei": + l += 4 line = lines[l].split("&") for i, term in enumerate(line): line[i] = r"\color{red}" + term @@ -3726,7 +3969,7 @@ def dictionary(self): self.objective_value(), self.basic_variables(), self.nonbasic_variables(), - "z") + self.problem().objective_name()) D._entering = self._entering D._leaving = self._leaving return D diff --git a/src/sage/numerical/linear_functions.pyx b/src/sage/numerical/linear_functions.pyx index a5d30a0241b..2e88237077b 100644 --- a/src/sage/numerical/linear_functions.pyx +++ b/src/sage/numerical/linear_functions.pyx @@ -78,9 +78,7 @@ See :trac:`12091` :: include "sage/ext/interrupt.pxi" from cpython.object cimport * -cdef extern from "limits.h": - long LONG_MAX - +from sage.misc.fast_methods cimport hash_by_id from sage.structure.parent cimport Parent from sage.structure.element cimport ModuleElement, Element from sage.misc.cachefunc import cached_function @@ -843,9 +841,9 @@ cdef class LinearFunction(ModuleElement): """ return (left-right).is_zero() - def __richcmp__(left, right, int op): + cdef _richcmp(left, right, int op): """ - Override the rich comparison. + Create an inequality or equality object. EXAMPLES:: @@ -853,14 +851,8 @@ cdef class LinearFunction(ModuleElement): sage: x = p.new_variable() sage: x[0].__le__(x[1]) # indirect doctest x_0 <= x_1 - """ - return (left)._richcmp(right, op) - cdef _richcmp(left, right, int op): - """ - Create a inequality or equality object. - - EXAMPLE:: + :: sage: p = MixedIntegerLinearProgram() sage: from sage.numerical.linear_functions import LinearFunction @@ -912,18 +904,14 @@ cdef class LinearFunction(ModuleElement): equality = (op == Py_EQ) cdef LinearConstraint left_constraint = LC(left, equality=equality) cdef LinearConstraint right_constraint = LC(right, equality=equality) - if op == Py_LT: + if op == Py_EQ or op == Py_LE or op == Py_GE: + return PyObject_RichCompare(left_constraint, right_constraint, op) + elif op == Py_LT: raise ValueError("strict < is not allowed, use <= instead.") - elif op == Py_EQ: - return left_constraint._richcmp(right_constraint, op) elif op == Py_GT: raise ValueError("strict > is not allowed, use >= instead.") - elif op == Py_LE: - return left_constraint._richcmp(right_constraint, op) elif op == Py_NE: raise ValueError("inequality != is not allowed, use one of <=, ==, >=.") - elif op == Py_GE: - return left_constraint._richcmp(right_constraint, op) else: assert(False) # unreachable @@ -941,20 +929,7 @@ cdef class LinearFunction(ModuleElement): sage: d[f] = 3 """ # see _cmp_() if you want to change the hash function - return id(self) % LONG_MAX - - def __cmp__(left, right): - """ - Part of the comparison framework. - - EXAMPLES:: - - sage: p = MixedIntegerLinearProgram() - sage: f = p({2 : 5, 3 : 2}) - sage: cmp(f, f) - 0 - """ - return (left)._cmp(right) + return hash_by_id( self) cpdef int _cmp_(left, Element right) except -2: """ @@ -1500,9 +1475,9 @@ cdef class LinearConstraint(Element): self._chained_comparator_hack_part2() return True - def __richcmp__(left, right, int op): + cdef _richcmp(py_left, py_right, int op): """ - Override the rich comparison. + Chain (in)equalities EXAMPLES:: @@ -1513,12 +1488,6 @@ cdef class LinearConstraint(Element): sage: b[0] <= 1 <= b[1] <= 2 <= b[2] <= 3 x_0 <= 1 <= x_1 <= 2 <= x_2 <= 3 """ - return (left)._richcmp(right, op) - - cdef _richcmp(py_left, py_right, int op): - """ - Chain (in)equalities - """ # print 'richcmp', py_left, ', ', py_right LC = py_left.parent() if not is_LinearConstraint(py_right): diff --git a/src/sage/numerical/linear_tensor_element.pyx b/src/sage/numerical/linear_tensor_element.pyx index 85690475c82..30ad818bc04 100644 --- a/src/sage/numerical/linear_tensor_element.pyx +++ b/src/sage/numerical/linear_tensor_element.pyx @@ -21,9 +21,7 @@ Here is an example of a linear function tensored with a vector space:: from cpython.object cimport * -cdef extern from "limits.h": - long LONG_MAX - +from sage.misc.fast_methods cimport hash_by_id from sage.structure.element cimport ModuleElement, RingElement from sage.numerical.linear_functions cimport LinearFunction, is_LinearFunction @@ -357,9 +355,9 @@ cdef class LinearTensor(ModuleElement): result[key] = b * coeff return self.parent()(result) - def __richcmp__(left, right, int op): + cdef _richcmp(left, right, int op): """ - Override the rich comparison. + Create an inequality or equality object. EXAMPLES:: @@ -368,14 +366,8 @@ cdef class LinearTensor(ModuleElement): sage: lt1 = x[1] * vector([2,3]) sage: lt0.__le__(lt1) # indirect doctest (1.0, 2.0)*x_0 <= (2.0, 3.0)*x_1 - """ - return (left)._richcmp(right, op) - cdef _richcmp(left, right, int op): - """ - Create a inequality or equality object. - - EXAMPLE:: + :: sage: mip. = MixedIntegerLinearProgram() sage: from sage.numerical.linear_functions import LinearFunction @@ -460,20 +452,7 @@ cdef class LinearTensor(ModuleElement): sage: d[f] = 3 """ # see _cmp_() if you want to change the hash function - return id(self) % LONG_MAX - - def __cmp__(left, right): - """ - Part of the comparison framework. - - EXAMPLES:: - - sage: p = MixedIntegerLinearProgram() - sage: f = p({2 : 5, 3 : 2}) - sage: cmp(f, f) - 0 - """ - return (left)._cmp(right) + return hash_by_id( self) cpdef int _cmp_(left, Element right) except -2: """ diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 80b33d56915..b645ab75f37 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -174,10 +174,13 @@ also implements the :class:`MIPSolverException` exception, as well as the :meth:`~MixedIntegerLinearProgram.add_constraint` | Adds a constraint to the ``MixedIntegerLinearProgram`` :meth:`~MixedIntegerLinearProgram.base_ring` | Return the base ring + :meth:`~MixedIntegerLinearProgram.best_known_objective_bound`| Return the value of the currently best known bound :meth:`~MixedIntegerLinearProgram.constraints` | Returns a list of constraints, as 3-tuples :meth:`~MixedIntegerLinearProgram.get_backend` | Returns the backend instance used :meth:`~MixedIntegerLinearProgram.get_max` | Returns the maximum value of a variable :meth:`~MixedIntegerLinearProgram.get_min` | Returns the minimum value of a variable + :meth:`~MixedIntegerLinearProgram.get_objective_value` | Return the value of the objective function + :meth:`~MixedIntegerLinearProgram.get_relative_objective_gap`| Return the relative objective gap of the best known solution :meth:`~MixedIntegerLinearProgram.get_values` | Return values found by the previous call to ``solve()`` :meth:`~MixedIntegerLinearProgram.is_binary` | Tests whether the variable ``e`` is binary :meth:`~MixedIntegerLinearProgram.is_integer` | Tests whether the variable is an integer @@ -2242,26 +2245,40 @@ cdef class MixedIntegerLinearProgram(SageObject): """ Return or define a solver parameter - The solver parameters are by essence solver-specific, which - means their meaning heavily depends on the solver used. + The solver parameters are by essence solver-specific, which means their + meaning heavily depends on the solver used. - (If you do not know which solver you are using, then you - use GLPK). + (If you do not know which solver you are using, then you use GLPK). Aliases: - Very common parameters have aliases making them - solver-independent. For example, the following:: + Very common parameters have aliases making them solver-independent. For + example, the following:: sage: p = MixedIntegerLinearProgram(solver = "GLPK") sage: p.solver_parameter("timelimit", 60) - Sets the solver to stop its computations after 60 seconds, and - works with GLPK, CPLEX and Gurobi. + Sets the solver to stop its computations after 60 seconds, and works + with GLPK, CPLEX and Gurobi. - ``"timelimit"`` -- defines the maximum time spent on a computation. Measured in seconds. + Another example is the ``"logfile"`` parameter, which is used to specify + the file in which computation logs are recorded. By default, the logs + are not recorded, and we can disable this feature providing an empty + filename. This is currently working with CPLEX and Gurobi:: + + sage: p = MixedIntegerLinearProgram(solver = "CPLEX") # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '' + sage: p.solver_parameter("logfile", "/dev/null") # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '/dev/null' + sage: p.solver_parameter("logfile", '') # optional - CPLEX + sage: p.solver_parameter("logfile") # optional - CPLEX + '' + Solver-specific parameters: - GLPK : We have implemented very close to comprehensive coverage of @@ -2369,6 +2386,102 @@ cdef class MixedIntegerLinearProgram(SageObject): """ return self._backend + def get_objective_value(self): + """ + Return the value of the objective function. + + .. NOTE:: + + Behaviour is undefined unless ``solve`` has been called before. + + EXAMPLE:: + + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: x, y = p[0], p[1] + sage: p.add_constraint(2*x + 3*y, max = 6) + sage: p.add_constraint(3*x + 2*y, max = 6) + sage: p.set_objective(x + y + 7) + sage: p.solve() # rel tol 1e-5 + 9.4 + sage: p.get_objective_value() # rel tol 1e-5 + 9.4 + """ + return self._backend.get_objective_value() + + def best_known_objective_bound(self): + r""" + Return the value of the currently best known bound. + + This method returns the current best upper (resp. lower) bound on the + optimal value of the objective function in a maximization + (resp. minimization) problem. It is equal to the output of + :meth:get_objective_value if the MILP found an optimal solution, but it + can differ if it was interrupted manually or after a time limit (cf + :meth:solver_parameter). + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: g = graphs.CubeGraph(9) + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: p.solver_parameter("mip_gap_tolerance",100) + sage: b = p.new_variable(binary=True) + sage: p.set_objective(p.sum(b[v] for v in g)) + sage: for v in g: + ....: p.add_constraint(b[v]+p.sum(b[u] for u in g.neighbors(v)) <= 1) + sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution + sage: p.solve() # rel tol 100 + 1.0 + sage: p.best_known_objective_bound() # random + 48.0 + """ + return self._backend.best_known_objective_bound() + + def get_relative_objective_gap(self): + r""" + Return the relative objective gap of the best known solution. + + For a minimization problem, this value is computed by + `(\texttt{bestinteger} - \texttt{bestobjective}) / (1e-10 + + |\texttt{bestobjective}|)`, where ``bestinteger`` is the value returned + by :meth:`~MixedIntegerLinearProgram.get_objective_value` and + ``bestobjective`` is the value returned by + :meth:`~MixedIntegerLinearProgram.best_known_objective_bound`. For a + maximization problem, the value is computed by `(\texttt{bestobjective} + - \texttt{bestinteger}) / (1e-10 + |\texttt{bestobjective}|)`. + + .. NOTE:: + + Has no meaning unless ``solve`` has been called before. + + EXAMPLE:: + + sage: g = graphs.CubeGraph(9) + sage: p = MixedIntegerLinearProgram(solver="GLPK") + sage: p.solver_parameter("mip_gap_tolerance",100) + sage: b = p.new_variable(binary=True) + sage: p.set_objective(p.sum(b[v] for v in g)) + sage: for v in g: + ....: p.add_constraint(b[v]+p.sum(b[u] for u in g.neighbors(v)) <= 1) + sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution + sage: p.solve() # rel tol 100 + 1.0 + sage: p.get_relative_objective_gap() # random + 46.99999999999999 + + TESTS: + + Just make sure that the variable *has* been defined, and is not just + undefined:: + + sage: p.get_relative_objective_gap() > 1 + True + """ + return self._backend.get_relative_objective_gap() + class MIPSolverException(RuntimeError): r""" @@ -2402,7 +2515,7 @@ class MIPSolverException(RuntimeError): sage: p.solve() Traceback (most recent call last): ... - MIPSolverException: 'GLPK : There is no feasible integer solution to this Linear Program' + MIPSolverException: 'GLPK : Problem has no feasible solution' No integer solution:: @@ -2418,7 +2531,7 @@ class MIPSolverException(RuntimeError): sage: p.solve() Traceback (most recent call last): ... - MIPSolverException: 'GLPK : There is no feasible integer solution to this Linear Program' + MIPSolverException: 'GLPK : Problem has no feasible solution' """ self.value = value diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index 83cb4995865..29b92572d44 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -117,6 +117,7 @@ import struct import zlib +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.misc.temporary_file import tmp_dir, tmp_filename, graphics_filename import plot @@ -139,7 +140,7 @@ def animate(frames, **kwds): """ return Animation(frames, **kwds) -class Animation(SageObject): +class Animation(WithEqualityById, SageObject): r""" Return an animation of a sequence of plots of objects. @@ -215,6 +216,11 @@ class Animation(SageObject): sage: a._frames 1-epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin = pi; axmax = 0 - aymin = 3*pi/2; aymax = pi/2 - - elif cos_angle < -1+epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin=0; axmax=pi - aymin=pi/2; aymax=3*pi/2 - - elif sin_angle > 1-epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = pi/2; axmax = 3*pi/2 - aymin = pi; aymax = 0 - - elif sin_angle < -1+epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = 3*pi/2; axmax = pi/2 - aymin = 0; aymax = pi + if cos_angle > 1 - epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = pi + axmax = 0 + aymin = 3 * pi / 2 + aymax = pi / 2 + + elif cos_angle < -1 + epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = 0 + axmax = pi + aymin = pi / 2 + aymax = 3 * pi / 2 + + elif sin_angle > 1 - epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = pi / 2 + axmax = 3 * pi / 2 + aymin = pi + aymax = 0 + + elif sin_angle < -1 + epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = 3 * pi / 2 + axmax = pi / 2 + aymin = 0 + aymax = pi else: tan_angle = sin_angle / cos_angle - axmax = atan(-r2/r1*tan_angle) - if axmax < 0: axmax += twopi - xmax = ( - r1 * cos_angle * cos(axmax) - - r2 * sin_angle * sin(axmax)) + axmax = atan(-r2 / r1 * tan_angle) + if axmax < 0: + axmax += twopi + xmax = (r1 * cos_angle * cos(axmax) - + r2 * sin_angle * sin(axmax)) if xmax < 0: xmax = -xmax - axmax = fmod(axmax+pi,twopi) + axmax = fmod(axmax + pi, twopi) xmin = -xmax - axmin = fmod(axmax + pi,twopi) + axmin = fmod(axmax + pi, twopi) - aymax = atan(r2/(r1*tan_angle)) - if aymax < 0: aymax += twopi - ymax = ( - r1 * sin_angle * cos(aymax) + - r2 * cos_angle * sin(aymax)) + aymax = atan(r2 / (r1 * tan_angle)) + if aymax < 0: + aymax += twopi + ymax = (r1 * sin_angle * cos(aymax) + + r2 * cos_angle * sin(aymax)) if ymax < 0: ymax = -ymax - aymax = fmod(aymax+pi,twopi) + aymax = fmod(aymax + pi, twopi) ymin = -ymax aymin = fmod(aymax + pi, twopi) - if s < twopi-epsilon: # bb determined by the sector - def is_cyclic_ordered(x1,x2,x3): - return ( - (x1 < x2 and x2 < x3) or - (x2 < x3 and x3 < x1) or - (x3 < x1 and x1 < x2)) - - x1 = cos_angle*r1*cos(s1) - sin_angle*r2*sin(s1) - x2 = cos_angle*r1*cos(s2) - sin_angle*r2*sin(s2) - y1 = sin_angle*r1*cos(s1) + cos_angle*r2*sin(s1) - y2 = sin_angle*r1*cos(s2) + cos_angle*r2*sin(s2) - - if is_cyclic_ordered(s1,s2,axmin): xmin = min(x1,x2) - if is_cyclic_ordered(s1,s2,aymin): ymin = min(y1,y2) - if is_cyclic_ordered(s1,s2,axmax): xmax = max(x1,x2) - if is_cyclic_ordered(s1,s2,aymax): ymax = max(y1,y2) + if s < twopi - epsilon: # bb determined by the sector + def is_cyclic_ordered(x1, x2, x3): + return ((x1 < x2 and x2 < x3) or + (x2 < x3 and x3 < x1) or + (x3 < x1 and x1 < x2)) + + x1 = cos_angle * r1 * cos(s1) - sin_angle * r2 * sin(s1) + x2 = cos_angle * r1 * cos(s2) - sin_angle * r2 * sin(s2) + y1 = sin_angle * r1 * cos(s1) + cos_angle * r2 * sin(s1) + y2 = sin_angle * r1 * cos(s2) + cos_angle * r2 * sin(s2) + + if is_cyclic_ordered(s1, s2, axmin): + xmin = min(x1, x2) + if is_cyclic_ordered(s1, s2, aymin): + ymin = min(y1, y2) + if is_cyclic_ordered(s1, s2, axmax): + xmax = max(x1, x2) + if is_cyclic_ordered(s1, s2, aymax): + ymax = max(y1, y2) return minmax_data([self.x + xmin, self.x + xmax], [self.y + ymin, self.y + ymax], @@ -226,15 +249,75 @@ def _allowed_options(self): sage: p[0]._allowed_options()['alpha'] 'How transparent the figure is.' """ - return {'alpha':'How transparent the figure is.', - 'thickness':'How thick the border of the arc is.', - 'hue':'The color given as a hue.', - 'rgbcolor':'The color', - 'zorder':'2D only: The layer level in which to draw', - 'linestyle':"2D only: The style of the line, which is one of " + return {'alpha': 'How transparent the figure is.', + 'thickness': 'How thick the border of the arc is.', + 'hue': 'The color given as a hue.', + 'rgbcolor': 'The color', + 'zorder': '2D only: The layer level in which to draw', + 'linestyle': "2D only: The style of the line, which is one of " "'dashed', 'dotted', 'solid', 'dashdot', or '--', ':', '-', '-.', " "respectively."} + def _matplotlib_arc(self): + """ + Return ``self`` as a matplotlib arc object. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: Arc(2,3,2.2,2.2,0,2,3,{})._matplotlib_arc() + + """ + import matplotlib.patches as patches + p = patches.Arc((self.x, self.y), + 2. * self.r1, + 2. * self.r2, + fmod(self.angle, 2 * pi) * (180. / pi), + self.s1 * (180. / pi), + self.s2 * (180. / pi)) + return p + + def bezier_path(self): + """ + Return ``self`` as a Bezier path. + + This is needed to concatenate arcs, in order to + create hyperbolic polygons. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, + ....: 'linestyle':'--'} + sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() + Graphics object consisting of 1 graphics primitive + + sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") + sage: b = a[0].bezier_path() + sage: b[0] + Bezier path from (1.618..., 0.5877...) to (-0.5176..., 0.9659...) + """ + from sage.plot.bezier_path import BezierPath + from sage.plot.graphics import Graphics + ma = self._matplotlib_arc() + transform = ma.get_transform().get_matrix() + cA, cC, cE = transform[0] + cB, cD, cF = transform[1] + points = [] + for u in ma._path.vertices: + x, y = list(u) + points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] + cutlist = [points[0: 4]] + N = 4 + while N < len(points): + cutlist += [points[N: N + 3]] + N += 3 + g = Graphics() + opt = self.options() + opt['fill'] = False + g.add_primitive(BezierPath(cutlist, opt)) + return g + def _repr_(self): """ String representation of ``Arc`` primitive. @@ -245,7 +328,7 @@ def _repr_(self): sage: print Arc(2,3,2.2,2.2,0,2,3,{}) Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0) """ - return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" %(self.x,self.y,self.r1,self.r2,self.angle,self.s1,self.s2) + return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" % (self.x, self.y, self.r1, self.r2, self.angle, self.s1, self.s2) def _render_on_subplot(self, subplot): """ @@ -254,26 +337,19 @@ def _render_on_subplot(self, subplot): sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ - import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle - options = self.options() - p = patches.Arc( - (self.x,self.y), - 2.*self.r1, - 2.*self.r2, - fmod(self.angle,2*pi)*(180./pi), - self.s1*(180./pi), - self.s2*(180./pi)) + p = self._matplotlib_arc() p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) - z = int(options.pop('zorder',1)) + z = int(options.pop('zorder', 1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) - p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) + p.set_linestyle(get_matplotlib_linestyle(options['linestyle'], + return_type='long')) p.set_edgecolor(c) subplot.add_patch(p) @@ -289,10 +365,11 @@ def plot3d(self): """ raise NotImplementedError + @rename_keyword(color='rgbcolor') -@options(alpha=1, thickness=1, linestyle='solid', zorder=5,rgbcolor='blue', +@options(alpha=1, thickness=1, linestyle='solid', zorder=5, rgbcolor='blue', aspect_ratio=1.0) -def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): +def arc(center, r1, r2=None, angle=0.0, sector=(0.0, 2 * pi), **options): r""" An arc (that is a portion of a circle or an ellipse) @@ -372,19 +449,19 @@ def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' - if len(center)==2: - if r2 is None: r2 = r1 + if len(center) == 2: + if r2 is None: + r2 = r1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) if len(sector) != 2: raise ValueError("the sector must consist of two angles") g.add_primitive(Arc( - center[0],center[1], - r1,r2, + center[0], center[1], + r1, r2, angle, - sector[0],sector[1], + sector[0], sector[1], options)) return g - elif len(center)==3: + elif len(center) == 3: raise NotImplementedError - diff --git a/src/sage/plot/contour_plot.py b/src/sage/plot/contour_plot.py index 296e0c1b1d5..36710510134 100644 --- a/src/sage/plot/contour_plot.py +++ b/src/sage/plot/contour_plot.py @@ -910,14 +910,13 @@ def region_plot(f, xrange, yrange, plot_points, incol, outcol, bordercol, border Graphics object consisting of 2 graphics primitives To check that :trac:`18286` is fixed:: + sage: x, y = var('x, y') sage: region_plot([x == 0], (x, -1, 1), (y, -1, 1)) Graphics object consisting of 1 graphics primitive sage: region_plot([x^2+y^2==1, x, +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -70,20 +71,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(),z0.imag()), (z3.real(),z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index e6465183e04..3bfbdcb3404 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -8,7 +8,8 @@ """ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien , -# 2014 Vincent Delecroix <20100.delecroix@gmail.com> +# 2014 Vincent Delecroix <20100.delecroix@gmail.com>, +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -85,20 +86,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(), z0.imag()), (z3.real(), z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index daf8f7d92df..907ef6cdaea 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -530,6 +530,7 @@ def set_locs(self, locs): Set the locations for the ticks that are not skipped. EXAMPLES:: + sage: from sage.plot.plot import SelectiveFormatter sage: import matplotlib.ticker sage: formatter=SelectiveFormatter(matplotlib.ticker.Formatter(),skip_values=[0,200]) diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index 44f9b6e0694..e27c8a7e67d 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -29,8 +29,8 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** - from cpython.list cimport * +from cpython.object cimport PyObject import os from functools import reduce @@ -42,6 +42,8 @@ from sage.misc.misc import sage_makedirs from sage.env import SAGE_LOCAL from sage.doctest import DOCTEST_MODE +from sage.misc.fast_methods cimport hash_by_id + from sage.modules.free_module_element import vector from sage.rings.real_double import RDF @@ -79,6 +81,16 @@ cdef class Graphics3d(SageObject): """ self._extra_kwds = dict() + def __hash__(self): + r""" + TESTS:: + + sage: from sage.plot.plot3d.base import Graphics3d + sage: hash(Graphics3d()) # random + 140658972348064 + """ + return hash_by_id( self) + def _repr_(self): """ Return a string representation. diff --git a/src/sage/plot/plot3d/platonic.py b/src/sage/plot/plot3d/platonic.py index 78f95ffd4db..2f7fd195629 100644 --- a/src/sage/plot/plot3d/platonic.py +++ b/src/sage/plot/plot3d/platonic.py @@ -1,21 +1,21 @@ r""" Platonic Solids -EXAMPLES: The five platonic solids in a row; +EXAMPLES: The five platonic solids in a row: :: - sage: G = tetrahedron((0,-3.5,0), color='blue') + cube((0,-2,0),color=(.25,0,.5)) +\ - octahedron(color='red') + dodecahedron((0,2,0), color='orange') +\ - icosahedron(center=(0,4,0), color='yellow') + sage: G = tetrahedron((0,-3.5,0), color='blue') + cube((0,-2,0),color=(.25,0,.5)) + sage: G += octahedron(color='red') + dodecahedron((0,2,0), color='orange') + sage: G += icosahedron(center=(0,4,0), color='yellow') sage: G.show(aspect_ratio=[1,1,1]) All the platonic solids in the same place:: - sage: G = tetrahedron(color='blue',opacity=0.7) + \ - cube(color=(.25,0,.5), opacity=0.7) +\ - octahedron(color='red', opacity=0.7) + \ - dodecahedron(color='orange', opacity=0.7) + icosahedron(opacity=0.7) + sage: G = tetrahedron(color='blue',opacity=0.7) + sage: G += cube(color=(.25,0,.5), opacity=0.7) + sage: G += octahedron(color='red', opacity=0.7) + sage: G += dodecahedron(color='orange', opacity=0.7) + icosahedron(opacity=0.7) sage: G.show(aspect_ratio=[1,1,1]) Display nice faces only:: @@ -46,8 +46,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** - - from sage.rings.all import RDF from sage.matrix.constructor import matrix @@ -55,6 +53,7 @@ from shapes2 import frame3d from index_face_set import IndexFaceSet + def index_face_set(face_list, point_list, enclosed, **kwds): """ Helper function that creates ``IndexFaceSet`` object for the @@ -62,14 +61,14 @@ def index_face_set(face_list, point_list, enclosed, **kwds): INPUT: - - ``face_list`` - list of faces, given explicitly from the - solid invocation + - ``face_list`` -- list of faces, given explicitly from the + solid invocation - - ``point_list`` - list of points, given explicitly from the - solid invocation + - ``point_list`` -- list of points, given explicitly from the + solid invocation - - ``enclosed`` - boolean (default passed is always True - for these solids) + - ``enclosed`` -- boolean (default passed is always ``True`` + for these solids) TESTS: @@ -92,7 +91,7 @@ def index_face_set(face_list, point_list, enclosed, **kwds): center = kwds['center'] del kwds['center'] else: - center = (0,0,0) + center = (0, 0, 0) if 'size' in kwds: size = kwds['size'] del kwds['size'] @@ -101,6 +100,7 @@ def index_face_set(face_list, point_list, enclosed, **kwds): I = IndexFaceSet(face_list, point_list, enclosed=enclosed, **kwds) return prep(I, center, size, kwds) + def prep(G, center, size, kwds): """ Helper function that scales and translates the platonic @@ -108,46 +108,46 @@ def prep(G, center, size, kwds): INPUT: - - ``center`` - 3-tuple indicating the center (default passed - from :func:`index_face_set` is the origin `(0,0,0)`) + - ``center`` -- 3-tuple indicating the center (default passed + from :func:`index_face_set` is the origin `(0,0,0)`) - - ``size`` - number indicating amount to scale by (default - passed from :func:`index_face_set` is 1) + - ``size`` -- number indicating amount to scale by (default + passed from :func:`index_face_set` is 1) - - ``kwds`` - a dictionary of keywords, passed from solid - invocation by :func:`index_face_set` + - ``kwds`` -- a dictionary of keywords, passed from solid + invocation by :func:`index_face_set` TESTS: Verify that scaling and moving the center work together properly, - and that keywords are passed (see Trac #10796):: + and that keywords are passed (see :trac:`10796`):: sage: octahedron(center=(2,0,0),size=2,color='red') Graphics3d Object """ if size != 1: G = G.scale(size) - if center != (0,0,0): + if center != (0, 0, 0): G = G.translate(center) G._set_extra_kwds(kwds) return G -def tetrahedron(center=(0,0,0), size=1, **kwds): + +def tetrahedron(center=(0, 0, 0), size=1, **kwds): """ A 3d tetrahedron. INPUT: - - ``center`` - (default: (0,0,0)) + - ``center`` -- (default: (0,0,0)) - - ``size`` - (default: 1) + - ``size`` -- (default: 1) - ``color`` -- a string (``"red"``, ``"green"``, etc) or a tuple (r, g, b) with r, g, b numbers between 0 and 1 - - ``opacity`` - (default: 1) if less than 1 then is - transparent - + - ``opacity`` -- (default: 1) if less than 1 then is + transparent EXAMPLES: A default colored tetrahedron at the origin:: @@ -179,7 +179,7 @@ def tetrahedron(center=(0,0,0), size=1, **kwds): sage: tetrahedron(color='red') + tetrahedron((0,0,-2)).scale([1,1,-1]) Graphics3d Object - A Dodecahedral complex of 5 tetrahedrons (a more elaborate examples + A Dodecahedral complex of 5 tetrahedrons (a more elaborate example from Peter Jipsen):: sage: v=(sqrt(5.)/2-5/6, 5/6*sqrt(3.)-sqrt(15.)/2, sqrt(5.)/3) @@ -196,7 +196,7 @@ def tetrahedron(center=(0,0,0), size=1, **kwds): - Robert Bradshaw and William Stein """ RR = RDF - one = RR(1) + one = RR.one() sqrt2 = RR(2).sqrt() sqrt6 = RR(6).sqrt() point_list = [(0,0,1), @@ -205,34 +205,34 @@ def tetrahedron(center=(0,0,0), size=1, **kwds): ( -sqrt2/3, -sqrt6/3, -one/3)] face_list = [[0,1,2],[1,3,2],[0,2,3],[0,3,1]] if 'aspect_ratio' not in kwds: - kwds['aspect_ratio'] = [1,1,1] + kwds['aspect_ratio'] = [1, 1, 1] return index_face_set(face_list, point_list, enclosed=True, center=center, size=size, **kwds) -def cube(center=(0,0,0), size=1, color=None, frame_thickness=0, frame_color=None, **kwds): + +def cube(center=(0, 0, 0), size=1, color=None, frame_thickness=0, + frame_color=None, **kwds): """ A 3D cube centered at the origin with default side lengths 1. INPUT: + - ``center`` -- (default: (0,0,0)) - - ``center`` - (default: (0,0,0)) + - ``size`` -- (default: 1) the side lengths of the + cube - - ``size`` - (default: 1) the side lengths of the - cube + - ``color`` -- a string that describes a color; this + can also be a list of 3-tuples or strings length 6 or 3, in which + case the faces (and oppositive faces) are colored. - - ``color`` - a string that describes a color; this - can also be a list of 3-tuples or strings length 6 or 3, in which - case the faces (and oppositive faces) are colored. + - ``frame_thickness`` -- (default: 0) if positive, + then thickness of the frame - - ``frame_thickness`` - (default: 0) if positive, - then thickness of the frame - - - ``frame_color`` - (default: None) if given, gives - the color of the frame - - - ``opacity`` - (default: 1) if less than 1 then it's - transparent + - ``frame_color`` -- (default: None) if given, gives + the color of the frame + - ``opacity`` -- (default: 1) if less than 1 then it's + transparent EXAMPLES: @@ -286,7 +286,7 @@ def cube(center=(0,0,0), size=1, color=None, frame_thickness=0, frame_color=None sage: c.show(viewer='tachyon') - This shows #11272 has been fixed:: + This shows :trac:`11272` has been fixed:: sage: cube(center=(10, 10, 10), size=0.5).bounding_box() ((9.75, 9.75, 9.75), (10.25, 10.25, 10.25)) @@ -300,7 +300,7 @@ def cube(center=(0,0,0), size=1, color=None, frame_thickness=0, frame_color=None else: if color is not None: kwds['color'] = color - B = Box(0.5,0.5,0.5, **kwds) + B = Box(0.5, 0.5, 0.5, **kwds) if frame_thickness > 0: if frame_color is None: B += frame3d((-0.5,-0.5,-0.5),(0.5,0.5,0.5), thickness=frame_thickness) @@ -308,53 +308,51 @@ def cube(center=(0,0,0), size=1, color=None, frame_thickness=0, frame_color=None B += frame3d((-0.5,-0.5,-0.5),(0.5,0.5,0.5), thickness=frame_thickness, color=frame_color) return prep(B, center, size, kwds) -def octahedron(center=(0,0,0), size=1, **kwds): + +def octahedron(center=(0, 0, 0), size=1, **kwds): r""" Return an octahedron. INPUT: + - ``center`` -- (default: (0,0,0)) - - ``center`` - (default: (0,0,0)) + - ``size`` -- (default: 1) - - ``size`` - (default: 1) - - - ``color`` - a string that describes a color; this - can also be a list of 3-tuples or strings length 6 or 3, in which - case the faces (and oppositive faces) are colored. - - - ``opacity`` - (default: 1) if less than 1 then is - transparent + - ``color`` -- a string that describes a color; this + can also be a list of 3-tuples or strings length 6 or 3, in which + case the faces (and oppositive faces) are colored. + - ``opacity`` -- (default: 1) if less than 1 then is + transparent EXAMPLES:: - sage: octahedron((1,4,3), color='orange') + \ - ....: octahedron((0,2,1), size=2, opacity=0.6) + sage: G = octahedron((1,4,3), color='orange') + sage: G += octahedron((0,2,1), size=2, opacity=0.6) + sage: G Graphics3d Object """ if 'aspect_ratio' not in kwds: - kwds['aspect_ratio'] = [1,1,1] + kwds['aspect_ratio'] = [1, 1, 1] return prep(Box(1,1,1).dual(**kwds), center, size, kwds) -def dodecahedron(center=(0,0,0), size=1, **kwds): + +def dodecahedron(center=(0, 0, 0), size=1, **kwds): r""" A dodecahedron. INPUT: + - ``center`` -- (default: (0,0,0)) - - ``center`` - (default: (0,0,0)) - - - ``size`` - (default: 1) - - - ``color`` - a string that describes a color; this - can also be a list of 3-tuples or strings length 6 or 3, in which - case the faces (and oppositive faces) are colored. + - ``size`` -- (default: 1) - - ``opacity`` - (default: 1) if less than 1 then is - transparent + - ``color`` -- a string that describes a color; this + can also be a list of 3-tuples or strings length 6 or 3, in which + case the faces (and oppositive faces) are colored. + - ``opacity`` -- (default: 1) if less than 1 then is transparent EXAMPLES: A plain Dodecahedron:: @@ -363,8 +361,9 @@ def dodecahedron(center=(0,0,0), size=1, **kwds): A translucent dodecahedron that contains a black sphere:: - sage: dodecahedron(color='orange', opacity=0.8) + \ - ....: sphere(size=0.5, color='black') + sage: G = dodecahedron(color='orange', opacity=0.8) + sage: G += sphere(size=0.5, color='black') + sage: G Graphics3d Object CONSTRUCTION: This is how we construct a dodecahedron. We let one @@ -442,38 +441,34 @@ def dodecahedron(center=(0,0,0), size=1, **kwds): # return Graphics3dGroup(vertex_spheres) -def icosahedron(center=(0,0,0), size=1, **kwds): +def icosahedron(center=(0, 0, 0), size=1, **kwds): r""" An icosahedron. INPUT: + - ``center`` -- (default: (0, 0, 0)) - - ``center`` - (default: (0,0,0)) + - ``size`` -- (default: 1) - - ``size`` - (default: 1) - - - ``color`` - a string that describes a color; this - can also be a list of 3-tuples or strings length 6 or 3, in which - case the faces (and oppositive faces) are colored. - - - ``opacity`` - (default: 1) if less than 1 then is - transparent + - ``color`` -- a string that describes a color; this + can also be a list of 3-tuples or strings length 6 or 3, in which + case the faces (and oppositive faces) are colored. + - ``opacity`` -- (default: 1) if less than 1 then is transparent EXAMPLES:: sage: icosahedron() Graphics3d Object - Two icosahedrons at different positions of different sizes. + Two icosahedrons at different positions of different sizes. :: - :: - - sage: icosahedron((-1/2,0,1), color='orange') + \ - ....: icosahedron((2,0,1), size=1/2, aspect_ratio=[1,1,1]) + sage: p = icosahedron((-1/2,0,1), color='orange') + sage: p += icosahedron((2,0,1), size=1/2, aspect_ratio=[1,1,1]) + sage: p Graphics3d Object """ if 'aspect_ratio' not in kwds: - kwds['aspect_ratio'] = [1,1,1] + kwds['aspect_ratio'] = [1, 1, 1] return prep(dodecahedron().dual(**kwds), center, size, kwds) diff --git a/src/sage/plot/plot3d/tachyon.py b/src/sage/plot/plot3d/tachyon.py index a8ef1aa0d23..185a5c82e6e 100644 --- a/src/sage/plot/plot3d/tachyon.py +++ b/src/sage/plot/plot3d/tachyon.py @@ -139,6 +139,7 @@ from sage.interfaces.tachyon import tachyon_rt +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.misc.misc import SAGE_TMP @@ -153,7 +154,7 @@ from math import sqrt -class Tachyon(SageObject): +class Tachyon(WithEqualityById, SageObject): r""" Create a scene the can be rendered using the Tachyon ray tracer. @@ -334,6 +335,11 @@ class Tachyon(SageObject): ....: tt = 't1' ....: T.sphere((q, q/3+.3*sin(3*q), .1+.3*cos(3*q)), .1, tt) sage: T.show() + + TESTS:: + + sage: hash(Tachyon()) # random + 140658972348064 """ def __init__(self, xres=350, yres=350, @@ -379,7 +385,7 @@ def __init__(self, raise ValueError('camera_center and look_at coincide') else: self._viewdir = viewdir - + def save_image(self, filename=None, *args, **kwds): r""" Save an image representation of ``self``. @@ -519,7 +525,7 @@ def show(self, **kwds): and we are simply shown the plot. :: - + sage: h = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: h.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) @@ -533,7 +539,7 @@ def show(self, **kwds): displays our graph. :: - + sage: s = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: s.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) @@ -552,7 +558,7 @@ def show(self, **kwds): the plot. :: - + sage: set_verbose(0) sage: d = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: d.light((4.4,-4.4,4.4), 0.2, (1,1,1)) diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index 66952d2827a..f85574aabe2 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -35,6 +35,7 @@ - Robert Bradshaw (2007-07-07) Initial version. """ +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.plot.colors import Color @@ -238,7 +239,7 @@ def parse_color(info, base=None): return (float(info*r), float(info*g), float(info*b)) -class Texture_class(SageObject): +class Texture_class(WithEqualityById, SageObject): r""" Construction of a texture. @@ -266,6 +267,9 @@ class Texture_class(SageObject): sage: Texture(opacity=1/3).opacity 0.3333333333333333 + + sage: hash(Texture()) # random + 42 """ def __init__(self, id, color=(.4, .4, 1), opacity=1, ambient=0.5, diffuse=1, specular=0, shininess=1, name=None, **kwds): r""" diff --git a/src/sage/plot/primitive.py b/src/sage/plot/primitive.py index 80b740e6f52..7deae3ff4cf 100644 --- a/src/sage/plot/primitive.py +++ b/src/sage/plot/primitive.py @@ -17,10 +17,11 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.misc.misc import verbose -class GraphicPrimitive(SageObject): +class GraphicPrimitive(WithEqualityById, SageObject): """ Base class for graphics primitives, e.g., things that knows how to draw themselves in 2D. @@ -34,6 +35,11 @@ class GraphicPrimitive(SageObject): Line defined by 2 points sage: type(P[0]) + + TESTS:: + + sage: hash(circle((0,0),1)) # random + 42 """ def __init__(self, options): """ @@ -50,6 +56,7 @@ def __init__(self, options): """ self._options = options + def _allowed_options(self): """ Return the allowed options for a graphics primitive. diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index 4e928ce6894..a2e5e6a060b 100644 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -241,6 +241,19 @@ def __call__(self, *args): x, y = args return (self._a * x + self._b * y) * x + self._c * y**2 + def __hash__(self): + r""" + TESTS:: + + sage: hash(BinaryQF([2,2,3])) + 802 + sage: hash(BinaryQF([2,3,2])) + 562 + sage: hash(BinaryQF([3,2,2])) + 547 + """ + return hash(self._a) ^ (hash(self._b) << 4) ^ (hash(self._c) << 8) + def __cmp__(self, right): """ Returns True if self and right are identical: the same coefficients. diff --git a/src/sage/quadratic_forms/extras.py b/src/sage/quadratic_forms/extras.py index 34c94471559..2ec17fc4f7d 100644 --- a/src/sage/quadratic_forms/extras.py +++ b/src/sage/quadratic_forms/extras.py @@ -1,3 +1,4 @@ +"Quadratic form extras" from sage.matrix.constructor import matrix from sage.matrix.matrix import is_Matrix @@ -81,12 +82,15 @@ def extend_to_primitive(A_input): Author(s): Gonzalo Tornaria and Jonathan Hanke. INPUT: - a matrix, or a list of length n vectors (in the same space) + + a matrix, or a list of length n vectors (in the same space) OUTPUT: - a square matrix, or a list of n vectors (resp.) - EXAMPLES: + a square matrix, or a list of n vectors (resp.) + + EXAMPLES:: + sage: A = Matrix(ZZ, 3, 2, range(6)) sage: extend_to_primitive(A) [ 0 1 0] diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 1cae9561655..1f978f0f1ad 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -1,3 +1,5 @@ +"Genus" + #***************************************************************************** # Copyright (C) 2007 David Kohel # Gabriele Nebe @@ -15,21 +17,23 @@ from sage.rings.integer import Integer from sage.rings.finite_rings.constructor import FiniteField + def Genus(A): - """ - Given a nonsingular symmetric matrix A, return the genus of A. + r""" + Given a nonsingular symmetric matrix `A`, return the genus of `A`. INPUT: - A -- a symmetric matrix with coefficients in ZZ + + - `A` -- a symmetric matrix with coefficients in `\ZZ` OUTPUT: - A GenusSymbol_global_ring object, encoding the Conway-Sloane - genus symbol of the quadratic form whose Gram matrix is A. - EXAMPLES: - sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring - sage: from sage.quadratic_forms.genera.genus import Genus + A ``GenusSymbol_global_ring`` object, encoding the Conway-Sloane + genus symbol of the quadratic form whose Gram matrix is `A`. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import Genus sage: A = Matrix(ZZ, 2, 2, [1,1,1,2]) sage: Genus(A) Genus of [1 1] @@ -38,20 +42,22 @@ def Genus(A): return GenusSymbol_global_ring(A) - def LocalGenusSymbol(A,p): """ Given a nonsingular symmetric matrix A, return the local symbol of A at the prime p. INPUT: - A -- a symmetric matrix with coefficients in ZZ - p -- an integer prime p > 0 + + - A -- a symmetric matrix with coefficients in ZZ + - p -- an integer prime p > 0 OUTPUT: - A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane - genus symbol at p of the quadratic form whose Gram matrix is A. - EXAMPLES: + A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane + genus symbol at p of the quadratic form whose Gram matrix is A. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol sage: A = Matrix(ZZ, 2, 2, [1,1,1,2]) @@ -78,12 +84,15 @@ def is_GlobalGenus(G): True in G represents the genus of a global quadratic form or lattice. INPUT: - G -- GenusSymbol_global_ring object + + - G -- GenusSymbol_global_ring object OUTPUT: - boolean - EXAMPLES: + boolean + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: from sage.quadratic_forms.genera.genus import Genus, is_GlobalGenus @@ -127,13 +136,16 @@ def is_2_adic_genus(genus_symbol_quintuple_list): check whether it is the 2-adic symbol of a 2-adic form. INPUT: - genus_symbol_quintuple_list -- a quintuple of integers (with certain - restrictions). + + - genus_symbol_quintuple_list -- a quintuple of integers (with certain + restrictions). OUTPUT: - boolean - EXAMPLES: + boolean + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol sage: A = Matrix(ZZ, 2, 2, [1,1,1,2]) @@ -189,13 +201,16 @@ def canonical_2_adic_compartments(genus_symbol_quintuple_list): components all (scaled) of type I (i.e. odd). INPUT: - genus_symbol_quintuple_list -- a quintuple of integers (with certain - restrictions). + + - genus_symbol_quintuple_list -- a quintuple of integers (with certain + restrictions). OUTPUT: - a list of lists of integers. - EXAMPLES: + a list of lists of integers. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_compartments @@ -224,6 +239,7 @@ def canonical_2_adic_compartments(genus_symbol_quintuple_list): [] NOTES: + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. """ symbol = genus_symbol_quintuple_list @@ -258,14 +274,17 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): Jordan components) is (scaled) of type I (i.e. odd). INPUT: - genus_symbol_quintuple_list -- a quintuple of integers (with certain - restrictions). - compartments -- a list of lists of distinct integers (optional) + + - genus_symbol_quintuple_list -- a quintuple of integers (with certain + restrictions). + - compartments -- a list of lists of distinct integers (optional) OUTPUT: - a list of lists of distinct integers. - EXAMPLES: + a list of lists of distinct integers. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_compartments sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_trains @@ -304,11 +323,13 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): sage: canonical_2_adic_trains(G2.symbol_tuple_list()) [] - NOTES: + .. NOTE:: + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add a non-trivial example in the doctest here! + .. TODO:: + + Add a non-trivial example in the doctest here! """ ## Recompute compartments if none are passed. if compartments is None: @@ -355,14 +376,17 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): "oddity fusion" equivalences. INPUT: - genus_symbol_quintuple_list -- a quintuple of integers (with certain - restrictions). - compartments -- a list of lists of distinct integers (optional) + + - genus_symbol_quintuple_list -- a quintuple of integers (with certain + restrictions). + - compartments -- a list of lists of distinct integers (optional) OUTPUT: - a list of lists of distinct integers. - EXAMPLES: + a list of lists of distinct integers. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_reduction @@ -390,11 +414,13 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): sage: canonical_2_adic_reduction(G2.symbol_tuple_list()) [[0, 2, -1, 0, 0]] - NOTES: + .. NOTE:: + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add an example where sign walking occurs! + .. TODO:: + + Add an example where sign walking occurs! """ canonical_symbol = genus_symbol_quintuple_list # Canonical determinants: @@ -438,12 +464,15 @@ def basis_complement(B): matrix whose rows form a basis complement (to the rows of B). INPUT: - B -- matrix over a field in row echelon form + + - B -- matrix over a field in row echelon form OUTPUT: - a rectangular matrix over a field - EXAMPLES: + a rectangular matrix over a field + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import basis_complement sage: A = Matrix(ZZ, 2, 2, [1,1,1,1]) @@ -476,16 +505,20 @@ def signature_pair_of_matrix(A): """ Computes the signature pair (p, n) of a non-degenerate symmetric matrix, where - p = number of positive eigenvalues of A - n = number of negative eigenvalues of A + + - p = number of positive eigenvalues of A + - n = number of negative eigenvalues of A INPUT: - A -- symmetric matrix (assumed to be non-degenerate) + + - A -- symmetric matrix (assumed to be non-degenerate) OUTPUT: - a pair (tuple) of integers. - EXAMPLES: + a pair (tuple) of integers. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import signature_pair_of_matrix sage: A = Matrix(ZZ, 2, 2, [-1,0,0,3]) @@ -531,17 +564,23 @@ def p_adic_symbol(A, p, val): val = valuation of the maximal elementary divisor of A needed to obtain enough precision calculation is modulo p to the val+3 - TODO: Some description of the definition of the genus symbol. + + .. TODO:: + + Some description of the definition of the genus symbol. INPUT: - A -- symmetric matrix with integer coefficients - p -- prime number > 0 - val -- integer >= 0 + + - A -- symmetric matrix with integer coefficients + - p -- prime number > 0 + - val -- integer >= 0 OUTPUT: - a list of lists of integers - EXAMPLES: + a list of lists of integers + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() @@ -597,12 +636,15 @@ def is_even_matrix(A): index -1. INPUT: - A -- symmetric integer matrix + + - A -- symmetric integer matrix OUTPUT: - a pair of the form (boolean, integer) - EXAMPLES: + a pair of the form (boolean, integer) + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import is_even_matrix sage: A = Matrix(ZZ, 2, 2, [1,1,1,1]) @@ -626,14 +668,17 @@ def split_odd(A): such that u is odd and B is not even. INPUT: - A -- an odd symmetric matrix with integer coefficients (which - admits a splitting as above). + + - A -- an odd symmetric matrix with integer coefficients (which admits a + splitting as above). OUTPUT: - a pair (u, B) consisting of an odd integer u and an odd - integral symmetric matrix B. - EXAMPLES: + a pair (u, B) consisting of an odd integer u and an odd + integral symmetric matrix B. + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import is_even_matrix sage: from sage.quadratic_forms.genera.genus import split_odd @@ -716,13 +761,16 @@ def trace_diag_mod_8(A): any 2-adic Jordan decomposition!) INPUT: - A -- symmetric matrix with coefficients in Z which is odd in - Z/2Z and has determinant not divisible by 8. + + - A -- symmetric matrix with coefficients in Z which is odd in Z/2Z and has + determinant not divisible by 8. OUTPUT: - an integer - EXAMPLES: + an integer + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import is_even_matrix sage: from sage.quadratic_forms.genera.genus import split_odd sage: from sage.quadratic_forms.genera.genus import trace_diag_mod_8 @@ -757,20 +805,24 @@ def two_adic_symbol(A, val): The genus symbol of a component 2^m*f is of the form (m,n,s,d[,o]), where - m = valuation of the component - n = dimension of f - d = det(f) in {1,3,5,7} - s = 0 (or 1) if even (or odd) - o = oddity of f (= 0 if s = 0) in Z/8Z + + - m = valuation of the component + - n = dimension of f + - d = det(f) in {1,3,5,7} + - s = 0 (or 1) if even (or odd) + - o = oddity of f (= 0 if s = 0) in Z/8Z INPUT: - A -- symmetric matrix with integer coefficients - val -- integer >=0 + + - A -- symmetric matrix with integer coefficients + - val -- integer >=0 OUTPUT: - a list of lists of integers (representing a Conway-Sloane 2-adic symbol) - EXAMPLES: + a list of lists of integers (representing a Conway-Sloane 2-adic symbol) + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import two_adic_symbol sage: A = diagonal_matrix(ZZ, [1,2,3,4]) @@ -841,29 +893,6 @@ def two_adic_symbol(A, val): return [ [s[0]+m0] + s[1:] for s in sym + two_adic_symbol(A, val) ] - - - -## Removed because it was unused and undocumented! -# -#def is_trivial_symbol(p, sym): -# """ -# """ -# if len(sym) != 1: -# return False -# if sym[0] != 0 or sym[2] != 1: -# return False -# if p != 2: -# return True -# return sym[3] == 1 and sym[1] % 8 == sym[4] - - - - - - - - class Genus_Symbol_p_adic_ring(object): """ Local genus symbol over a p-adic ring. @@ -875,19 +904,19 @@ def __init__(self, prime, symbol, check = True): The genus symbol of a component p^m*A for odd prime = p is of the form (m,n,d), where - m = valuation of the component - n = rank of A - d = det(A) in {1,u} for normalized quadratic non-residue u. + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,u} for normalized quadratic non-residue u. The genus symbol of a component 2^m*A is of the form (m,n,s,d,o), where - m = valuation of the component - n = rank of A - d = det(A) in {1,3,5,7} - s = 0 (or 1) if even (or odd) - o = oddity of A (= 0 if s = 0) in Z/8Z - = the trace of the diagonalization of A + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,3,5,7} + - s = 0 (or 1) if even (or odd) + - o = oddity of A (= 0 if s = 0) in Z/8Z + = the trace of the diagonalization of A The genus symbol is a list of such symbols (ordered by m) for each of the Jordan blocks A_1,...,A_t. @@ -900,16 +929,18 @@ def __init__(self, prime, symbol, check = True): doubling conventions straight throughout! This is especially noticeable in the determinant and excess methods!! - INPUT: - prime -- a prime integer > 0 - symbol -- the list of invariants for Jordan blocks - A_t,...,A_t given as a list of lists of integers + + - prime -- a prime integer > 0 + - symbol -- the list of invariants for Jordan blocks A_t,...,A_t given + as a list of lists of integers OUTPUT: - None - EXAMPLES: + None + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -940,28 +971,29 @@ def __init__(self, prime, symbol, check = True): self._canonical_symbol = None def __repr__(self): - """ + r""" Gives a string representation for the p-adic genus symbol INPUT: - None + + None OUTPUT: - a string - EXAMPLES: + a string + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import two_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring - sage: A = diagonal_matrix(ZZ, [1,2,3,4]) sage: s2 = two_adic_symbol(A, 2); s2 [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]] sage: G = Genus_Symbol_p_adic_ring(2, s2) sage: G.__repr__() 'Genus symbol at 2 : [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]' - """ - return "Genus symbol at %s : %s"%(self._prime, self._symbol) + return "Genus symbol at %s : %s" % (self._prime, self._symbol) def __eq__(self, other): @@ -969,12 +1001,15 @@ def __eq__(self, other): Determines if two genus symbols are equal (not just equivalent!). INPUT: - a Genus_Symbol_p_adic_ring object + + a Genus_Symbol_p_adic_ring object OUTPUT: - boolean - EXAMPLES: + boolean + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1005,10 +1040,14 @@ def __ne__(self, other): Determines if two genus symbols are unequal (not just inequivalent!). INPUT: - a Genus_Symbol_p_adic_ring object + + a ``Genus_Symbol_p_adic_ring`` object OUTPUT: - boolean + + boolean + + EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1048,12 +1087,15 @@ def canonical_symbol(self): symbol is already canonical. INPUT: - None + + None OUTPUT: - a list of lists of integers - EXAMPLES: + a list of lists of integers + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1093,12 +1135,13 @@ def canonical_symbol(self): sage: G3.canonical_symbol() [[0, 3, 1], [1, 1, -1]] + .. NOTE:: + + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - NOTES: - See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. + .. TODO:: - TO DO: - - Add an example where sign walking occurs! + Add an example where sign walking occurs! """ symbol = self._symbol if self._prime == 2: @@ -1115,12 +1158,15 @@ def symbol_tuple_list(self): Returns the underlying list of lists of integers defining the genus symbol. INPUT: - None + + None OUTPUT: - list of lists of integers - EXAMPLES: + list of lists of integers + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1152,12 +1198,15 @@ def number_of_blocks(self): Returns the number of positive dimensional symbols/Jordan blocks INPUT: - None + + None OUTPUT: - integer >= 0 - EXAMPLES: + integer >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1187,12 +1236,15 @@ def determinant(self): the Gram matrix of Q) associated to this local genus symbol. INPUT: - None + + None OUTPUT: - an integer - EXAMPLES: + an integer + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1218,16 +1270,20 @@ def rank(self): """ Returns the dimension of a quadratic form associated to this genus symbol. - TO DO: DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! + .. TODO:: + DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! INPUT: - None + + None OUTPUT: - an integer >= 0 - EXAMPLES: + an integer >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1248,18 +1304,20 @@ def rank(self): """ return sum([ s[1] for s in self._symbol ]) - def dimension(self): """ Returns the dimension of a quadratic form associated to this genus symbol. INPUT: - None + + None OUTPUT: - an integer >= 0 - EXAMPLES: + an integer >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1292,15 +1350,19 @@ def excess(self): doubling conventions straight throughout! REFERENCE: - Conway and Sloane Book, 3rd edition, pp 370-371. + + Conway and Sloane Book, 3rd edition, pp 370-371. INPUT: - None + + None OUTPUT: - an integer - EXAMPLES: + an integer + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1364,12 +1426,15 @@ def trains(self): error for all other primes). INPUT: - None + + None OUTPUT: - a list of integers >= 0 - EXAMPLES: + a list of integers >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1396,12 +1461,15 @@ def compartments(self): error for all other primes). INPUT: - None + + None OUTPUT: - a list of integers >= 0 - EXAMPLES: + a list of integers >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1438,14 +1506,17 @@ def __init__(self, A, max_elem_divisors=None): largest elementary divisors). INPUT: - A -- a symmetric matrix with integer coefficients - max_elem_divisors -- the input precision for valuation of - maximal p-elementary divisor. (OPTIONAL) + + - A -- a symmetric matrix with integer coefficients + - max_elem_divisors -- the input precision for valuation of maximal + p-elementary divisor. (OPTIONAL) OUTPUT: - None - EXAMPLES: + None + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() @@ -1473,26 +1544,26 @@ def __init__(self, A, max_elem_divisors=None): def __repr__(self): - """ + r""" Returns a string representing the global genus symbol. INPUT: - None + + None OUTPUT: - a string - EXAMPLES: - sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring + a string + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() sage: GS = GenusSymbol_global_ring(A) sage: GS.__repr__() 'Genus of [2 0 0 0]\n[0 4 0 0]\n[0 0 6 0]\n[0 0 0 8]' - """ - return "Genus of %s"%self._representative - + return "Genus of %s" % self._representative def __eq__(self, other): @@ -1500,12 +1571,15 @@ def __eq__(self, other): Determines if two global genus symbols are equal (not just equivalent!). INPUT: - a GenusSymbol_global_ring object + + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean - EXAMPLES: + boolean + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A1 = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() @@ -1543,12 +1617,15 @@ def __ne__(self, other): Determines if two global genus symbols are unequal (not just inequivalent!). INPUT: - a GenusSymbol_global_ring object + + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean - EXAMPLES: + boolean + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A1 = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() @@ -1579,12 +1656,15 @@ def signature_pair_of_matrix(self): eigenvalues and n is the number of negative eigenvalues. INPUT: - None + + None OUTPUT: - a pair of integers (p, n) each >= 0 - EXAMPLES: + a pair of integers (p, n) each >= 0 + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A = DiagonalQuadraticForm(ZZ, [1,-2,3,4,8,-11]).Hessian_matrix() @@ -1604,12 +1684,15 @@ def determinant(self): symbol. INPUT: - None + + None OUTPUT: - an integer - EXAMPLES: + an integer + + EXAMPLES:: + sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: A = DiagonalQuadraticForm(ZZ, [1,-2,3,4]).Hessian_matrix() diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index e08a59e8d60..1e39e1c6fd1 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -214,6 +214,7 @@ class QuadraticForm(SageObject): ## Routines to compute p-adic field invariants from sage.quadratic_forms.quadratic_form__local_field_invariants import \ rational_diagonal_form, \ + _rational_diagonal_form_and_transformation, \ signature_vector, \ signature, \ hasse_invariant, \ @@ -370,10 +371,10 @@ class QuadraticForm(SageObject): ## Routines to test the local and global equivalence/isometry of two quadratic forms. from sage.quadratic_forms.quadratic_form__equivalence_testing import \ - is_globally_equivalent__souvigner, \ is_globally_equivalent_to, \ is_locally_equivalent_to, \ - has_equivalent_Jordan_decomposition_at_prime + has_equivalent_Jordan_decomposition_at_prime, \ + is_rationally_isometric def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_of_automorphisms=None, determinant=None): """ @@ -651,6 +652,20 @@ def __setitem__(self, ij, coeff): # TO DO: def __cmp__(self, other): ###################################### + def __hash__(self): + r""" + TESTS:: + + sage: Q1 = QuadraticForm(QQ, 2, [1,1,1]) + sage: Q2 = QuadraticForm(QQ, 2, [1,1,1]) + sage: Q3 = QuadraticForm(QuadraticField(2), 2, [1,1,1]) + sage: hash(Q1) == hash(Q2) + True + sage: hash(Q1) == hash(Q3) + False + """ + return hash(self.__base_ring) ^ hash(tuple(self.__coeffs)) + def __eq__(self, right): """ Determines if two quadratic forms are equal. diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 8138e7df591..28999a4a140 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -1,120 +1,86 @@ """ Automorphisms of Quadratic Forms """ -from sage.interfaces.gp import gp + +#***************************************************************************** +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.misc.cachefunc import cached_method from sage.libs.pari.all import pari from sage.matrix.constructor import Matrix from sage.rings.integer_ring import ZZ -from sage.misc.mrange import mrange -from sage.misc.all import cython_lambda from sage.modules.all import FreeModule from sage.modules.free_module_element import vector from sage.rings.arith import GCD -from sage.misc.sage_eval import sage_eval -from sage.env import SAGE_LOCAL -import tempfile, os -from random import random - -def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): +@cached_method +def basis_of_short_vectors(self, show_lengths=False, safe_flag=None): """ Return a basis for `ZZ^n` made of vectors with minimal lengths Q(`v`). - The safe_flag allows us to select whether we want a copy of the - output, or the original output. By default safe_flag = True, so - we return a copy of the cached information. If this is set to - False, then the routine is much faster but the return values are - vulnerable to being corrupted by the user. - - OUTPUT: - a list of vectors, and optionally a list of values for each vector. + OUTPUT: a tuple of vectors, and optionally a tuple of values for + each vector. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.basis_of_short_vectors() - [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] + ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)) sage: Q.basis_of_short_vectors(True) - ([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)], [1, 3, 5, 7]) + (((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)), (1, 3, 5, 7)) - """ - ## Try to use the cached results - try: - ## Return the appropriate result - if show_lengths: - if safe_flag: - return deep_copy(self.__basis_of_short_vectors), deepcopy(self.__basis_of_short_vectors_lengths) - else: - return self.__basis_of_short_vectors, self.__basis_of_short_vectors_lengths - else: - if safe_flag: - return deepcopy(self.__basis_of_short_vectors) - else: - return deepcopy(self.__basis_of_short_vectors) - except Exception: - pass + The returned vectors are immutable:: + sage: v = Q.basis_of_short_vectors()[0] + sage: v + (1, 0, 0, 0) + sage: v[0] = 0 + Traceback (most recent call last): + ... + ValueError: vector is immutable; please change a copy instead (use copy()) + """ + if safe_flag is not None: + from sage.misc.superseded import deprecation + deprecation(18673, "The safe_flag argument to basis_of_short_vectors() is deprecated and no longer used") ## Set an upper bound for the number of vectors to consider Max_number_of_vectors = 10000 - ## Generate a PARI matrix string for the associated Hessian matrix - M_str = str(gp(self.matrix())) - + ## Generate a PARI matrix for the associated Hessian matrix + M_pari = self._pari_() ## Run through all possible minimal lengths to find a spanning set of vectors n = self.dim() - #MS = MatrixSpace(QQ, n) M1 = Matrix([[0]]) vec_len = 0 while M1.rank() < n: - - ## DIAGONSTIC - #print - #print "Starting with vec_len = ", vec_len - #print "M_str = ", M_str - vec_len += 1 - gp_mat = gp.qfminim(M_str, vec_len, Max_number_of_vectors)[3].mattranspose() - number_of_vecs = ZZ(gp_mat.matsize()[1]) + pari_mat = M_pari.qfminim(vec_len, Max_number_of_vectors)[2] + number_of_vecs = ZZ(pari_mat.matsize()[1]) vector_list = [] for i in range(number_of_vecs): - #print "List at", i, ":", list(gp_mat[i+1,]) - new_vec = vector([ZZ(x) for x in list(gp_mat[i+1,])]) + new_vec = vector([ZZ(x) for x in list(pari_mat[i])]) vector_list.append(new_vec) - - ## DIAGNOSTIC - #print "number_of_vecs = ", number_of_vecs - #print "vector_list = ", vector_list - - ## Make a matrix from the short vectors if len(vector_list) > 0: M1 = Matrix(vector_list) - - ## DIAGNOSTIC - #print "matrix of vectors = \n", M1 - #print "rank of the matrix = ", M1.rank() - - - - #print " vec_len = ", vec_len - #print M1 - - ## Organize these vectors by length (and also introduce their negatives) max_len = vec_len // 2 - vector_list_by_length = [[] for _ in range(max_len + 1)] + vector_list_by_length = [[] for _ in range(max_len + 1)] for v in vector_list: l = self(v) vector_list_by_length[l].append(v) vector_list_by_length[l].append(vector([-x for x in v])) - ## Make a matrix from the column vectors (in order of ascending length). sorted_list = [] for i in range(len(vector_list_by_length)): @@ -122,32 +88,20 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): sorted_list.append(v) sorted_matrix = Matrix(sorted_list).transpose() - ## Determine a basis of vectors of minimal length pivots = sorted_matrix.pivots() - basis = [sorted_matrix.column(i) for i in pivots] - pivot_lengths = [self(v) for v in basis] - - - ## DIAGNOSTIC - #print "basis = ", basis - #print "pivot_lengths = ", pivot_lengths - - - ## Cache the result - self.__basis_of_short_vectors = basis - self.__basis_of_short_vectors_lenghts = pivot_lengths - + basis = tuple(sorted_matrix.column(i) for i in pivots) + for v in basis: + v.set_immutable() ## Return the appropriate result if show_lengths: + pivot_lengths = tuple(self(v) for v in basis) return basis, pivot_lengths else: return basis - - def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with @@ -160,10 +114,8 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): - ``up_to_sign_flag`` -- (default: ``False``) if set to True, then only one of the vectors of the pair `[v, -v]` is listed. - OUTPUT: - - A list of lists of vectors such that entry `[i]` contains all - vectors of length `i`. + OUTPUT: a list of lists of vectors such that entry ``[i]`` contains + all vectors of length `i`. EXAMPLES:: @@ -258,7 +210,6 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): # In certain trivial cases, PARI can sometimes return longer # vectors than requested. if length < len_bound: - v = parilist[i] sagevec = V(list(parilist[i])) vec_sorted_list[length].append(sagevec) if not up_to_sign_flag : @@ -269,6 +220,7 @@ def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): return vec_sorted_list + def short_primitive_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short primitive vectors `v`, sorted by length, with @@ -278,8 +230,7 @@ def short_primitive_vector_list_up_to_length(self, len_bound, up_to_sign_flag=Fa Note: This processes the PARI/GP output to always give elements of type `ZZ`. - OUTPUT: - a list of lists of vectors. + OUTPUT: a list of lists of vectors. EXAMPLES:: @@ -425,6 +376,7 @@ def number_of_automorphisms(self, recompute=None): the quadratic form. OUTPUT: + an integer >= 2. EXAMPLES:: @@ -451,7 +403,6 @@ def number_of_automorphisms(self, recompute=None): self._compute_automorphisms() return self.__number_of_automorphisms - def set_number_of_automorphisms(self, num_autos): """ Set the number of automorphisms to be the value given. No error diff --git a/src/sage/quadratic_forms/quadratic_form__count_local_2.py b/src/sage/quadratic_forms/quadratic_form__count_local_2.py index e46bdf42cf1..95b1b73ffea 100644 --- a/src/sage/quadratic_forms/quadratic_form__count_local_2.py +++ b/src/sage/quadratic_forms/quadratic_form__count_local_2.py @@ -2,7 +2,7 @@ Counting Congruence Solutions This file provides more user-friendly Python front-ends to the Cython code in -:module:`sage.quadratic_forms.count_local`. +:mod:`sage.quadratic_forms.count_local`. """ ################################################################## ## Methods for counting/computing the number of representations ## @@ -32,22 +32,22 @@ def count_congruence_solutions_as_vector(self, p, k, m, zvec, nzvec): 2) `x[i] != 0 (mod p) for all i` in nzvec - REFERENCES: See Hanke's (????) paper "Local Densities and explicit - bounds...", p??? for the definitions of the solution types and - congruence conditions. + REFERENCES: - INPUT: - `p` -- prime number > 0 - - `k` -- an integer > 0 + See Hanke's (????) paper "Local Densities and explicit bounds...", p??? for + the definitions of the solution types and congruence conditions. - `m` -- an integer (depending only on mod `p^k`) + INPUT: - zvec, nzvec -- a list of integers in range(self.dim()), or None + - `p` -- prime number > 0 + - `k` -- an integer > 0 + - `m` -- an integer (depending only on mod `p^k`) + - zvec, nzvec -- a list of integers in range(self.dim()), or None OUTPUT: - a list of six integers >= 0 representing the solution types: - [All, Good, Zero, Bad, BadI, BadII] + + a list of six integers >= 0 representing the solution types: + [All, Good, Zero, Bad, BadI, BadII] EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py index 524de9e65d0..a27837f6b3a 100644 --- a/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py +++ b/src/sage/quadratic_forms/quadratic_form__equivalence_testing.py @@ -1,150 +1,52 @@ """ Equivalence Testing + +AUTHORS: + +- Anna Haensch (2014-12-01): added test for rational isometry """ -from sage.matrix.constructor import Matrix -from sage.misc.mrange import mrange from sage.rings.arith import hilbert_symbol, prime_divisors, is_prime, valuation, GCD, legendre_symbol from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from quadratic_form import is_QuadraticForm -from sage.env import SAGE_LOCAL - -import tempfile, os - -from random import random - - ################################################################################ ## Routines to test if two quadratic forms over ZZ are globally equivalent. ## ## (For now, we require both forms to be positive definite.) ## ################################################################################ -def is_globally_equivalent__souvigner(self, other, return_transformation=False): +def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_precision=None, check_local_equivalence=None): """ - Uses the Souvigner code to compute the number of automorphisms. + Determines if the current quadratic form is equivalent to the + given form over ZZ. If ``return_matrix`` is True, then we return + the transformation matrix `M` so that ``self(M) == other``. INPUT: - a QuadraticForm - OUTPUT: - boolean, and optionally a matrix - - EXAMPLES:: + - ``self``, ``other`` -- positive definite integral quadratic forms - sage: Q = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) - sage: Q1 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2]) - sage: M = Q.is_globally_equivalent__souvigner(Q1, True) ; M # optional -- souvigner - [ 0 0 -1] - [ 1 0 0] - [-1 1 1] - sage: Q1(M) == Q # optional -- souvigner - True + - ``return_matrix`` -- (boolean, default ``False``) return + the transformation matrix instead of a boolean - """ - ## Write an input text file - F_filename = '/tmp/tmp_isom_input' + str(random()) + ".txt" - F = open(F_filename, 'w') - #F = tempfile.NamedTemporaryFile(prefix='tmp_isom_input', suffix=".txt") ## This failed because it may have hyphens, which are interpreted badly by the Souvigner code. - F.write("\n #1 \n") - - ## Write the first form - n = self.dim() - F.write(str(n) + "x0 \n") ## Use the lower-triangular form - for i in range(n): - for j in range(i+1): - if i == j: - F.write(str(2 * self[i,j]) + " ") - else: - F.write(str(self[i,j]) + " ") - F.write("\n") - - ## Write the second form - F.write("\n") - n = self.dim() - F.write(str(n) + "x0 \n") ## Use the lower-triangular form - for i in range(n): - for j in range(i+1): - if i == j: - F.write(str(2 * other[i,j]) + " ") - else: - F.write(str(other[i,j]) + " ") - F.write("\n") - F.flush() - #print "Input filename = ", F.name - #os.system("less " + F.name) - - ## Call the Souvigner automorphism code - souvigner_isom_path = os.path.join(SAGE_LOCAL,'bin','Souvigner_ISOM') - G1 = tempfile.NamedTemporaryFile(prefix='tmp_isom_ouput', suffix=".txt") - #print "Output filename = ", G1.name - #print "Executing the shell command: " + souvigner_isom_path + " '" + F.name + "' > '" + G1.name + "'" - os.system(souvigner_isom_path + " '" + F.name + "' > '" + G1.name +"'") - - - ## Read the output - G2 = open(G1.name, 'r') - line = G2.readline() - if line.startswith("Error:"): - raise RuntimeError("There is a problem using the souvigner code... " + line) - elif line.find("not isomorphic") != -1: ## Checking if this text appears, if so then they're not isomorphic! - return False - else: - ## Decide whether to read the transformation matrix, and return true - if not return_transformation: - F.close() - G1.close() - G2.close() - os.system("rm -f " + F_filename) - return True - else: - ## Try to read the isomorphism matrix - M = Matrix(ZZ, n, n) - for i in range(n): - new_row_text = G2.readline().split() - #print new_row_text - for j in range(n): - M[i,j] = new_row_text[j] - - ## Remove temporary files and return the value - F.close() - G1.close() - G2.close() - os.system("rm -f " + F_filename) - if return_transformation: - return M.transpose() - else: - return True - #return True, M - - ## Raise and error if we're here: - raise RuntimeError("Oops! There is a problem...") - - - -def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_precision='sturm', check_local_equivalence=True): - """ - Determines if the current quadratic form is equivalent to the - given form over ZZ. If return_matrix is True, then we also return - the transformation matrix M so that self(M) == other. + OUTPUT: - INPUT: - a QuadraticForm + - if ``return_matrix`` is ``False``: a boolean - OUTPUT: - boolean, and optionally a matrix + - if ``return_matrix`` is ``True``: either ``False`` or the + transformation matrix EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: M = Matrix(ZZ, 4, 4, [1,2,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) sage: Q1 = Q(M) - sage: Q.(Q1) # optional -- souvigner + sage: Q.is_globally_equivalent_to(Q1) True - sage: MM = Q.is_globally_equivalent_to(Q1, return_matrix=True) # optional -- souvigner - sage: Q(MM) == Q1 # optional -- souvigner + sage: MM = Q.is_globally_equivalent_to(Q1, return_matrix=True) + sage: Q(MM) == Q1 True :: @@ -152,15 +54,17 @@ def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_p sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q3 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2]) - sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner + sage: Q1.is_globally_equivalent_to(Q2) + False + sage: Q1.is_globally_equivalent_to(Q2, return_matrix=True) False - sage: Q1.is_globally_equivalent_to(Q3) # optional -- souvigner + sage: Q1.is_globally_equivalent_to(Q3) True - sage: M = Q1.is_globally_equivalent_to(Q3, True) ; M # optional -- souvigner + sage: M = Q1.is_globally_equivalent_to(Q3, True); M [-1 -1 0] [ 1 1 1] [-1 0 0] - sage: Q1(M) == Q3 # optional -- souvigner + sage: Q1(M) == Q3 True :: @@ -171,68 +75,32 @@ def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_p ... ValueError: not a definite form in QuadraticForm.is_globally_equivalent_to() + ALGORITHM: this uses the PARI function ``qfisom()``, implementing + an algorithm by Plesken and Souvignier. """ - ## only for definite forms - if not self.is_definite(): - raise ValueError("not a definite form in QuadraticForm.is_globally_equivalent_to()") + if check_theta_to_precision is not None: + from sage.misc.superseded import deprecation + deprecation(19111, "The check_theta_to_precision argument is deprecated and ignored") + if check_local_equivalence is not None: + from sage.misc.superseded import deprecation + deprecation(19111, "The check_local_equivalence argument is deprecated and ignored") ## Check that other is a QuadraticForm - #if not isinstance(other, QuadraticForm): if not is_QuadraticForm(other): - raise TypeError("Oops! You must compare two quadratic forms, but the argument is not a quadratic form. =(") - - - ## Now use the Souvigner code by default! =) - return other.is_globally_equivalent__souvigner(self, return_matrix) ## Note: We switch this because the Souvigner code has the opposite mapping convention to us. (It takes the second argument to the first!) - - + raise TypeError("you must compare two quadratic forms, but the argument is not a quadratic form") - ## ---------------------------------- Unused Code below --------------------------------------------------------- - - ## Check if the forms are locally equivalent - if (check_local_equivalence == True): - if not self.is_locally_equivalent_to(other): - return False - - ## Check that the forms have the same theta function up to the desired precision (this can be set so that it determines the cusp form) - if check_theta_to_precision is not None: - if self.theta_series(check_theta_to_precision, var_str='', safe_flag=False) != other.theta_series(check_theta_to_precision, var_str='', safe_flag=False): - return False - - - ## Make all possible matrices which give an isomorphism -- can we do this more intelligently? - ## ------------------------------------------------------------------------------------------ - - ## Find a basis of short vectors for one form, and try to match them with vectors of that length in the other one. - basis_for_self, self_lengths = self.basis_of_short_vectors(show_lengths=True) - max_len = max(self_lengths) - short_vectors_of_other = other.short_vector_list_up_to_length(max_len + 1) - - ## Make the matrix A:e_i |--> v_i to our new basis. - A = Matrix(basis_for_self).transpose() - Q2 = A.transpose() * self.matrix() * A ## This is the matrix of 'self' in the new basis - Q3 = other.matrix() - - ## Determine all automorphisms - n = self.dim() - Auto_list = [] - - ## DIAGNOSTIC - #print "n = " + str(n) - #print "pivot_lengths = " + str(pivot_lengths) - #print "vector_list_by_length = " + str(vector_list_by_length) - #print "length of vector_list_by_length = " + str(len(vector_list_by_length)) + ## only for definite forms + if not self.is_definite() or not other.is_definite(): + raise ValueError("not a definite form in QuadraticForm.is_globally_equivalent_to()") - for index_vec in mrange([len(short_vectors_of_other[self_lengths[i]]) for i in range(n)]): - M = Matrix([short_vectors_of_other[self_lengths[i]][index_vec[i]] for i in range(n)]).transpose() - if M.transpose() * Q3 * M == Q2: - if return_matrix: - return A * M.inverse() - else: - return True + mat = other._pari_().qfisom(self) + if not mat: + return False - ## If we got here, then there is no isomorphism - return False + if return_matrix: + return mat.sage() + else: + return True def is_locally_equivalent_to(self, other, check_primes_only=False, force_jordan_equivalence_test=False): @@ -245,16 +113,18 @@ def is_locally_equivalent_to(self, other, check_primes_only=False, force_jordan_ prime, and the dimension and signature at the real place. INPUT: - a QuadraticForm + + a QuadraticForm OUTPUT: - boolean + + boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) - sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner + sage: Q1.is_globally_equivalent_to(Q2) False sage: Q1.is_locally_equivalent_to(Q2) True @@ -303,10 +173,12 @@ def has_equivalent_Jordan_decomposition_at_prime(self, other, p): equivalent to that of self. INPUT: - a QuadraticForm + + a QuadraticForm OUTPUT: - boolean + + boolean EXAMPLES:: @@ -450,9 +322,168 @@ def has_equivalent_Jordan_decomposition_at_prime(self, other, p): else: raise TypeError("Oops! This should not have happened.") +def is_rationally_isometric(self, other): + """ + Determines if two regular quadratic forms over a number field are isometric. + INPUT: + a quadratic form + OUTPUT: + boolean + EXAMPLES:: + sage: V=DiagonalQuadraticForm(QQ,[1,1,2]) + sage: W=DiagonalQuadraticForm(QQ,[2,2,2]) + sage: V.is_rationally_isometric(W) + True + + :: + + sage: K.=NumberField(x^2-3) + sage: V=QuadraticForm(K,4,[1,0,0,0,2*a,0,0,a,0,2]);V + Quadratic form in 4 variables over Number Field in a with defining polynomial x^2 - 3 with coefficients: + [ 1 0 0 0 ] + [ * 2*a 0 0 ] + [ * * a 0 ] + [ * * * 2 ] + sage: W=QuadraticForm(K,4,[1,2*a,4,6,3,10,2,1,2,5]);W + Quadratic form in 4 variables over Number Field in a with defining polynomial x^2 - 3 with coefficients: + [ 1 2*a 4 6 ] + [ * 3 10 2 ] + [ * * 1 2 ] + [ * * * 5 ] + sage: V.is_rationally_isometric(W) + False + + :: + + sage: K.=NumberField(x^4+2*x+6) + sage: V=DiagonalQuadraticForm(K,[a,2,3,2,1]);V + Quadratic form in 5 variables over Number Field in a with defining polynomial x^4 + 2*x + 6 with coefficients: + [ a 0 0 0 0 ] + [ * 2 0 0 0 ] + [ * * 3 0 0 ] + [ * * * 2 0 ] + [ * * * * 1 ] + sage: W=DiagonalQuadraticForm(K,[a,a,a,2,1]);W + Quadratic form in 5 variables over Number Field in a with defining polynomial x^4 + 2*x + 6 with coefficients: + [ a 0 0 0 0 ] + [ * a 0 0 0 ] + [ * * a 0 0 ] + [ * * * 2 0 ] + [ * * * * 1 ] + sage: V.is_rationally_isometric(W) + False + + :: + + sage: K.=NumberField(x^2-3) + sage: V=DiagonalQuadraticForm(K,[-1,a,-2*a]) + sage: W=DiagonalQuadraticForm(K,[-1,-a,2*a]) + sage: V.is_rationally_isometric(W) + True + + TESTS:: + + sage: K.=QuadraticField(3) + sage: V=DiagonalQuadraticForm(K,[1,2]) + sage: W=DiagonalQuadraticForm(K,[1,0]) + sage: V.is_rationally_isometric(W) + Traceback (most recent call last): + ... + NotImplementedError: This only tests regular forms + + Forms must have the same base ring otherwise a `TypeError` is raised:: + + sage: K1. = QuadraticField(5) + sage: K2. = QuadraticField(7) + sage: V = DiagonalQuadraticForm(K1,[1,a]) + sage: W = DiagonalQuadraticForm(K2,[1,b]) + sage: V.is_rationally_isometric(W) + Traceback (most recent call last): + ... + TypeError: forms must have the same base ring. + + Forms which have different dimension are not isometric:: + + sage: W=DiagonalQuadraticForm(QQ,[1,2]) + sage: V=DiagonalQuadraticForm(QQ,[1,1,1]) + sage: V.is_rationally_isometric(W) + False + + Forms whose determinants do not differ by a square in the base field are not isometric:: + + sage: K.=NumberField(x^2-3) + sage: V=DiagonalQuadraticForm(K,[-1,a,-2*a]) + sage: W=DiagonalQuadraticForm(K,[-1,a,2*a]) + sage: V.is_rationally_isometric(W) + False + + :: + + sage: K. = NumberField(x^5 - x + 2, 'a') + sage: Q = QuadraticForm(K,3,[a,1,0,-a**2,-a**3,-1]) + sage: m = Q.matrix() + sage: for _ in range(5): + ....: t = random_matrix(ZZ,3,algorithm='unimodular') + ....: m2 = t*m*t.transpose() + ....: Q2 = QuadraticForm(K, 3, [m2[i,j] / (2 if i==j else 1) + ....: for i in range(3) for j in range(i,3)]) + ....: print Q.is_rationally_isometric(Q2) + True + True + True + True + True + + """ + + if self.Gram_det() == 0 or other.Gram_det() == 0: + raise NotImplementedError("This only tests regular forms") + + if self.base_ring() != other.base_ring(): + raise TypeError("forms must have the same base ring.") + + if self.dim() != other.dim(): + return False + + if not ((self.Gram_det()*other.Gram_det()).is_square()): + return False + + L1=self.Gram_det().support() + L2=other.Gram_det().support() + + for p in set().union(L1,L2): + if self.hasse_invariant(p) != other.hasse_invariant(p): + return False + + if self.base_ring() == QQ: + if self.signature() != other.signature(): + return False + else: + + M = self.rational_diagonal_form().Gram_matrix_rational() + N = other.rational_diagonal_form().Gram_matrix_rational() + K = self.base_ring() + + Mentries = M.diagonal() + Nentries = N.diagonal() + + for emb in K.real_embeddings(): + + Mpos=0 + for x in Mentries: + Mpos+= emb(x) >= 0 + + Npos=0 + for x in Nentries: + Npos+= emb(x) >= 0 + + if Npos != Mpos: + return False + + return True diff --git a/src/sage/quadratic_forms/quadratic_form__evaluate.pyx b/src/sage/quadratic_forms/quadratic_form__evaluate.pyx index a97c7de40e1..dab22b69057 100644 --- a/src/sage/quadratic_forms/quadratic_form__evaluate.pyx +++ b/src/sage/quadratic_forms/quadratic_form__evaluate.pyx @@ -1,4 +1,4 @@ - +"Evaluation" def QFEvaluateVector(Q, v): """ @@ -8,6 +8,8 @@ def QFEvaluateVector(Q, v): matrix is given then the output will be the quadratic form Q' which in matrix notation is given by: + .. math:: + Q' = v^t * Q * v. Note: This is a Python wrapper for the fast evaluation routine @@ -15,13 +17,16 @@ def QFEvaluateVector(Q, v): called more conveniently as Q(M). INPUT: - Q -- QuadraticForm over a base ring R - v -- a tuple or list (or column matrix) of Q.dim() elements of R + + - Q -- QuadraticForm over a base ring R + - v -- a tuple or list (or column matrix) of Q.dim() elements of R OUTPUT: + an element of R - EXAMPLES: + EXAMPLES:: + sage: from sage.quadratic_forms.quadratic_form__evaluate import QFEvaluateVector sage: Q = QuadraticForm(ZZ, 4, range(10)); Q Quadratic form in 4 variables over Integer Ring with coefficients: @@ -74,13 +79,16 @@ def QFEvaluateMatrix(Q, M, Q2): for now creates circular imports. INPUT: - Q -- QuadraticForm over a base ring R - M -- a Q.dim() x Q2.dim() matrix of elements of R + + - Q -- QuadraticForm over a base ring R + - M -- a Q.dim() x Q2.dim() matrix of elements of R OUTPUT: - Q2 -- a QuadraticForm over R - EXAMPLES: + - Q2 -- a QuadraticForm over R + + EXAMPLES:: + sage: from sage.quadratic_forms.quadratic_form__evaluate import QFEvaluateMatrix sage: Q = QuadraticForm(ZZ, 4, range(10)); Q Quadratic form in 4 variables over Integer Ring with coefficients: diff --git a/src/sage/quadratic_forms/quadratic_form__genus.py b/src/sage/quadratic_forms/quadratic_form__genus.py index 6c1343efd0d..e779dd651eb 100644 --- a/src/sage/quadratic_forms/quadratic_form__genus.py +++ b/src/sage/quadratic_forms/quadratic_form__genus.py @@ -1,6 +1,5 @@ """ Local and Global Genus Symbols - """ @@ -75,6 +74,7 @@ def local_genus_symbol(self, p): coefficients the p-adic integers) of the following form: 1. If p>2 then return triples of the form [`m`, `n`, `d`] where + `m` = valuation of the component `n` = rank of A @@ -82,6 +82,7 @@ def local_genus_symbol(self, p): `d` = det(A) in {1,u} for normalized quadratic non-residue u. 2. If p=2 then return quintuples of the form [`m`,`n`,`s`, `d`, `o`] where + `m` = valuation of the component `n` = rank of A @@ -103,6 +104,7 @@ def local_genus_symbol(self, p): -`p` -- a prime number > 0 OUTPUT: + Returns a Conway-Sloane genus symbol at p, which is an instance of the Genus_Symbol_p_adic_ring class. diff --git a/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py b/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py index dd348e62d66..575c629e324 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py +++ b/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py @@ -1,6 +1,5 @@ """ Local Density Congruence - """ @@ -36,11 +35,13 @@ def count_modp_solutions__by_Gauss_sum(self, p, m): Densities..." paper. INPUT: + `p` -- a prime number > 2 `m` -- an integer OUTPUT: + an integer >= 0 EXAMPLES:: @@ -80,6 +81,7 @@ def local_good_density_congruence_odd(self, p, m, Zvec, NZvec): normal form. INPUT: + Q -- quadratic form assumed to be diagonal and p-integral `p` -- a prime number @@ -89,6 +91,7 @@ def local_good_density_congruence_odd(self, p, m, Zvec, NZvec): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -181,6 +184,7 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): INPUT: + Q -- quadratic form assumed to be block diagonal and 2-integral `p` -- a prime number @@ -190,6 +194,7 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -363,6 +368,7 @@ def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): INPUT: + Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number @@ -372,6 +378,7 @@ def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -449,6 +456,7 @@ def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): INPUT: + Q -- quadratic form assumed to be block diagonal and `p`-integral `p` -- a prime number @@ -458,6 +466,7 @@ def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -531,6 +540,7 @@ def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): INPUT: + Q -- quadratic form assumed to be block diagonal and `p`-integral `p` -- a prime number @@ -540,6 +550,7 @@ def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -720,6 +731,7 @@ def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): INPUT: + Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number @@ -729,6 +741,7 @@ def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -890,6 +903,7 @@ def local_bad_density_congruence(self, p, m, Zvec=None, NZvec=None): `m` at `p`, allowing certain congruence conditions mod `p`. INPUT: + Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number @@ -899,6 +913,7 @@ def local_bad_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -950,6 +965,7 @@ def local_density_congruence(self, p, m, Zvec=None, NZvec=None): allowing certain congruence conditions mod `p`. INPUT: + Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number @@ -959,6 +975,7 @@ def local_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: @@ -1022,6 +1039,7 @@ def local_primitive_density_congruence(self, p, m, Zvec=None, NZvec=None): Note: The following routine is not used internally, but is included for consistency. INPUT: + Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number @@ -1031,6 +1049,7 @@ def local_primitive_density_congruence(self, p, m, Zvec=None, NZvec=None): Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: + a rational number EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py b/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py index 2afb1b26a2c..68de70fc98e 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py +++ b/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py @@ -21,10 +21,12 @@ def local_density(self, p, m): routines performing the computations! INPUT: + `p` -- a prime number > 0 `m` -- an integer OUTPUT: + a rational number EXAMPLES:: @@ -79,10 +81,12 @@ def local_primitive_density(self, p, m): the routines performing the computations! INPUT: + `p` -- a prime number > 0 `m` -- an integer OUTPUT: + a rational number EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py index efcdc77d384..22b74002786 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py +++ b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py @@ -1,18 +1,17 @@ """ Local Field Invariants + +This contains routines to compute local (p-adic) invariants of +quadratic forms over the rationals. """ + #***************************************************************************** # Copyright (C) 2007 William Stein and Jonathan Hanke # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# 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/ #***************************************************************************** @@ -23,34 +22,36 @@ ########################################################################### -import copy - +from copy import deepcopy from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RR from sage.rings.arith import prime_divisors, hilbert_symbol from sage.functions.all import sgn -from sage.rings.fraction_field import FractionField from sage.matrix.matrix_space import MatrixSpace +from sage.misc.cachefunc import cached_method -## Routines to compute local (p-adic) invariants of a quadratic form Q: -## (Note: Here Q is the matrix so that Q(x) = x^t * Q * x.) -## -------------------------------------------------------------------- - def rational_diagonal_form(self, return_matrix=False): """ - Returns a diagonal form equivalent to Q over the fraction field of - its defining ring. If the return_matrix is True, then we return - the transformation matrix performing the diagonalization as the - second argument. + Return a diagonal form equivalent to the given quadratic from + over the fraction field of its defining ring. INPUT: - none - OUTPUT: - Q -- the diagonalized form of this quadratic form - (optional) T -- matrix which diagonalizes Q (over it's fraction field) + - ``return_matrix`` -- (boolean, default: False) also return the + transformation matrix. + + OUTPUT: either ``D`` (if ``return_matrix`` is false) or ``(D,T)`` + (if ``return_matrix`` is true) where + + - ``D`` -- the diagonalized form of this quadratic form. + + - ``T`` -- transformation matrix. This is such that + ``T.transpose() * self.matrix() * T`` gives ``D.matrix()``. + + Both ``D`` and ``T`` are defined over the fraction field of the + base ring of the given form. EXAMPLES:: @@ -64,22 +65,43 @@ def rational_diagonal_form(self, return_matrix=False): [ -2 0 ] [ * 1/8 ] - :: + If we start with a diagonal form, we get back the same form defined + over the fraction field:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) - sage: Q.rational_diagonal_form(return_matrix=True) - ( + sage: Q.rational_diagonal_form() Quadratic form in 4 variables over Rational Field with coefficients: [ 1 0 0 0 ] [ * 3 0 0 ] [ * * 5 0 ] - [ * * * 7 ] , - - [1 0 0 0] - [0 1 0 0] - [0 0 1 0] - [0 0 0 1] - ) + [ * * * 7 ] + + In the following example, we check the consistency of the + transformation matrix:: + + sage: Q = QuadraticForm(ZZ, 4, range(10)) + sage: D, T = Q.rational_diagonal_form(return_matrix=True) + sage: D + Quadratic form in 4 variables over Rational Field with coefficients: + [ 5 0 0 0 ] + [ * -1/20 0 0 ] + [ * * 13 0 ] + [ * * * 563/52 ] + sage: T + [ 1 -9/10 11 149/26] + [ 1 1/10 -2 -10/13] + [ 0 0 1 -29/26] + [ 0 0 0 1] + sage: T.transpose() * Q.matrix() * T + [ 10 0 0 0] + [ 0 -1/10 0 0] + [ 0 0 26 0] + [ 0 0 0 563/26] + sage: D.matrix() + [ 10 0 0 0] + [ 0 -1/10 0 0] + [ 0 0 26 0] + [ 0 0 0 563/26] :: @@ -103,11 +125,83 @@ def rational_diagonal_form(self, return_matrix=False): [ 0 0 1 0] [ 0 0 0 1] ) + + TESTS: + + Changing the output quadratic form does not affect the caching:: + + sage: Q, T = Q1.rational_diagonal_form(return_matrix=True) + sage: Q[0,0] = 13 + sage: Q1.rational_diagonal_form() + Quadratic form in 4 variables over Rational Field with coefficients: + [ 1 0 0 0 ] + [ * 3/4 0 0 ] + [ * * 1 0 ] + [ * * * 18 ] + + The transformation matrix is immutable:: + + sage: T[0,0] = 13 + Traceback (most recent call last): + ... + ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + """ + Q, T = self._rational_diagonal_form_and_transformation() + + # Quadratic forms do not support immutability, so we need to make + # a copy to be safe. The matrix T is already immutable. + Q = deepcopy(Q) + + if return_matrix: + return Q, T + else: + return Q + + +@cached_method +def _rational_diagonal_form_and_transformation(self): + """ + Return a diagonal form equivalent to the given quadratic from and + the corresponding transformation matrix. This is over the fraction + field of the base ring of the given quadratic form. + + OUTPUT: a tuple ``(D,T)`` where + + - ``D`` -- the diagonalized form of this quadratic form. + + - ``T`` -- transformation matrix. This is such that + ``T.transpose() * self.matrix() * T`` gives ``D.matrix()``. + + Both ``D`` and ``T`` are defined over the fraction field of the + base ring of the given form. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 4, [1, 1, 0, 0, 1, 0, 0, 1, 0, 18]) + sage: Q + Quadratic form in 4 variables over Integer Ring with coefficients: + [ 1 1 0 0 ] + [ * 1 0 0 ] + [ * * 1 0 ] + [ * * * 18 ] + sage: Q._rational_diagonal_form_and_transformation() + ( + Quadratic form in 4 variables over Rational Field with coefficients: + [ 1 0 0 0 ] + [ * 3/4 0 0 ] + [ * * 1 0 ] + [ * * * 18 ] , + + [ 1 -1/2 0 0] + [ 0 1 0 0] + [ 0 0 1 0] + [ 0 0 0 1] + ) """ n = self.dim() - Q = copy.deepcopy(self) - Q.__init__(FractionField(self.base_ring()), self.dim(), self.coefficients()) - MS = MatrixSpace(Q.base_ring(), n, n) + K = self.base_ring().fraction_field() + Q = self.base_change_to(K) + MS = MatrixSpace(K, n, n) T = MS(1) ## Clear the entries one row at a time. @@ -139,15 +233,8 @@ def rational_diagonal_form(self, return_matrix=False): Q = Q(temp) T = T * temp - - ## Return the appropriate output - if return_matrix: - return Q, T - else: - return Q - - - + T.set_immutable() + return Q, T def signature_vector(self): @@ -204,8 +291,6 @@ def signature_vector(self): else: z += 1 - ## TO DO: Cache this result? - return (p, n, z) @@ -219,12 +304,15 @@ def signature(self): of the matrix of the quadratic form. INPUT: + None OUTPUT: + an integer EXAMPLES: + sage: Q = DiagonalQuadraticForm(ZZ, [1,0,0,-4,3,11,3]) sage: Q.signature() 3 @@ -277,9 +365,11 @@ def hasse_invariant(self, p): INPUT: + `p` -- a prime number > 0 OUTPUT: + 1 or -1 EXAMPLES:: @@ -361,9 +451,11 @@ def hasse_invariant__OMeara(self, p): INPUT: + `p` -- a prime number > 0 OUTPUT: + 1 or -1 EXAMPLES:: @@ -434,14 +526,17 @@ def is_hyperbolic(self, p): the p-adic numbers Q_p. REFERENCES: + This criteria follows from Cassels's "Rational Quadratic Forms": - local invariants for hyperbolic plane (Lemma 2.4, p58) - direct sum formulas (Lemma 2.3 on p58) INPUT: + `p` -- a prime number > 0 OUTPUT: + boolean EXAMPLES:: @@ -489,9 +584,11 @@ def is_anisotropic(self, p): Checks if the quadratic form is anisotropic over the p-adic numbers `Q_p`. INPUT: + `p` -- a prime number > 0 OUTPUT: + boolean EXAMPLES:: @@ -553,9 +650,11 @@ def is_isotropic(self, p): Checks if Q is isotropic over the p-adic numbers `Q_p`. INPUT: + `p` -- a prime number > 0 OUTPUT: + boolean EXAMPLES:: @@ -598,9 +697,11 @@ def anisotropic_primes(self): INPUT: + None OUTPUT: + Returns a list of prime numbers >0. EXAMPLES:: @@ -657,9 +758,11 @@ def compute_definiteness(self): Note: The zero-dim'l form is considered both positive definite and negative definite. INPUT: + QuadraticForm OUTPUT: + boolean EXAMPLES:: @@ -742,12 +845,15 @@ def compute_definiteness_string_by_determinants(self): self.compute_definiteness() for more documentation. INPUT: + None OUTPUT: + string describing the definiteness EXAMPLES: + sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1,1]) sage: Q.compute_definiteness_string_by_determinants() 'pos_def' @@ -831,9 +937,11 @@ def is_positive_definite(self): Note: The zero-dim'l form is considered both positive definite and negative definite. INPUT: + None OUTPUT: + boolean -- True or False EXAMPLES:: @@ -870,9 +978,11 @@ def is_negative_definite(self): Note: The zero-dim'l form is considered both positive definite and negative definite. INPUT: + None OUTPUT: + boolean -- True or False EXAMPLES:: @@ -908,9 +1018,11 @@ def is_indefinite(self): Note: The zero-dim'l form is not considered indefinite. INPUT: + None OUTPUT: + boolean -- True or False EXAMPLES:: @@ -945,9 +1057,11 @@ def is_definite(self): Note: The zero-dim'l form is considered indefinite. INPUT: + None OUTPUT: + boolean -- True or False EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__local_normal_form.py b/src/sage/quadratic_forms/quadratic_form__local_normal_form.py index acf5b88c7da..87d5d3cdf71 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_normal_form.py +++ b/src/sage/quadratic_forms/quadratic_form__local_normal_form.py @@ -1,6 +1,5 @@ """ Local Normal Form - """ #***************************************************************************** @@ -36,9 +35,11 @@ def find_entry_with_minimal_scale_at_prime(self, p): Hessian) associated to the form. INPUT: + `p` -- a prime number > 0 OUTPUT: + a pair of integers >= 0 EXAMPLES:: @@ -89,9 +90,11 @@ def local_normal_form(self, p): the 2x2 blocks in each Jordan component.) INPUT: + `p` -- a positive prime number. OUTPUT: + a quadratic form over ZZ WARNING: Currently this only works for quadratic forms defined over ZZ. @@ -408,10 +411,12 @@ def jordan_blocks_in_unimodular_list_by_scale_power(self, p): correctly for p=2 when the form has an integer Gram matrix. INPUT: + self -- a quadratic form over ZZ, which has integer Gram matrix if p == 2 `p` -- a prime number > 0 OUTPUT: + a list of p-unimodular quadratic forms EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py index 56bf0685d06..a8ed40edbe8 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py +++ b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py @@ -103,9 +103,11 @@ def __init__(self, Q): new conditions. INPUT: + Q -- Quadratic form over ZZ OUTPUT: + a QuadraticFormLocalRepresentationConditions object EXAMPLES:: @@ -212,9 +214,11 @@ def __repr__(self): Print the local conditions. INPUT: + none OUTPUT: + string TO DO: Improve the output for the real numbers, and special output for locally universality. @@ -258,9 +262,11 @@ def __eq__(self, right): Determines if two sets of local conditions are equal. INPUT: + right -- a QuadraticFormLocalRepresentationConditions object OUTPUT: + boolean EXAMPLES:: @@ -305,9 +311,11 @@ def squareclass_vector(self, p): (or the real squareclasses) at the prime `p`. INPUT: + `p` -- a positive prime number or "infinity". OUTPUT: + a list of integers EXAMPLES:: @@ -334,9 +342,11 @@ def local_conditions_vector_for_prime(self, p): Returns a local representation vector for the (possibly infinite) prime `p`. INPUT: + `p` -- a positive prime number. (Is 'infinity' allowed here?) OUTPUT: + a list of integers EXAMPLES:: @@ -392,9 +402,11 @@ def is_universal_at_prime(self, p): Determines if the (integer-valued/rational) quadratic form represents all of `Z_p`. INPUT: + `p` -- a positive prime number or "infinity". OUTPUT: + boolean EXAMPLES:: @@ -439,9 +451,11 @@ def is_universal_at_all_finite_primes(self): Determines if the quadratic form represents `Z_p` for all finite/non-archimedean primes. INPUT: + none OUTPUT: + boolean EXAMPLES:: @@ -478,9 +492,11 @@ def is_universal_at_all_places(self): finite/non-archimedean primes, and represents all real numbers. INPUT: + none OUTPUT: + boolean EXAMPLES:: @@ -533,6 +549,7 @@ def is_locally_represented_at_place(self, m, p): `p` -- a positive prime number or "infinity". OUTPUT: + boolean EXAMPLES:: @@ -602,9 +619,11 @@ def is_locally_represented(self, m): places). INPUT: + `m` -- an integer OUTPUT: + boolean EXAMPLES:: @@ -706,9 +725,11 @@ def local_representation_conditions(self, recompute_flag=False, silent_flag=Fals and is listed before the other ones. INPUT: + none OUTPUT: + A list of 9-element vectors describing the representation obstructions at primes dividing the level. @@ -780,9 +801,11 @@ def is_locally_universal_at_prime(self, p): Determines if the (integer-valued/rational) quadratic form represents all of `Z_p`. INPUT: + `p` -- a positive prime number or "infinity". OUTPUT: + boolean EXAMPLES:: @@ -826,9 +849,11 @@ def is_locally_universal_at_all_primes(self): Determines if the quadratic form represents `Z_p` for all finite/non-archimedean primes. INPUT: + none OUTPUT: + boolean EXAMPLES:: @@ -861,9 +886,11 @@ def is_locally_universal_at_all_places(self): finite/non-archimedean primes, and represents all real numbers. INPUT: + none OUTPUT: + boolean EXAMPLES:: @@ -902,6 +929,7 @@ def is_locally_represented_number_at_place(self, m, p): `p` -- a prime number > 0 or 'infinity' OUTPUT: + boolean EXAMPLES:: @@ -943,9 +971,11 @@ def is_locally_represented_number(self, m): Determines if the rational number m is locally represented by the quadratic form. INPUT: + `m` -- an integer OUTPUT: + boolean EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__mass.py b/src/sage/quadratic_forms/quadratic_form__mass.py index f911f13e8f9..462c5100c61 100644 --- a/src/sage/quadratic_forms/quadratic_form__mass.py +++ b/src/sage/quadratic_forms/quadratic_form__mass.py @@ -39,9 +39,11 @@ def shimura_mass__maximal(self,): but has a small technical restriction when `n` is odd. INPUT: + none OUTPUT: + a rational number EXAMPLE:: @@ -62,9 +64,11 @@ def GHY_mass__maximal(self): Reference: See [GHY, Prop 7.4 and 7.5, p121] and [GY, Thrm 10.20, p25]. INPUT: + none OUTPUT: + a rational number EXAMPLE:: diff --git a/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py b/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py index e30d72c7fa6..a5d75b42f63 100644 --- a/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py +++ b/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py @@ -29,10 +29,12 @@ def parity(self, allow_rescaling_flag=True): Jordan component, after a possible rescaling. INPUT: + self -- a quadratic form with base_ring `ZZ`, which we may require to have integer Gram matrix. OUTPUT: + One of the strings: "even" or "odd" EXAMPLES:: @@ -166,9 +168,11 @@ def conway_species_list_at_odd_prime(self, p): interpret the return value of zero as positive here! =) INPUT: + a positive prime number OUTPUT: + a list of integers EXAMPLES:: @@ -235,6 +239,7 @@ def conway_species_list_at_2(self): interpret the return value of zero as positive here! =) OUTPUT: + a list of integers EXAMPLES:: @@ -318,9 +323,11 @@ def conway_octane_of_this_unimodular_Jordan_block_at_2(self): leftmost position. INPUT: + none OUTPUT: + an integer 0 <= x <= 7 EXAMPLES:: @@ -405,9 +412,11 @@ def conway_diagonal_factor(self, p): Computes the diagonal factor of Conway's `p`-mass. INPUT: + `p` -- a prime number > 0 OUTPUT: + a rational number > 0 EXAMPLES:: @@ -446,9 +455,11 @@ def conway_cross_product_doubled_power(self, p): term in Conway's mass formula. INPUT: + `p` -- a prime number > 0 OUTPUT: + a rational number EXAMPLES:: @@ -483,9 +494,11 @@ def conway_type_factor(self): This is a special factor only present in the mass formula when `p=2`. INPUT: + none OUTPUT: + a rational number EXAMPLES:: @@ -508,9 +521,11 @@ def conway_p_mass(self, p): Computes Conway's `p`-mass. INPUT: + `p` -- a prime number > 0 OUTPUT: + a rational number > 0 EXAMPLES:: @@ -539,9 +554,11 @@ def conway_standard_p_mass(self, p): Computes the standard (generic) Conway-Sloane `p`-mass. INPUT: + `p` -- a prime number > 0 OUTPUT: + a rational number > 0 EXAMPLES:: @@ -576,9 +593,11 @@ def conway_standard_mass(self): Returns the infinite product of the standard mass factors. INPUT: + none OUTPUT: + a rational number > 0 EXAMPLES:: @@ -624,9 +643,11 @@ def conway_mass(self): Compute the mass by using the Conway-Sloane mass formula. INPUT: + none OUTPUT: + a rational number > 0 EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py b/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py index d8b11b097e6..2d63c829f3a 100644 --- a/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py +++ b/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py @@ -139,13 +139,16 @@ def Pall_mass_density_at_odd_prime(self, p): representing itself) defined over `ZZ`, at some prime `p>2`. REFERENCES: + Pall's article "The Weight of a Genus of Positive n-ary Quadratic Forms" appearing in Proc. Symp. Pure Math. VIII (1965), pp95--105. INPUT: + `p` -- a prime number > 2. OUTPUT: + a rational number. EXAMPLES:: @@ -204,9 +207,11 @@ def Watson_mass_at_2(self): in Mathematika 23 (1976), pp 94--106. INPUT: + none OUTPUT: + a rational number EXAMPLES:: @@ -310,9 +315,11 @@ def Kitaoka_mass_at_2(self): Quadratic Forms". INPUT: + none OUTPUT: + a rational number > 0 EXAMPLES:: @@ -415,9 +422,11 @@ def mass_at_two_by_counting_mod_power(self, k): INPUT: + k -- an integer >= 1 OUTPUT: + a rational number EXAMPLE:: diff --git a/src/sage/quadratic_forms/quadratic_form__neighbors.py b/src/sage/quadratic_forms/quadratic_form__neighbors.py index 7265339074d..f337a557704 100644 --- a/src/sage/quadratic_forms/quadratic_form__neighbors.py +++ b/src/sage/quadratic_forms/quadratic_form__neighbors.py @@ -82,6 +82,7 @@ def find_primitive_p_divisible_vector__next(self, p, v=None): vectors, then return None. OUTPUT: + vector or None EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__split_local_covering.py b/src/sage/quadratic_forms/quadratic_form__split_local_covering.py index 10fb7f68e04..f8de77ddae1 100644 --- a/src/sage/quadratic_forms/quadratic_form__split_local_covering.py +++ b/src/sage/quadratic_forms/quadratic_form__split_local_covering.py @@ -27,19 +27,23 @@ def cholesky_decomposition(self, bit_prec = 53): of precision ``bit_prec``. RESTRICTIONS: + Q must be given as a QuadraticForm defined over `\ZZ`, `\QQ`, or some real field. If it is over some real field, then an error is raised if the precision given is not less than the defined precision of the real field defining the quadratic form! REFERENCE: + From Cohen's "A Course in Computational Algebraic Number Theory" book, p 103. INPUT: + ``bit_prec`` -- a natural number (default 53). OUTPUT: + an upper triangular real matrix of precision ``bit_prec``. @@ -122,9 +126,11 @@ def vectors_by_length(self, bound): but does not use the LLL-reduction algorithm. INPUT: + bound -- an integer >= 0 OUTPUT: + A list L of length (bound + 1) whose entry L `[i]` is a list of all vectors of length `i`. @@ -302,9 +308,11 @@ def complementary_subform_to_vector(self, v): now extend `v` to a unimodular matrix. INPUT: + `v` -- a list of self.dim() integers OUTPUT: + a QuadraticForm over `ZZ` @@ -407,9 +415,11 @@ def split_local_cover(self): lattice and the original quadratic form Q. INPUT: + none OUTPUT: + a QuadraticForm over ZZ EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__theta.py b/src/sage/quadratic_forms/quadratic_form__theta.py index dc2c08df4cc..eebbc25f086 100644 --- a/src/sage/quadratic_forms/quadratic_form__theta.py +++ b/src/sage/quadratic_forms/quadratic_form__theta.py @@ -95,10 +95,12 @@ def theta_by_pari(self, Max, var_str='q', safe_flag=True): INPUT: + Max -- an integer >=0 var_str -- a string OUTPUT: + a power series or a vector EXAMPLES:: @@ -151,6 +153,7 @@ def theta_by_cholesky(self, q_prec): {\text{q\_prec} + 1})`.) REFERENCE: + From Cohen's "A Course in Computational Algebraic Number Theory" book, p 102. diff --git a/src/sage/quadratic_forms/quadratic_form__variable_substitutions.py b/src/sage/quadratic_forms/quadratic_form__variable_substitutions.py index b2bb9957b17..0c00115a001 100644 --- a/src/sage/quadratic_forms/quadratic_form__variable_substitutions.py +++ b/src/sage/quadratic_forms/quadratic_form__variable_substitutions.py @@ -26,9 +26,11 @@ def swap_variables(self, r, s, in_place = False): (replacing the original form if the in_place flag is True). INPUT: + `r`, `s` -- integers >= 0 OUTPUT: + a QuadraticForm (by default, otherwise none) EXAMPLES:: @@ -87,11 +89,13 @@ def multiply_variable(self, c, i, in_place = False): quadratic form. INPUT: + `c` -- an element of Q.base_ring() `i` -- an integer >= 0 OUTPUT: + a QuadraticForm (by default, otherwise none) EXAMPLES:: @@ -133,11 +137,13 @@ def divide_variable(self, c, i, in_place = False): ring. INPUT: + `c` -- an element of Q.base_ring() `i` -- an integer >= 0 OUTPUT: + a QuadraticForm (by default, otherwise none) EXAMPLES:: @@ -178,9 +184,11 @@ def scale_by_factor(self, c, change_value_ring_flag=False): to be the field of fractions of the original ring (if necessary). INPUT: + `c` -- a scalar in the fraction field of the value ring of the form. OUTPUT: + A quadratic form of the same dimension EXAMPLES:: @@ -231,9 +239,11 @@ def extract_variables(self, var_indices): var_indices, to give a new quadratic form. INPUT: + var_indices -- a list of integers >= 0 OUTPUT: + a QuadraticForm EXAMPLES:: @@ -267,11 +277,13 @@ def elementary_substitution(self, c, i, j, in_place = False): ## CHECK THIS! original form if the in_place flag is True). INPUT: + `c` -- an element of Q.base_ring() `i`, `j` -- integers >= 0 OUTPUT: + a QuadraticForm (by default, otherwise none) EXAMPLES:: @@ -351,11 +363,13 @@ def add_symmetric(self, c, i, j, in_place = False): INPUT: + `c` -- an element of Q.base_ring() `i`, `j` -- integers >= 0 OUTPUT: + a QuadraticForm (by default, otherwise none) EXAMPLES:: diff --git a/src/sage/quadratic_forms/ternary.pyx b/src/sage/quadratic_forms/ternary.pyx index a14e41955bc..6e34d864f79 100644 --- a/src/sage/quadratic_forms/ternary.pyx +++ b/src/sage/quadratic_forms/ternary.pyx @@ -1,20 +1,18 @@ +""" +Helper code for ternary quadratic forms +""" + #***************************************************************************** # Copyright (C) 2012 Gustavo Rama # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** - from sage.rings.integer_ring import ZZ from sage.matrix.constructor import matrix, identity_matrix, diagonal_matrix from sage.modules.free_module_element import vector @@ -33,9 +31,11 @@ def red_mfact(a,b): Auxiliar function for reduction that finds the reduction factor of a, b integers. INPUT: + - a, b integers OUTPUT: + Integer EXAMPLES:: diff --git a/src/sage/quadratic_forms/ternary_qf.py b/src/sage/quadratic_forms/ternary_qf.py index 58b6af84c59..152a10fb713 100644 --- a/src/sage/quadratic_forms/ternary_qf.py +++ b/src/sage/quadratic_forms/ternary_qf.py @@ -3,7 +3,7 @@ AUTHOR: - - Gustavo Rama +- Gustavo Rama Based in code of Gonzalo Tornaria @@ -49,9 +49,11 @@ class TernaryQF(SageObject): The ``TernaryQF`` class represents a quadratic form in 3 variables with coefficients in Z. INPUT: + - `v` -- a list or tuple of 6 entries: [a,b,c,r,s,t] OUTPUT: + - the ternary quadratic form a*x^2 + b*y^2 + c*z^2 + r*y*z + s*x*z + t*x*y. EXAMPLES:: @@ -1059,9 +1061,10 @@ def xi_rec(self, p): def symmetry(self, v): """ Returns A the automorphism of the ternary quadratic form such that: - :: + - A*v = -v. - A*u = 0, if u is orthogonal to v. + where v is a given vector. EXAMPLES:: @@ -1168,23 +1171,23 @@ def _border(self,n): """ Auxiliar function to find the automorphisms of a positive definite ternary quadratic form. It return a boolean whether the n-condition is true. If Q = TernaryQF([a,b,c,r,s,t]), the conditions are: - :: - 1- a = t, s = 2r. - 2- a = s, t = 2r. - 3- b = r, t = 2s. - 4- a = -t. - 5- a = -s. - 6- b = -r. - 7- a + b + r + s + t = 0, 2a + 2s + t = 0. - 8- a = b, r = s. - 9- b = c, s = t. - 10- r = s, r = 0. - 11- r = t, r = 0. - 12- s = t, s = 0. - 13- r = s, s = t, t = a. - 14- a = s, a = t. - 15- a = b, a + b + r + s + t = 0. - 16- a = b, b = c, a + b + r + s + t = 0. + + 1. a = t, s = 2r. + 2. a = s, t = 2r. + 3. b = r, t = 2s. + 4. a = -t. + 5. a = -s. + 6. b = -r. + 7. a + b + r + s + t = 0, 2a + 2s + t = 0. + 8. a = b, r = s. + 9. b = c, s = t. + 10. r = s, r = 0. + 11. r = t, r = 0. + 12. s = t, s = 0. + 13. r = s, s = t, t = a. + 14. a = s, a = t. + 15. a = b, a + b + r + s + t = 0. + 16. a = b, b = c, a + b + r + s + t = 0. EXAMPLES:: @@ -1236,8 +1239,6 @@ def _border(self,n): sage: Q16 = TernaryQF([4, 4, 4, -2, -3, -3]) sage: Q16._border(16) True - - """ a, b, c, r, s, t = self.coefficients() diff --git a/src/sage/quivers/algebra.py b/src/sage/quivers/algebra.py index 1cef6e9dd7a..c9feb117ade 100644 --- a/src/sage/quivers/algebra.py +++ b/src/sage/quivers/algebra.py @@ -5,6 +5,7 @@ #***************************************************************************** # Copyright (C) 2012 Jim Stark # 2013 Simon King +# 2014 Simon King # # Distributed under the terms of the GNU General Public License (GPL) # @@ -21,6 +22,7 @@ import six from sage.misc.cachefunc import cached_method from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from algebra_elements import PathAlgebraElement class PathAlgebra(CombinatorialFreeModule): r""" @@ -39,9 +41,17 @@ class PathAlgebra(CombinatorialFreeModule): - ``P`` -- the path semigroup of a quiver `Q` + - ``order`` -- optional string, one of "negdegrevlex" (default), + "degrevlex", "negdeglex" or "deglex", defining the monomial order to be + used. + OUTPUT: - - the path algebra `kP` + - the path algebra `kP` with the given monomial order + + NOTE: + + Monomial orders that are not degree orders are not supported. EXAMPLES:: @@ -52,10 +62,13 @@ class PathAlgebra(CombinatorialFreeModule): sage: A.variable_names() ('e_1', 'e_2', 'e_3', 'a', 'b') - Note that path algebras are uniquely defined by their quiver and field:: + Note that path algebras are uniquely defined by their quiver, field and + monomial order:: sage: A is P.algebra(GF(7)) True + sage: A is P.algebra(GF(7), order="degrevlex") + False sage: A is P.algebra(RR) False sage: A is DiGraph({1:{2:['a']}}).path_semigroup().algebra(GF(7)) @@ -110,6 +123,8 @@ class PathAlgebra(CombinatorialFreeModule): sage: TestSuite(A).run() """ + Element = PathAlgebraElement + ########################################################################### # # # PRIVATE FUNCTIONS # @@ -117,7 +132,7 @@ class PathAlgebra(CombinatorialFreeModule): # # ########################################################################### - def __init__(self, k, P): + def __init__(self, k, P, order = "negdegrevlex"): """ Creates a :class:`PathAlgebra` object. Type ``PathAlgebra?`` for more information. @@ -148,13 +163,36 @@ def __init__(self, k, P): from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis self._quiver = P.quiver() self._semigroup = P + self._ordstr = order super(PathAlgebra, self).__init__(k, self._semigroup, prefix='', - element_class=self.Element, + #element_class=self.Element, category=GradedAlgebrasWithBasis(k), bracket=False) self._assign_names(self._semigroup.variable_names()) + def order_string(self): + """ + Return the string that defines the monomial order of this algebra. + + EXAMPLES:: + + sage: P1 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: P2 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P3 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="negdeglex") + sage: P4 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="deglex") + sage: P1.order_string() + 'negdegrevlex' + sage: P2.order_string() + 'degrevlex' + sage: P3.order_string() + 'negdeglex' + sage: P4.order_string() + 'deglex' + + """ + return self._ordstr + @cached_method def gens(self): """ @@ -169,9 +207,7 @@ def gens(self): sage: A.gens() (e_1, e_2, e_3, e_4, a, b, c) """ - return tuple(self._from_dict( {index: self.base_ring().one()}, - remove_zeros=False ) - for index in self._semigroup.gens()) + return tuple(self.gen(i) for i in range(self.ngens())) @cached_method def arrows(self): @@ -207,6 +243,7 @@ def idempotents(self): remove_zeros=False ) for index in self._semigroup.idempotents()) + @cached_method def gen(self, i): """ Return the `i`-th generator of this algebra. @@ -249,34 +286,46 @@ def _element_constructor_(self, x): TESTS:: - sage: A = DiGraph({1:{2:['a']}}).path_semigroup().algebra(QQ) - sage: B = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup().algebra(QQ) - sage: x = A('a') + 1 # indirect doctest + sage: A = DiGraph({2:{3:['b']}}).path_semigroup().algebra(ZZ) + sage: B = DiGraph({0:{1:['a']}, 1:{2:['c']}, 2:{3:['b']}}).path_semigroup().algebra(GF(5)) + sage: x = A('b') + 1 # indirect doctest sage: x - a + e_1 + e_2 + e_2 + b + e_3 sage: B(x) # indirect doctest - a + e_1 + e_2 + e_2 + b + e_3 sage: A(1) # indirect doctest - e_1 + e_2 + e_2 + e_3 + sage: B(2) # indirect doctest + 2*e_0 + 2*e_1 + 2*e_2 + 2*e_3 + sage: B([(0,1,'a'),(1,2,'c')]) # indirect doctest + a*c + """ from sage.quivers.paths import QuiverPath # If it's an element of another path algebra, do a linear combination # of the basis - if isinstance(x, CombinatorialFreeModuleElement) and isinstance(x.parent(), PathAlgebra): + if isinstance(x, PathAlgebraElement) and isinstance(x.parent(), PathAlgebra): + result = {} coeffs = x.monomial_coefficients() - result = self.zero() for key in coeffs: - result += coeffs[key]*self.monomial(key) - return result + result[self._semigroup(key)] = coeffs[key] + return self.element_class(self, result) # If it's a QuiverPath return the associated basis element if isinstance(x, QuiverPath): - return self.monomial(x) + return self.element_class(self, {x: self.base_ring().one()}) + + # If it's a scalar, return a multiple of one: + if x in self.base_ring(): + return self.one()*x - # If it's a tuple or a list try and create a QuiverPath from it and + # If it's a tuple or a list, try and create a QuiverPath from it and # then return the associated basis element if isinstance(x, (tuple, list, six.string_types)): - return self.monomial(self._semigroup(x)) + return self.element_class(self, {self._semigroup(x): self.base_ring().one()}) + + if isinstance(x, dict): + return self.element_class(self, x) # Otherwise let CombinatorialFreeModule try return super(PathAlgebra, self)._element_constructor_(x) @@ -338,8 +387,19 @@ def _coerce_map_from_(self, other): sage: A2.one() * a == a # indirect doctest True - """ + :: + sage: A = DiGraph({2:{3:['b']}}).path_semigroup().algebra(ZZ) + sage: B = DiGraph({0:{1:['a']}, 1:{2:['c']}, 2:{3:['b']}}).path_semigroup().algebra(GF(5)) + sage: x = A('b') + 1 # indirect doctest + sage: x + e_2 + b + e_3 + sage: B(2) + 2*e_0 + 2*e_1 + 2*e_2 + 2*e_3 + sage: B(2)*x*B(3) # indirect doctest + e_2 + b + e_3 + + """ if isinstance(other, PathAlgebra) and self._base.has_coerce_map_from(other._base): OQ = other._quiver SQ = self._quiver @@ -362,90 +422,51 @@ def _repr_(self): """ return "Path algebra of {0} over {1}".format(self._quiver, self._base) - ########################################################################### - # # - # CATEGORY METHODS # - # These functions are used by the category to implement the algebra # - # structure. # - # # - ########################################################################### - - def _monomial(self, index): + # String representation of a monomial + def _repr_monomial(self, data): """ - This method makes sure that the invalid path evaluates as zero. + String representation of a monomial. - TESTS:: + INPUT: - sage: P = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() - sage: P.algebra(ZZ)(P('a')*P('b')) - a*b - sage: P.algebra(ZZ)(P('b'))*P.algebra(ZZ)(P('a')) # indirect doctest - 0 + A list providing the indices of the path algebra generators occurring + in the monomial. - The following was an issue during work at :trac:`12630`:: + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: X # indirect doctest + 5*e_0 + a + 2*b + 3*c + 3*e_2 - sage: P1 = DiGraph({1:{2:['a']}}).path_semigroup() - sage: P2 = DiGraph({1:{2:['a','b']}}).path_semigroup() - sage: A1 = P1.algebra(GF(3)) - sage: A2 = P2.algebra(GF(3)) - sage: b = P2.arrows()[1]; b - b - sage: A1(b) - Traceback (most recent call last): - ... - ValueError: b is not an edge """ - if index is not None: - return self._from_dict( {self._semigroup(index): self.base_ring().one()}, - remove_zeros=False ) - return self.zero() + # m is [list, pos, mid], where the list gives the nb of arrows, pos + # gives the component in the module, and mid gives the length of the + # left factor in a two-sided module. + arrows = self.variable_names() + return '*'.join( [arrows[n] for n in data] ) - def product_on_basis(self, p1, p2): + def _latex_monomial(self, data): """ - Return the product ``p1*p2`` in the path algebra. + Latex string representation of a monomial. INPUT: - - ``p1``, ``p2`` -- QuiverPaths - - OUTPUT: - - - :class:`~sage.combinat.free_module.CombinatorialFreeModuleElement` + A list providing the indices of the path algebra generators occurring + in the monomial. EXAMPLES:: - sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}, 3:{4:['c']}}).path_semigroup() - sage: p1 = Q('a') - sage: p2 = Q([(2, 3, 'b'), (3, 4, 'c')]) - sage: A = Q.algebra(QQ) - sage: A.product_on_basis(p1, p2) - a*b*c - sage: A.product_on_basis(p2, p1) - 0 - """ - PSG = self._semigroup - p = PSG(p1)*PSG(p2) - if p is not None: - return self.basis()[p] - else: - return self.zero() + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: latex(X) # indirect doctest + 5e_0 + a + 2b + 3c + 3e_2 - def degree_on_basis(self, p): """ - Return the degree of the monomial specified by the path ``p``. - - EXAMPLES:: - - sage: A = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup().algebra(QQ) - sage: A.degree_on_basis([(1, 1)]) - 0 - sage: A.degree_on_basis('a') - 1 - sage: A.degree_on_basis([(1, 2, 'a'), (2, 3, 'b')]) - 2 - """ - return len(self._semigroup(p)) + arrows = self.variable_names() + return '\\cdot '.join( [arrows[n] for n in data] ) + @cached_method def one(self): """ Return the multiplicative identity element. @@ -459,8 +480,9 @@ def one(self): sage: A.one() e_1 + e_2 + e_3 """ - return self.sum_of_monomials([self._semigroup([(v, v)], check=False) - for v in self._quiver]) + one = self.base_ring().one() + D = dict((index,one) for index in self._semigroup.idempotents()) + return self._from_dict( D ) ########################################################################### # # @@ -512,6 +534,58 @@ def semigroup(self): """ return self._semigroup + def degree_on_basis(self, x): + """ + Return ``x.degree()``. + + This function is here to make some methods work that are inherited + from :class:`~sage.combinat.free_module.CombinatorialFreeModule`. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: X = a+2*b+3*c*e-a*d+5*e_0+3*e_2 + sage: X + 5*e_0 + a - a*d + 2*b + 3*e_2 + sage: X.homogeneous_component(0) # indirect doctest + 5*e_0 + 3*e_2 + sage: X.homogeneous_component(1) + a + 2*b + sage: X.homogeneous_component(2) + -a*d + sage: X.homogeneous_component(3) + 0 + """ + return x.degree() + + def sum(self, iter_of_elements): + """ + Returns the sum of all elements in ``iter_of_elements`` + + INPUT: + + - ``iter_of_elements``: iterator of elements of ``self`` + + NOTE: + + It overrides a method inherited from + :class:`~sage.combinat.free_module.CombinatorialFreeModule`, which + relies on a private attribute of elements---an implementation + detail that is simply not available for + :class:`~sage.quivers.algebra_elements.PathAlgebraElement`. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: A.sum((a, 2*b, 3*c*e, -a*d, 5*e_0, 3*e_2)) + 5*e_0 + a - a*d + 2*b + 3*e_2 + """ + return sum(iter_of_elements, self.zero()) + def homogeneous_component(self, n): """ Return the `n`-th homogeneous piece of the path algebra. @@ -537,6 +611,7 @@ def homogeneous_component(self, n): sage: A = P.algebra(ZZ) sage: A.homogeneous_component(3) Free module spanned by [a*b*c, b*c*a, c*a*b] over Integer Ring + """ basis = [] for v in self._semigroup._quiver: @@ -579,67 +654,3 @@ def homogeneous_components(self): result.append(c) i += 1 return result - - ########################################################################### - # # - # ELEMENT CLASS # - # The class of elements of the path algebra. # - # # - ########################################################################### - - class Element(CombinatorialFreeModuleElement): - def is_homogeneous(self): - """ - Return ``True`` if and only if this element is homogeneous. - - EXAMPLES:: - - sage: A = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup().algebra(QQ) - sage: (A('a') + A('b')).is_homogeneous() - True - sage: (A(1) + A('a')).is_homogeneous() - False - """ - # Get the support, the zero element is homogeneous - paths = self.support() - if not paths: - return True - - # Compare the rest of the paths, they must be the same length - for p in paths[1:]: - if len(p) != len(paths[0]): - return False - - return True - - def degree(self): - """ - The degree of ``self``, if ``self`` is homogeneous. - - EXAMPLES:: - - sage: A = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup().algebra(QQ) - sage: A(1).degree() - 0 - sage: (A('a') + A('b')).degree() - 1 - - An error is raised if the element is not homogeneous:: - - sage: (A(1) + A('a')).degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - """ - # Deal with zero - paths = self.support() - if not paths: - raise ValueError("The zero element does not have a well-defined degree.") - - # Check that the element is homogeneous - for p in paths[1:]: - if len(p) != len(paths[0]): - raise ValueError("Element is not homogeneous.") - - return len(paths[0]) - diff --git a/src/sage/quivers/algebra_elements.pxd b/src/sage/quivers/algebra_elements.pxd new file mode 100644 index 00000000000..dd49e175449 --- /dev/null +++ b/src/sage/quivers/algebra_elements.pxd @@ -0,0 +1,94 @@ +""" +Cython types for elements of path algebras +""" +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +# This file declares the types. The implementation of the basic on these types +# is in algebra_elements.pxi, the implementation of the Python class is in +# algebra_elements.pyx. The latter file also contains all doctests. + +from cpython cimport PyObject +from sage.data_structures.bounded_integer_sequences cimport * +from sage.structure.element cimport RingElement, ModuleElement, Element +from sage.quivers.paths cimport QuiverPath + +# Type definitions + +cdef struct path_mon_s: + # The monomial is of the form "a*I_i*b" with "a" a path + # of length "l_len", and I_i the generator of the i-th component of a free module. + mp_size_t l_len + # In a sub-module of a direct sum, "i=pos" denotes the direct summand that + # this monomial belongs to. If pos==-1 then the monomial is supposed to be + # element of a path algebra, and should be formed by a single path. In particular, + # l_len has to be zero. + long pos + # In the Schreyer order, monomials of the form a*I_i*b are not + # compared directly, but to each position is associated a monomial s_i, + # and a*I_i*b is compared with c*I_j*d by first comparing a*s_i*b with + # c*s_j*d. s_length is the length of s_i. + mp_size_t s_len + # paths are encoded as lists of integers. We store a*s_i*b if the monomial is + # a*I_i*b. + biseq_t path + +ctypedef path_mon_s path_mon_t[1] + +cdef struct path_term_t: + # A term is given by a monomial "mon" and a coefficient "coef". + path_mon_t mon + # We need to manually take care of the reference count for the + # coefficient! + PyObject *coef + # In a polynomial, the terms are arranged in a pointered list + path_term_t *nxt + +# Type of monomial ordering functions. +# Returns -1, 0 or 1, depending on whether the first argument is +# smaller (wrt. the chosen ordering function), equal to, or greater +# than the second argument. +ctypedef int (*path_order_t)(path_mon_t, path_mon_t) except -2 + +# Polynomials are decreasingly sorted lists of terms, wrt. some fixed +# monomial ordering. The list starts with pointer .lead. For convenience, +# the number of terms is directly available by .nterms. +cdef struct path_poly_t: + path_term_t *lead + size_t nterms + +# In path_poly_t, the terms need not to have all the same start and end +# points. path_homog_poly_t points to a path_poly_t whose terms are all +# guaranteed to start and end at the given vertex labels (which are integers). +# We will work with lists of path_homog_poly_t, and thus have a pointer +# to the next start and end point homogeneous polynomial. +cdef struct path_homog_poly_t: + path_poly_t *poly + int start, end + path_homog_poly_t *nxt + +cdef class PathAlgebraElement(RingElement): + # The terms of path algebra element are stored as a list of start and + # end point homogeneous polynomials. These are sorted increasingly + # according to the (start, end) point pairs. + cdef path_homog_poly_t *data + # A fixed term ordering is stored along with the path algebra element. + # This ordering has to be passed as an argument to all boilerplate + # functions. + cdef path_order_t cmp_terms + cdef long _hash + cpdef ssize_t degree(self) except -2 + cpdef dict monomial_coefficients(self) + cpdef list coefficients(self) + cpdef list monomials(self) + cpdef list support(self) + cpdef list terms(self) + cpdef object coefficient(self, QuiverPath P) + cdef list _sorted_items_for_printing(self) + cdef inline PathAlgebraElement _new_(self, path_homog_poly_t *h) diff --git a/src/sage/quivers/algebra_elements.pxi b/src/sage/quivers/algebra_elements.pxi new file mode 100644 index 00000000000..30f9acd342d --- /dev/null +++ b/src/sage/quivers/algebra_elements.pxi @@ -0,0 +1,1313 @@ +""" +Boilerplate functions for a cython implementation of elements of path algebras. + +AUTHORS: + +- Simon King (2015-08) + +""" + +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# 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/ +#***************************************************************************** + +include "sage/ext/interrupt.pxi" +include "sage/ext/stdsage.pxi" +include "sage/data_structures/bitset.pxi" + +from cpython.ref cimport * +from cython.operator cimport predecrement as predec, postincrement as postinc +from sage.libs.gmp.mpn cimport mpn_cmp +from libc.stdlib cimport free + +cdef extern from *: # Defined by Cython + int unlikely(int) nogil + int likely(int) nogil + +######################################## +## +## Allocation and Deallocation of monomials +# +# Monomials are expensive, hence, copying will just be done by increasing a +# reference counter. + +# Create a monomial by copying the given bounded integer sequence +cdef bint mon_create(path_mon_t out, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len) except -1: + biseq_init_copy(out.path, Mon) + out.pos = Pos + out.l_len = L_len + out.s_len = S_len + +# The following is only used in the free-list for terms. +# It changes an existing monomial in-place (which should NEVER +# be done on a monomial that is in use), re-allocating memory +# and filling it with a copy of the given bounded integer sequence. +cdef bint mon_realloc(path_mon_t out, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len) except -1: + biseq_dealloc(out.path) + sig_check() + biseq_init_copy(out.path, Mon) + out.pos = Pos + out.l_len = L_len + out.s_len = S_len + +# Create a monomial without copying the given bounded integer sequence +cdef bint mon_create_keep(path_mon_t out, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len) except -1: + out.path[0] = Mon[0] + out.pos = Pos + out.l_len = L_len + out.s_len = S_len + +# The following is only used in the free-list for terms. +# It changes an existing monomial in-place (which should NEVER +# be done on a monomial that is in use), re-allocating memory +# and filling it with the given bounded integer sequence (not a copy). +cdef bint mon_realloc_keep(path_mon_t out, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len): + biseq_dealloc(out.path) + out.path[0] = Mon[0] + out.pos = Pos + out.l_len = L_len + out.s_len = S_len + return True + +cdef inline bint mon_copy(path_mon_t out, path_mon_t M) except -1: + out.pos = M.pos + out.l_len = M.l_len + out.s_len = M.s_len + biseq_init_copy(out.path, M.path) + +# Deallocate the monomial, which means to decrease the reference count, +# or to actually deallocate the data if there is no reference left. +cdef inline void mon_free(path_mon_t M): + biseq_dealloc(M.path) + +# Linearisation +cdef inline tuple mon_pickle(path_mon_t M): + return (bitset_pickle(M.path.data) if M.path.length>0 else (), + M.path.itembitsize, M.path.length, M.pos, M.l_len, M.s_len) + +# De-linearisation +cdef bint mon_unpickle(path_mon_t out, tuple data) except -1: + cdef tuple bitset_data + cdef mp_bitcnt_t itembitsize + cdef mp_size_t length + cdef long Pos + cdef mp_size_t L_len + cdef mp_size_t S_len + bitset_data, itembitsize, length, Pos, L_len, S_len = data + out.path.itembitsize = itembitsize + out.path.mask_item = limb_lower_bits_up(itembitsize) + out.path.length = length + + # bitset_unpickle assumes that out.path.data is initialised. + bitset_init(out.path.data, GMP_LIMB_BITS) + if bitset_data: + sig_on() + bitset_unpickle(out.path.data, bitset_data) + sig_off() + out.pos = Pos + out.l_len = L_len + out.s_len = S_len + + +######################################## +## +## Monomial orders---we only use degree orders + +# Negative degree reverse lexicographic ordering +cdef int negdegrevlex(path_mon_t M1, path_mon_t M2) except -2: + # a*s_i*b + # 1. deg(a*b) > deg(c*d), otherwise + # 2. deg(a) > deg(c) (note that one of them may be -1), otherwise + # 3. deg(s_i) < deg(s_j), otherwise + # 4. a*s_i*b <_revlex c*s_j*d, otherwise + # 5. i + # 1. deg(a*b) < deg(c*d), otherwise + # 2. deg(a) < deg(c) (note that one of them may be -1), otherwise + # 3. deg(s_i) > deg(s_j), otherwise + # 4. a*s_i*b <_revlex c*s_j*d, otherwise + # 5. i + # 1. deg(a*b) > deg(c*d), otherwise + # 2. deg(a) > deg(c) (note that one of them may be -1), otherwise + # 3. deg(s_i) < deg(s_j), otherwise + # 4. a*s_i*b <_lex c*s_j*d, otherwise + # 5. i + # 1. deg(a*b) < deg(c*d), otherwise + # 2. deg(a) < deg(c) (note that one of them may be -1), otherwise + # 3. deg(s_i) > deg(s_j), otherwise + # 4. a*s_i*b <_lex c*s_j*d, otherwise + # 5. icheck_malloc(sizeof(freelist_t)) +freelist.used = 0 +freelist.pool = check_allocarray(poolsize, sizeof(path_term_t*)) + +# Deallocate the term, and return the pointer .nxt, without using kill list +cdef inline path_term_t *term_free_force(path_term_t *T): + mon_free(T.mon) + cdef path_term_t *out = T.nxt + sage_free(T) + return out + +cdef class _FreeListProtector: + """ + The purpose of this class is to deallocate our freelist + of path algebra terms. When its only instance is deleted (which + should only happen when a SageMath session ends), then the + freelist is cleared. + """ + def __dealloc__(self): + """ + TESTS:: + + sage: s = Sage() + sage: s.eval("P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'))") + '' + sage: s.eval("P.inject_variables()") + 'Defining e_1, x, y, z' + sage: s.eval("x*y+y*z*x") + 'x*y + y*z*x' + sage: s.quit() # indirect doctest + """ + cdef size_t i + for i in range(freelist.used): + term_free_force(freelist.pool[i]) + sig_check() + sage_free(freelist.pool) + sage_free(freelist) + +_freelist_protector = _FreeListProtector() + +# Put the term on the freelist (unless the list is full), +# and return the pointer .nxt +cdef inline path_term_t *term_free(path_term_t *T): + if T.coef!=NULL: + Py_XDECREF(T.coef) + if likely(freelist.used < poolsize): + freelist.pool[postinc(freelist.used)] = T + return T.nxt + return term_free_force(T) + +# Create a term by copying the given bounded integer sequence, +# with the given coefficient +cdef path_term_t *term_create(object coef, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len) except NULL: + cdef path_term_t *out + if likely(freelist.used > 0): + out = freelist.pool[predec(freelist.used)] + mon_realloc(out.mon, Mon, Pos, L_len, S_len) + else: + out = check_malloc(sizeof(path_term_t)) + mon_create(out.mon, Mon, Pos, L_len, S_len) + Py_INCREF(coef) + out.coef = coef + out.nxt = NULL + return out + +# Create a term without copying the given bounded integer sequence +cdef path_term_t *term_create_keep(object coef, biseq_t Mon, long Pos, mp_size_t L_len, mp_size_t S_len) except NULL: + cdef path_term_t *out + if likely(freelist.used) > 0: + out = freelist.pool[predec(freelist.used)] + mon_realloc_keep(out.mon, Mon, Pos, L_len, S_len) + else: + out = check_malloc(sizeof(path_term_t)) + mon_create_keep(out.mon, Mon, Pos, L_len, S_len) + Py_INCREF(coef) + out.coef = coef + #out.nxt = NULL # to be taken care of externally + return out + +# Create a term with a given coefficient, but empty monomial +cdef path_term_t *term_create_blank(object coef) except NULL: + cdef path_term_t *out + if likely(freelist.used > 0): + out = freelist.pool[predec(freelist.used)] + mon_free(out.mon) + else: + out = check_malloc(sizeof(path_term_t)) + Py_INCREF(coef) + out.coef = coef + #out.nxt = NULL # to be taken care of externally + return out + +###################################################################### +###################################################################### + +# Copy a term; recall that copying the underlying monomial +# just means to increase its reference count. However, +# the copied TERM is new. +# The .nxt attribute is NOT defined on the copy of the term. +cdef path_term_t *term_copy(path_term_t *T) except NULL: + cdef path_term_t *out + if likely(freelist.used > 0): + out = freelist.pool[predec(freelist.used)] + mon_free(out.mon) + else: + out = check_malloc(sizeof(path_term_t)) + sig_on() + mon_copy(out.mon, T.mon) + sig_off() + Py_XINCREF(T.coef) + out.coef = T.coef + # out.nxt is supposed to be taken care of externally + return out + +# Create a copy of T and recursively of T.nxt +cdef path_term_t *term_copy_recursive(path_term_t *T) except NULL: + cdef path_term_t *out = term_copy(T) + cdef path_term_t *first = out + T = T.nxt + while T!=NULL: + out.nxt = term_copy(T) + out = out.nxt + T = T.nxt + out.nxt = NULL + return first + +# Hash of a term; probably not a good one. +cdef inline long term_hash(path_term_t *T): + return (hash(T.coef)+(T.mon.l_len<<5)+(T.mon.pos<<10))^bitset_hash(T.mon.path.data) + +# Recall that a monomial a*I*b (with I a generator of a free module) +# is encoded by a path a*s*b for some monomial s that refers to a +# so-called Schreyer ordering. The total degree of a*I*b is the length +# of a plus the length of b. +cdef inline mp_size_t term_total_degree(path_term_t *T): + return T.mon.path.length-T.mon.s_len + +# Linearisation +cdef inline tuple term_pickle(path_term_t *T): + return (T.coef, mon_pickle(T.mon)) + +# De-linearisation +cdef inline path_term_t *term_unpickle(object coef, tuple mon_data) except NULL: + cdef path_term_t *out = term_create_blank(coef) + mon_unpickle(out.mon, mon_data) + return out + +######################################## +## +## Multiplication of monomials + +# Return T*p, for a path p and a monomial T. +cdef bint mon_mul_path(path_mon_t out, path_mon_t T, biseq_t p) except -1: + if unlikely(p.length == 0): + return mon_copy(out, T) + biseq_init_concat(out.path, T.path, p) + out.pos = T.pos + out.l_len = T.l_len + out.s_len = T.s_len + +# Return p*T, for a path p and a monomial T. +cdef bint path_mul_mon(path_mon_t out, biseq_t p, path_mon_t T) except -1: + if unlikely(p.length == 0): + return mon_copy(out, T) + biseq_init_concat(out.path, p, T.path) + out.pos = T.pos + out.l_len = 0 if T.pos==-1 else T.l_len+p.length + out.s_len = T.s_len + +# Return p*T*q, for paths p,q and a monomial T. +cdef bint path_mul_mon_mul_path(path_mon_t out, biseq_t p, path_mon_t T, biseq_t q) except -1: + # .l_len and .s_len are taken care of externally! + if unlikely(p.length==0 and q.length==0): + return mon_copy(out, T) + if unlikely(p.length == 0): + return mon_mul_path(out, T, q) + if unlikely(q.length == 0): + return path_mul_mon(out, p, T) + out.pos = T.pos + if unlikely(T.path.length == 0): + biseq_init_concat(out.path, p, q) + return True + cdef mp_size_t pTlength = p.length + T.path.length + cdef mp_size_t res_length = pTlength + q.length + biseq_init(out.path, res_length, p.itembitsize) + if res_length == 0: + return False + cdef mp_bitcnt_t pTsize = p.data.size+T.path.data.size + sig_on() + bitset_lshift(out.path.data, q.data, pTsize) + cdef mp_bitcnt_t p_offset = p.data.size%GMP_LIMB_BITS + # p_limbs gives the index of the limb that will store the first bit of the + # shifted version of T. + cdef mp_bitcnt_t p_limbs = (p.data.limbs - 1) if p_offset>0 else p.data.limbs + + # pT_limbs gives the index of the last limb used to store p+T + cdef mp_bitcnt_t pT_limbs = (pTsize-1)//GMP_LIMB_BITS + if ((T.path.data.size-1)%GMP_LIMB_BITS)+p_offset >= GMP_LIMB_BITS: + # We shift all limbs of T. The highest bits of the highest limbs are + # pushed out and returned by mpn_lshift. We need to assign them to the + # beginning of the last limb that is (partially) occupied by p+T + out.path.data.bits[pT_limbs] |= mpn_lshift(out.path.data.bits+p_limbs, + T.path.data.bits, T.path.data.limbs, p_offset) + else: + if T.path.data.limbs>1: + # If we would move all limbs of T, then the result would override + # the lowest limb of the shifted copy of q. We thus only move all + # but the last limb of T, assigning to the beginning of the last + # limb of p+T the bits that have been pushed out. + out.path.data.bits[pT_limbs] |= mpn_lshift(out.path.data.bits+p_limbs, + T.path.data.bits, T.path.data.limbs-1, p_offset) + # Last, we need to move the last limb of T (which is only + # partially occupied), namely into the spot between the previously + # moved parts of T and the beginning of the shifted copy of q. + out.path.data.bits[pT_limbs] |= (T.path.data.bits[T.path.data.limbs-1]< 0): + out = freelist.pool[predec(freelist.used)] + mon_free(out.mon) + else: + out = check_malloc(sizeof(path_term_t)) + cdef object coef = -T.coef + out.coef = coef + Py_INCREF(coef) + mon_copy(out.mon, T.mon) + # out.nxt is supposed to be taken care of externally + return out + +# Return -T, and recurse over T.nxt +cdef path_term_t *term_neg_recursive(path_term_t *T) except NULL: + cdef path_term_t *out = term_neg(T) + cdef path_term_t *first = out + T = T.nxt + while T!=NULL: + sig_check() + out.nxt = term_neg(T) + out = out.nxt + T = T.nxt + out.nxt = NULL + return first + +# Return coef*T +cdef path_term_t *term_scale(path_term_t *T, object coef) except NULL: + cdef path_term_t *out + if likely(freelist.used > 0): + out = freelist.pool[predec(freelist.used)] + mon_free(out.mon) + else: + out = check_malloc(sizeof(path_term_t)) + cdef object new_coef = coef*T.coef + if new_coef: + out.coef = new_coef + Py_INCREF(new_coef) + mon_copy(out.mon, T.mon) + else: + out.coef = NULL + # out.nxt is supposed to be taken care of externally + return out + +# Return coef*T and recurse over T.nxt +cdef path_term_t *term_scale_recursive(path_term_t *T, object coef) except NULL: + cdef path_term_t *out = term_scale(T,coef) + cdef path_term_t *first = out + T = T.nxt + while T!=NULL: + sig_check() + out.nxt = term_scale(T, coef) + if out.nxt.coef == NULL: + term_free(out.nxt) + out.nxt = NULL + else: + out = out.nxt + T = T.nxt + out.nxt = NULL + return first + +# Return T1*T2. +# An error is raised if both T1 and T2 belong to a free module over a +# path algebra (but not to the path algebra itself). Hence, this function +# implements multiplication of a term in a path algebra, and the action +# of a term of a path algebra on a term in a free module. +cdef path_term_t *term_mul_term(path_term_t *T1, path_term_t *T2) except NULL: + cdef mp_size_t new_l_len + cdef long new_pos + cdef mp_size_t new_s_len + if T1.mon.pos!=-1: + if T2.mon.pos!=-1: + raise ValueError("We cannot multiply two module elements") + new_l_len = T1.mon.l_len + new_pos = T1.mon.pos + new_s_len = T1.mon.s_len + elif T2.mon.pos!=-1: + new_l_len = T2.mon.l_len+T1.mon.path.length + new_pos = T2.mon.pos + new_s_len = T2.mon.s_len + else: + new_l_len = 0 + new_pos = -1 + new_s_len = 0 + cdef object new_coef = (T1.coef)*(T2.coef) + + cdef path_term_t *out + if likely(freelist.used > 0): + out = freelist.pool[predec(freelist.used)] + if new_coef: + out.coef = (new_coef) + Py_INCREF(new_coef) + biseq_dealloc(out.mon.path) + biseq_init_concat(out.mon.path, T1.mon.path, T2.mon.path) + else: + out.coef = NULL + else: + out = check_malloc(sizeof(path_term_t)) + if new_coef: + out.coef = (new_coef) + Py_INCREF(new_coef) + biseq_init_concat(out.mon.path, T1.mon.path, T2.mon.path) + else: + out.coef = NULL + out.mon.pos = new_pos + out.mon.l_len = new_l_len + out.mon.s_len = new_s_len + out.nxt = NULL + return out + +######################################## +## +## Basics for polynomials + +# Create an empty polynomial +cdef inline path_poly_t *poly_create() except NULL: + cdef path_poly_t *out = check_malloc(sizeof(path_poly_t)) + out.lead = NULL + out.nterms = 0 + return out + +# Deallocate all terms of the polynomial, but NOT the polynomial itself +cdef inline void poly_dealloc(path_poly_t *P): + cdef path_term_t *T = P.lead + while T!=NULL: + T = term_free(T) + +# Deallocate all terms of the polynomial, and free the chunk of memory +# used by the polynomial. +cdef inline void poly_free(path_poly_t *P): + poly_dealloc(P) + sage_free(P) + +# Fill "out" with a copy of the terms of P. Note that previous contents +# of "out" will NOT be freed---this function should thus only be called +# when "out" is empty. +cdef inline bint poly_icopy(path_poly_t *out, path_poly_t *P) except -1: + cdef path_term_t *T = P.lead + out.nterms = P.nterms + out.lead = term_copy_recursive(T) + return True + +# Fill "out" with a copy of the terms of -P. Note that previous contents +# of "out" will NOT be freed---this function should thus only be called +# when "out" is empty. +cdef inline bint poly_icopy_neg(path_poly_t *out, path_poly_t *P) except -1: + cdef path_term_t *T = P.lead + out.nterms = P.nterms + out.lead = term_neg_recursive(T) + return True + +# Fill "out" with a copy of the terms of coef*P. Note that previous contents +# of "out" will NOT be freed---this function should thus only be called +# when "out" is empty. +cdef bint poly_icopy_scale(path_poly_t *out, path_poly_t *P, object coef) except -1: + cdef path_term_t *T = P.lead + cdef path_term_t *res = term_scale(T, coef) + out.nterms = 0 + out.lead = NULL + while res.coef == NULL: + sig_check() + sage_free(res) + T = T.nxt + if T == NULL: + return True + res = term_scale(T, coef) + out.lead = res + out.nterms += 1 + T = T.nxt + while T != NULL: + sig_check() + res.nxt = term_scale(T, coef) + if res.nxt.coef == NULL: + sage_free(res.nxt) + else: + res = res.nxt + out.nterms += 1 + T = T.nxt + if res != NULL: + res.nxt = NULL + return True + +# Linearisation of a path polynomials +cdef list poly_pickle(path_poly_t *P): + cdef list L = [] + cdef path_term_t *T = P.lead + while T != NULL: + L.append(term_pickle(T)) + T = T.nxt + return L + +# De-linearisation +cdef bint poly_inplace_unpickle(path_poly_t *P, list data) except -1: + cdef tuple term_data + cdef object coef + cdef path_term_t *T + if not data: + P.nterms = 0 + P.lead = NULL + return True + P.nterms = len(data) + coef, term_data = data.pop(0) + P.lead = term_unpickle(coef, term_data) + T = P.lead + for coef, term_data in data: + T.nxt = term_unpickle(coef, term_data) + T = T.nxt + T.nxt = NULL + return True + +############################################ +## +## Polynomial arithmetics + +# Comparison of P1 and P2, using the given monomial ordering cmp_terms. +# Return -1, 0, 1, if P1P2, respectively. +cdef int poly_cmp(path_poly_t *P1, path_poly_t *P2, path_order_t cmp_terms) except -2: + cdef path_term_t *T1 = P1.lead + cdef path_term_t *T2 = P2.lead + cdef int c + while T1 != NULL and T2 != NULL: + sig_check() + c = cmp_terms(T1.mon, T2.mon) + if c != 0: + return c + c = cmp(T1.coef, T2.coef) + if c != 0: + return c + T1 = T1.nxt + T2 = T2.nxt + if T1 == NULL: + if T2 == NULL: + return 0 + return -1 + return 1 + +# Hash of a polynomial. Probably not a very strong hash. +cdef inline long poly_hash(path_poly_t *P): + cdef path_term_t *T = P.lead + cdef long out = 0 + while T != NULL: + out = out<<7 | (out>>(sizeof(long)-7)) + out += term_hash(T) + T = T.nxt + return out + +# Change T1 inplace to T1+T2.coeff*T1. If the new coefficient is zero, +# then T1.coef becomes NULL +cdef inline void term_iadd(path_term_t *T1, path_term_t *T2): + cdef object coef = (T1.coef) + (T2.coef) + Py_XDECREF(T1.coef) + if coef: + Py_INCREF(coef) + T1.coef = coef + else: + T1.coef = NULL + +# Change P inplace to P+T. It is assumed that initially the terms of P are +# decreasingly sorted wrt. cmp_terms, and then it is guaranteed that they +# are decreasingly sorted wrt. cmp_terms after adding T. +# The adddition is "destructive" for T, which means that one MUST NOT +# call term_free(T) after the addition! +cdef bint poly_iadd_term_d(path_poly_t *P, path_term_t *T, path_order_t cmp_terms) except -1: + if P.lead == NULL: + P.nterms += 1 + T.nxt = NULL + P.lead = T + return True + cdef path_term_t *tmp = P.lead + cdef int c + cdef object coef + c = cmp_terms(tmp.mon, T.mon) + if c==-1: + # The poly's lead term is smaller than T. Hence, we need to prepend + # it. + P.nterms += 1 + T.nxt = tmp + P.lead = T + return True + elif c==0: + sig_on() + term_iadd(tmp, T) + term_free(T) + if tmp.coef==NULL: + P.nterms -= 1 + P.lead = term_free(tmp) + elif (tmp.coef)==0: + sig_off() + raise RuntimeError("This should never happen") + sig_off() + return True + while True: + # At this point, we have tmp>T. + # + # We need to append the term, or continue until we can + # insert/append + sig_check() + if tmp.nxt == NULL: + P.nterms += 1 + T.nxt = NULL + tmp.nxt = T + return True + c = cmp_terms(tmp.nxt.mon, T.mon) + if c==-1: + P.nterms += 1 + T.nxt = tmp.nxt + tmp.nxt = T + return True + elif c==0: + term_iadd(tmp.nxt, T) + term_free(T) + if tmp.nxt.coef==NULL: + P.nterms -= 1 + tmp.nxt = term_free(tmp.nxt) + elif (tmp.coef)==0: + raise RuntimeError("This should never happen") + return True + # otherwise, tmp is still larger than T. Hence, move to the next term + # of P. + tmp = tmp.nxt + +# Change P1 inplace to P1+P2. It is assumed that both P1's and P2's terms +# are decreasingly sorted wrt. cmp_terms, and after the operation P1's terms +# will still be decreasingly sorted. After the operation, one MUST NOT +# call poly_free(P2)! +cdef bint poly_iadd_d(path_poly_t *P1, path_poly_t *P2, path_order_t cmp_terms) except -1: + # Terms of P2 will be moved to P1, so that deallocation of P2 will not be + # needed. + if P1.lead == NULL: + P1.lead = P2.lead + P1.nterms = P2.nterms + P2.nterms = 0 + P2.lead = NULL + return 1 + if P2.lead == NULL: + return 1 + cdef path_term_t *T1 = P1.lead + cdef path_term_t *T2 = P2.lead + cdef path_term_t *prev = NULL + cdef int c + cdef object new_coef + while True: + # Is one of the summands consumed already? Then we can use an easier + # method. + sig_check() + if T1 == NULL: + if prev==NULL: + P1.lead = T2 + else: + prev.nxt = T2 + P1.nterms += P2.nterms + P2.nterms = 0 + P2.lead = NULL + return 1 + elif T2 == NULL: + if P2.nterms != 0: + print "term counting of second summand was wrong!",P2.nterms + P2.lead = NULL + return 1 + c = cmp_terms(T1.mon, T2.mon) + if c == 0: + # T1==T2 --- We need to add + new_coef = (T1.coef)+(T2.coef) + if new_coef: + Py_INCREF(new_coef) + Py_XDECREF(T1.coef) + T1.coef = new_coef + prev = T1 + T1 = T1.nxt + else: + T1 = term_free(T1) + if prev==NULL: + P1.lead = T1 + else: + prev.nxt = T1 + P1.nterms -= 1 + P2.nterms -= 1 + T2 = term_free(T2) + elif c == 1: + # We move the prev/T1 through P1, until prev>T2>=T1. But now, + # T1>T2. So, we move prev/T1 further down. + prev = T1 + T1 = T1.nxt + else: + # prev > T2 > T1. Hence, we insert T2, without copying + if prev==NULL: + P1.lead = T2 + T2 = T2.nxt + prev = P1.lead + else: + prev.nxt = T2 + T2 = T2.nxt + prev = prev.nxt + prev.nxt = T1 + P1.nterms += 1 + P2.nterms -= 1 + +# Return P1+P2 (a new polynomial, and after the operation it is still safe +# to call poly_free(P2)). Both P1's and P2's terms are supposed to be +# decreasingly sorted wrt. cmp_terms, and so will be the terms of P1+P2. +cdef path_poly_t *poly_add(path_poly_t *P1, path_poly_t *P2, path_order_t cmp_terms) except NULL: + cdef path_poly_t *out = poly_create() + cdef path_term_t *T1 = P1.lead + cdef path_term_t *T2 = P2.lead + cdef path_term_t *T = NULL + cdef path_term_t *res + cdef size_t count1, count2 # How many terms of P1/P2 have been considered? + count1 = 0 + count2 = 0 + cdef object coef + cdef int c + while True: + sig_check() + if T1 == NULL: + out.nterms += (P2.nterms-count2) + if T == NULL: + if T2 == NULL: + out.lead = NULL + else: + out.lead = term_copy_recursive(T2) + else: + if T2 == NULL: + T.nxt = NULL + else: + T.nxt = term_copy_recursive(T2) + return out + if T2 == NULL: + out.nterms += (P1.nterms-count1) + if T == NULL: + out.lead = term_copy_recursive(T1) + else: + T.nxt = term_copy_recursive(T1) + return out + + c = cmp_terms(T1.mon,T2.mon) + if c == 1: + if T == NULL: + out.lead = term_copy(T1) + T = out.lead + else: + T.nxt = term_copy(T1) + T = T.nxt + T1 = T1.nxt + count1 += 1 + out.nterms += 1 + elif c == -1: + if T == NULL: + out.lead = term_copy(T2) + T = out.lead + else: + T.nxt = term_copy(T2) + T = T.nxt + T2 = T2.nxt + count2 += 1 + out.nterms += 1 + else: + coef = (T1.coef)+(T2.coef) + if coef: + out.nterms += 1 + if T == NULL: + out.lead = term_create(coef, T1.mon.path, T1.mon.pos, T1.mon.l_len, T1.mon.s_len) + T = out.lead + else: + T.nxt = term_create(coef, T1.mon.path, T1.mon.pos, T1.mon.l_len, T1.mon.s_len) + T = T.nxt + count1 += 1 + count2 += 1 + T1 = T1.nxt + T2 = T2.nxt + +# Return P1-P2 (a new polynomial, and after the operation it is still safe +# to call poly_free(P2)). Both P1's and P2's terms are supposed to be +# decreasingly sorted wrt. cmp_terms, and so will be the terms of P1-P2. +cdef path_poly_t *poly_sub(path_poly_t *P1, path_poly_t *P2, path_order_t cmp_terms) except NULL: + cdef path_poly_t *out = poly_create() + cdef path_term_t *T1 = P1.lead + cdef path_term_t *T2 = P2.lead + cdef path_term_t *T = NULL + cdef path_term_t *res + cdef size_t count1, count2 # How many terms of P1/P2 have been considered? + count1 = 0 + count2 = 0 + cdef object coef + cdef int c + while True: + sig_check() + if T1 == NULL: + out.nterms += (P2.nterms-count2) + if T == NULL: + if T2 == NULL: + out.lead = NULL + else: + out.lead = term_neg_recursive(T2) + else: + if T2 == NULL: + T.nxt = NULL + else: + T.nxt = term_neg_recursive(T2) + return out + if T2 == NULL: + out.nterms += (P1.nterms-count1) + if T == NULL: + out.lead = term_copy_recursive(T1) + else: + T.nxt = term_copy_recursive(T1) + return out + + c = cmp_terms(T1.mon,T2.mon) + if c == 1: + if T == NULL: + out.lead = term_copy(T1) + T = out.lead + else: + T.nxt = term_copy(T1) + T = T.nxt + T1 = T1.nxt + count1 += 1 + out.nterms += 1 + elif c == -1: + if T == NULL: + out.lead = term_neg(T2) + T = out.lead + else: + T.nxt = term_neg(T2) + T = T.nxt + T2 = T2.nxt + count2 += 1 + out.nterms += 1 + else: + coef = (T1.coef)-(T2.coef) + if coef: + out.nterms += 1 + if T == NULL: + out.lead = term_create(coef, T1.mon.path, T1.mon.pos, T1.mon.l_len, T1.mon.s_len) + T = out.lead + else: + T.nxt = term_create(coef, T1.mon.path, T1.mon.pos, T1.mon.l_len, T1.mon.s_len) + T = T.nxt + count1 += 1 + count2 += 1 + T1 = T1.nxt + T2 = T2.nxt + +## +## In-place addition of a multiple of a polynomial +# Replace P1 by P1+coef*P2*R. Return a pointer to the first term of P1 +# that may be involved in a change when calling the function again with +# P1, P2 and a cofactor that is smaller than R wrt. cmp_terms. +# The return value should then be provided as argument "P1start" of the +# next function call. +# +# We return P1start if P2.lead is NULL. Otherwise, if P1.lead becomes NULL +# during addition, then we return P2.lead. +# +# Let m be a monomial of P2. If it is of module-type (i.e., m.pos!=-1), +# then it is clear what m2=m*R is (m2.l_len and m2.s_len are obtained from +# m.l_len and m.s_len). Otherwise, however, it could be that we +# want m2=m*R to denote a module-type monomial. In that case, "m2.l_len" +# and "m2.s_len" are given by the respective arguments. + +cdef path_term_t *poly_iadd_lmul(path_poly_t *P1, object coef, path_poly_t *P2, biseq_t R, path_order_t cmp_terms, long pos, mp_size_t l_len, mp_size_t s_len, path_term_t *P1start) except NULL: + if not coef or P2.lead==NULL: + return P1start + cdef path_mon_t new_mon + cdef object new_coef + cdef path_term_t *prev = NULL + cdef path_term_t *T1 + if P1start == NULL: + T1 = P1.lead + else: + T1 = P1start + cdef path_term_t *T2 = P2.lead + cdef int c + cdef path_term_t *out = P1start + while T2!=NULL: + sig_check() + new_coef = coef*(T2.coef) + if not new_coef: + T2 = T2.nxt + continue + + if T2.mon.pos!=-1: + mon_mul_path(new_mon, T2.mon, R) + new_mon.pos = T2.mon.pos + new_mon.l_len = T2.mon.l_len + new_mon.s_len = T2.mon.s_len + else: + mon_mul_path(new_mon, T2.mon, R) + new_mon.pos = pos + new_mon.l_len = l_len + new_mon.s_len = s_len + # Now new_term is T2*R + # We go down in P1 until we may append, insert or add + while T1!=NULL: + sig_check() + c = cmp_terms(T1.mon, new_mon) + if c!=1: + break + prev = T1 + T1 = prev.nxt + if T1==NULL: + # We need to append to P1 + if prev==NULL: + P1.lead = term_create_blank(new_coef) + P1.lead.mon[0] = new_mon[0] + prev = P1.lead + else: + prev.nxt = term_create_blank(new_coef) + prev.nxt.mon[0] = new_mon[0] + prev = prev.nxt + if T2 == P2.lead: + out = prev + prev.nxt = NULL + P1.nterms += 1 + elif c==-1: + # We need to insert between prev and T1 + if prev==NULL: + P1.lead = term_create_blank(new_coef) + P1.lead.mon[0] = new_mon[0] + prev = P1.lead + else: + prev.nxt = term_create_blank(new_coef) + prev.nxt.mon[0] = new_mon[0] + prev = prev.nxt + if T2 == P2.lead: + out = prev + prev.nxt = T1 + P1.nterms += 1 + else: + # we add the coefficients and see what happens + new_coef+=(T1.coef) + mon_free(new_mon) + if new_coef: + Py_INCREF(new_coef) + Py_XDECREF(T1.coef) + T1.coef = new_coef + if T2 == P2.lead: + out = T1 + prev = T1 + T1 = T1.nxt + else: + P1.nterms -= 1 + T1 = term_free(T1) + if prev==NULL: + P1.lead = T1 + else: + prev.nxt = T1 + if T2 == P2.lead: + out = prev + T2 = T2.nxt + if out == NULL: + return P2.lead + return out + +######################################## +## +## Basics for homogeneous polynomials + +# Create an empty polynomial whose to-be-inserted terms +# have start- and end-points of the given integer labels +# start and end. +cdef path_homog_poly_t *homog_poly_create(int start, int end) except NULL: + cdef path_homog_poly_t *out = check_malloc(sizeof(path_homog_poly_t)) + out.poly = poly_create() + out.start = start + out.end = end + out.nxt = NULL + return out + +# Create a new homogeneous polynomial from a given polynomial P. +# It is assumed that all terms of P have start- and end-points +# with integer labels start and end. This assumption is NOT checked! +# P is inserted, not copied. +cdef path_homog_poly_t *homog_poly_init_poly(int start, int end, path_poly_t *P) except NULL: + cdef path_homog_poly_t *out = check_malloc(sizeof(path_homog_poly_t)) + out.poly = P + out.start = start + out.end = end + out.nxt = NULL + return out + +# L provides a list of pairs (P,coef), where P is a path +# and coef the coefficient of the corresponding term. +# With this function, one can create elements of a path algebra (pos==-1), +# or elements of free modules over a path algebra in summand pos. +# In either case, the length of left cofactors of each monomial will +# be zero, and also the length of Schreyer monomials will be zero. +cdef path_homog_poly_t *homog_poly_init_list(int start, int end, list L, path_order_t cmp_terms, long pos) except NULL: + cdef path_homog_poly_t * out = homog_poly_create(start, end) + cdef QuiverPath P + for P,coef in L: + poly_iadd_term_d(out.poly, term_create(coef, P._path, pos, 0, 0), cmp_terms) + return out + +cdef void homog_poly_free(path_homog_poly_t *P): + cdef path_homog_poly_t *nxt + while P!=NULL: + nxt = P.nxt + poly_free(P.poly) + sage_free(P) + P = nxt + +# Return a copy of H +cdef path_homog_poly_t *homog_poly_copy(path_homog_poly_t *H) except NULL: + cdef path_homog_poly_t *out + cdef path_homog_poly_t *tmp + if H == NULL: + raise ValueError("The polynomial to be copied is the NULL pointer") + out = homog_poly_create(H.start, H.end) + poly_icopy(out.poly, H.poly) + tmp = out + H = H.nxt + while H != NULL: + sig_check() + tmp.nxt = homog_poly_create(H.start, H.end) + tmp = tmp.nxt + poly_icopy(tmp.poly, H.poly) + H = H.nxt + return out + +# Linearisation +cdef list homog_poly_pickle(path_homog_poly_t *H): + cdef list L = [] + while H != NULL: + L.append((H.start, H.end, poly_pickle(H.poly))) + H = H.nxt + return L + +# De-linearisation +cdef path_homog_poly_t *homog_poly_unpickle(list data) except NULL: + #ASSUMPTION: data is not empty + cdef int start, end + cdef list poly_data + cdef path_homog_poly_t *out + start, end, poly_data = data.pop(0) + out = homog_poly_create(start, end) + poly_inplace_unpickle(out.poly, poly_data) + cdef path_homog_poly_t *tmp = out + for start, end, poly_data in data: + sig_check() + tmp.nxt = homog_poly_create(start, end) + tmp = tmp.nxt + poly_inplace_unpickle(tmp.poly, poly_data) + return out + +# Return -H +cdef path_homog_poly_t *homog_poly_neg(path_homog_poly_t *H) except NULL: + cdef path_homog_poly_t *out + cdef path_homog_poly_t *tmp + if H == NULL: + raise ValueError("The polynomial to be copied is the NULL pointer") + out = homog_poly_create(H.start, H.end) + poly_icopy_neg(out.poly, H.poly) + tmp = out + H = H.nxt + while H != NULL: + sig_check() + tmp.nxt = homog_poly_create(H.start, H.end) + tmp = tmp.nxt + poly_icopy_neg(tmp.poly, H.poly) + H = H.nxt + return out + +# Return coef*H +cdef path_homog_poly_t *homog_poly_scale(path_homog_poly_t *H, object coef) except NULL: + # The first component may be zero, all other zero components are removed. + cdef path_homog_poly_t *out + cdef path_homog_poly_t *tmp + if H == NULL: + raise ValueError("The polynomial to be copied is the NULL pointer") + out = homog_poly_create(H.start, H.end) + poly_icopy_scale(out.poly, H.poly, coef) + tmp = out + H = H.nxt + while H != NULL: + sig_check() + tmp.nxt = homog_poly_create(H.start, H.end) + poly_icopy_scale(tmp.nxt.poly, H.poly, coef) + if tmp.nxt.poly.nterms == 0: + homog_poly_free(tmp.nxt) + tmp.nxt = NULL + else: + tmp = tmp.nxt + H = H.nxt + return out + +cdef path_homog_poly_t *homog_poly_get_predecessor_of_component(path_homog_poly_t *H, int s, int e): + # Search through H.nxt.nxt... and return the pointer C to a component of H + # such that either C.nxt.start==s and C.nxt.end==e, or the component for + # (s,e) should be inserted between C and C.nxt. Return NULL if H==NULL or + # (s,e) should be inserted in front of H. + if H == NULL: + return NULL + if H.start > s: + return NULL + elif H.start == s and H.end >= e: + return NULL + while True: + sig_check() + if H.nxt == NULL: + return H + if H.nxt.start == s: + if H.nxt.end >= e: + return H + elif H.nxt.start > s: + return H + H = H.nxt diff --git a/src/sage/quivers/algebra_elements.pyx b/src/sage/quivers/algebra_elements.pyx new file mode 100644 index 00000000000..f7ca263d3ae --- /dev/null +++ b/src/sage/quivers/algebra_elements.pyx @@ -0,0 +1,1434 @@ +""" +Path algebra elements + +AUTHORS: + +- Simon King (2015-08) + +""" + +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# 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/ +#***************************************************************************** + +include "algebra_elements.pxi" +from sage.misc.cachefunc import cached_method +from sage.misc.misc import repr_lincomb + +cdef class PathAlgebraElement(RingElement): + """ + Elements of a :class:`~sage.quivers.algebra.PathAlgebra`. + + NOTE: + + Upon creation of a path algebra, one can choose among several monomial + orders, which are all positive or negative degree orders. Monomial orders + that are not degree orders are not supported. + + EXAMPLES: + + After creating a path algebra and getting hold of its generators, one can + create elements just as usual:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = a+2*b+3*c+5*e_0+3*e_2 + sage: x + 5*e_0 + a + 2*b + 3*c + 3*e_2 + + The path algebra decomposes as a direct sum according to start- and endpoints:: + + sage: x.sort_by_vertices() + [(5*e_0, 0, 0), + (a, 0, 1), + (2*b, 0, 2), + (3*c, 1, 0), + (3*e_2, 2, 2)] + sage: (x^3+x^2).sort_by_vertices() + [(150*e_0 + 33*a*c, 0, 0), + (30*a + 3*a*c*a, 0, 1), + (114*b + 6*a*c*b, 0, 2), + (90*c + 9*c*a*c, 1, 0), + (18*c*a, 1, 1), + (54*c*b, 1, 2), + (36*e_2, 2, 2)] + + For a consistency test, we create a path algebra that is isomorphic to a + free associative algebra, and compare arithmetic with two other + implementations of free algebras (note that the letterplace implementation + only allows weighted homogeneous elements):: + + sage: F. = FreeAlgebra(GF(25,'t')) + sage: pF = x+y*z*x+2*y-z+1 + sage: pF2 = x^4+x*y*x*z+2*z^2*x*y + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: pP = sage_eval('x+y*z*x+2*y-z+1', P.gens_dict()) + sage: pP^5+3*pP^3 == sage_eval(repr(pF^5+3*pF^3), P.gens_dict()) + True + sage: L. = FreeAlgebra(GF(25,'t'), implementation='letterplace') + sage: pL2 = x^4+x*y*x*z+2*z^2*x*y + sage: pP2 = sage_eval('x^4+x*y*x*z+2*z^2*x*y', P.gens_dict()) + sage: pP2^7 == sage_eval(repr(pF2^7), P.gens_dict()) + True + sage: pP2^7 == sage_eval(repr(pL2^7), P.gens_dict()) + True + + When the Cython implementation of path algebra elements was + introduced, it was faster than both the default implementation and + the letterplace implementation of free algebras. The following + timings where obtained with a 32-bit operating system; using 64-bit + on the same machine, the letterplace implementation has not become + faster, but the timing for path algebra elements has improved by + about 20%:: + + sage: timeit('pF^5+3*pF^3') # not tested + 1 loops, best of 3: 338 ms per loop + sage: timeit('pP^5+3*pP^3') # not tested + 100 loops, best of 3: 2.55 ms per loop + sage: timeit('pF2^7') # not tested + 10000 loops, best of 3: 513 ms per loop + sage: timeit('pL2^7') # not tested + 125 loops, best of 3: 1.99 ms per loop + sage: timeit('pP2^7') # not tested + 10000 loops, best of 3: 1.54 ms per loop + + So, if one is merely interested in basic arithmetic operations for + free associative algebras, it could make sense to model the free + associative algebra as a path algebra. However, standard basis + computations are not available for path algebras, yet. Hence, to + implement computations in graded quotients of free algebras, the + letterplace implementation currently is the only option. + + """ + def __cinit__(self): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = a+2*b+3*c+5*e_0+3*e_2 # indirect doctest + sage: x + 5*e_0 + a + 2*b + 3*c + 3*e_2 + + """ + self.data = NULL + + def __dealloc__(self): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = a+2*b+3*c+5*e_0+3*e_2 + sage: del x # indirect doctest + + """ + homog_poly_free(self.data) + + def __init__(self, S, data): + """ + Do not call directly. + + INPUT: + + - ``S``, a path algebra. + + - ``data``, a dictionary. Most of its keys are + :class:`~sage.quivers.paths.QuiverPath`, the value giving its + coefficient. + + NOTE: + + Monomial orders that are not degree orders are not supported. + + EXAMPLES:: + + sage: P1 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: P1.inject_variables() # indirect doctest + Defining e_1, x, y, z + sage: (x+2*z+1)^2 + e_1 + 4*z + 2*x + 4*z*z + 2*x*z + 2*z*x + x*x + sage: P2 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P2.inject_variables() + Defining e_1, x, y, z + sage: (x+2*z+1)^2 + 4*z*z + 2*x*z + 2*z*x + x*x + 4*z + 2*x + e_1 + sage: P3 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="negdeglex") + sage: P3.inject_variables() + Defining e_1, x, y, z + sage: (x+2*z+1)^2 + e_1 + 4*z + 2*x + 4*z*z + 2*z*x + 2*x*z + x*x + sage: P4 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="deglex") + sage: P4.inject_variables() + Defining e_1, x, y, z + sage: (x+2*z+1)^2 + 4*z*z + 2*z*x + 2*x*z + x*x + 4*z + 2*x + e_1 + + + """ + self._hash = -1 + order = S.order_string() + if order=="negdegrevlex": + self.cmp_terms = negdegrevlex + elif order=="degrevlex": + self.cmp_terms = degrevlex + elif order=="negdeglex": + self.cmp_terms = negdeglex + elif order=="deglex": + self.cmp_terms = deglex + else: + raise ValueError("Unknown term order '{}'".format(order)) + cdef QuiverPath tmp = None + RingElement.__init__(self, S) + cdef dict homog = {} + cdef list L + for tmp, c in data.iteritems(): + sig_check() + homog.setdefault((tmp.initial_vertex(),tmp.terminal_vertex()),[]).append((tmp,c)) + cdef path_homog_poly_t *HP + for (s,e),L in sorted(homog.iteritems(), reverse=True): + sig_check() + HP = homog_poly_init_list(s,e,L,self.cmp_terms, -1) + HP.nxt = self.data + self.data = HP + + def __reduce__(self): + """ + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: p = sage_eval('(x+2*z+1)^3', P.gens_dict()) + sage: loads(dumps(p)) == p # indirect doctest + True + + """ + return path_algebra_element_unpickle, (self._parent, homog_poly_pickle(self.data)) + + cdef list _sorted_items_for_printing(self): + """ + Return list of pairs ``(M,c)``, where ``c`` is a coefficient and ``M`` + will be passed to ``self.parent()._repr_monomial`` resp. to + ``self.parent()._latex_monomial``, providing the indices of the + algebra generators occurring in the monomial. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: X # indirect doctest + 5*e_0 + a + 2*b + 3*c + 3*e_2 + sage: latex(X) # indirect doctest + 5e_0 + a + 2b + 3c + 3e_2 + + """ + cdef path_homog_poly_t *H = self.data + cdef list L, L_total + cdef size_t i + cdef path_term_t * T + L_total = [] + cdef list vertices = self._parent.quiver().vertices() + cdef mp_size_t offset = len(vertices) + while H != NULL: + L = [] # data for a single component (given by start- and endpoints) + T = H.poly.lead + while T!=NULL: + sig_check() + if T.mon.path.length: + L.append(([offset+biseq_getitem(T.mon.path,i) for i in range(T.mon.path.length)], + (T.coef))) + else: + L.append(([vertices.index(H.start)], (T.coef))) + T = T.nxt + if len(L) != H.poly.nterms: + print "Term count of polynomial is wrong, got",len(L), "expected", H.poly.nterms + L_total.extend(L) + H = H.nxt + return L_total + + def _repr_(self): + """ + String representation. + + NOTE: + + The terms are first sorted by initial and terminal vertices, and only + then by the given monomial order. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: X # indirect doctest + 5*e_0 + a + 2*b + 3*c + 3*e_2 + + """ + return repr_lincomb(self._sorted_items_for_printing(), strip_one=True, + scalar_mult=self.parent()._print_options['scalar_mult'], + repr_monomial = self._parent._repr_monomial + ) + + def _latex_(self): + """ + Latex string representation. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: latex(X) # indirect doctest + 5e_0 + a + 2b + 3c + 3e_2 + sage: latex(X*X) + 10e_0 + 3a\cdot c + 5a + b + 3c\cdot a + 6c\cdot b + 9e_2 + """ + return repr_lincomb(self._sorted_items_for_printing(), + scalar_mult = self.parent()._print_options['scalar_mult'], + latex_scalar_mult = self.parent()._print_options['latex_scalar_mult'], + repr_monomial = self._parent._latex_monomial, + is_latex=True, strip_one = True) + + # Basic properties + + def __nonzero__(self): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: bool(a+b+c+d) # indirect doctest + True + sage: bool(((a+b+c+d)-(a+b))-(c+d)) + False + """ + return self.data != NULL + + def __len__(self): + """ + Return the number of terms appearing in this element. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: X = a+2*b+3*c+5*e_0+3*e_2 + sage: len(X) + 5 + sage: len(X^5) + 17 + + """ + cdef size_t l = 0 + cdef path_homog_poly_t *H = self.data + while H != NULL: + sig_check() + l += H.poly.nterms + H = H.nxt + return l + + cpdef ssize_t degree(self) except -2: + """ + Return the degree, provided the element is homogeneous. + + An error is raised if the element is not homogeneous. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: P.inject_variables() + Defining e_1, x, y, z + sage: q = (x+y+2*z)^3 + sage: q.degree() + 3 + sage: p = (x+2*z+1)^3 + sage: p.degree() + Traceback (most recent call last): + ... + ValueError: Element is not homogeneous. + + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef mp_size_t deg = 0 + cdef bint zero = True + while H!=NULL: + sig_check() + T = H.poly.lead + while T!=NULL: + if zero: + deg = term_total_degree(T) + elif deg != term_total_degree(T): + raise ValueError("Element is not homogeneous.") + zero = False + T = T.nxt + H = H.nxt + if zero: + return -1 + return deg + + def is_homogeneous(self): + """ + Tells whether this element is homogeneous. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: P.inject_variables() + Defining e_1, x, y, z + sage: q = (x+y+2*z)^3 + sage: q.is_homogeneous() + True + sage: p = (x+2*z+1)^3 + sage: p.is_homogeneous() + False + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef mp_size_t deg = 0 + cdef bint zero = True + while H!=NULL: + T = H.poly.lead + while T!=NULL: + sig_check() + if zero: + deg = term_total_degree(T) + elif deg != term_total_degree(T): + return False + zero = False + T = T.nxt + H = H.nxt + return True + + cpdef dict monomial_coefficients(self): + """ + Return the dictionary keyed by the monomials appearing + in this element, the values being the coefficients. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: list(sorted(p.monomial_coefficients().items())) + [(x*x*x, 1), + (z*x*x, 2), + (x*z*x, 2), + (z*z*x, 4), + (x*x*z, 2), + (z*x*z, 4), + (x*z*z, 4), + (z*z*z, 3), + (x*x, 3), + (z*x, 1), + (x*z, 1), + (z*z, 2), + (x, 3), + (z, 1), + (e_1, 1)] + + Note that the dictionary can be fed to the algebra, to reconstruct the + element:: + + sage: P(p.monomial_coefficients()) == p + True + + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef QuiverPath sample = self._parent.semigroup().gen(0) + cdef QuiverPath tmp + cdef dict D = {} + while H!=NULL: + T = H.poly.lead + while T!=NULL: + tmp = sample._new_(H.start, H.end) + biseq_init_copy(tmp._path, T.mon.path) + D[tmp] = T.coef + T = T.nxt + H = H.nxt + return D + + cpdef list coefficients(self): + """ + Returns the list of coefficients. + + .. NOTE:: + + The order in which the coefficients are returned corresponds to the + order in which the terms are printed. That is *not* the same as the + order given by the monomial order, since the terms are first ordered + according to initial and terminal vertices, before applying the + monomial order of the path algebra. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: p.coefficients() + [3, 4, 4, 2, 4, 2, 2, 1, 2, 1, 1, 3, 1, 3, 1] + + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef list L = [] + while H!=NULL: + T = H.poly.lead + while T!=NULL: + L.append(T.coef) + T = T.nxt + H = H.nxt + return L + + cpdef list monomials(self): + """ + Returns the list of monomials appearing in this element. + + .. NOTE:: + + The order in which the monomials are returned corresponds to the + order in which the element's terms are printed. That is *not* the + same as the order given by the monomial order, since the terms are + first ordered according to initial and terminal vertices, before + applying the monomial order of the path algebra. + + The monomials are not elements of the underlying partial + semigroup, but of the algebra. + + .. SEEALSO:: :meth:`support` + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: p.monomials() + [z*z*z, + x*z*z, + z*x*z, + x*x*z, + z*z*x, + x*z*x, + z*x*x, + x*x*x, + z*z, + x*z, + z*x, + x*x, + z, + x, + e_1] + sage: p.monomials()[1].parent() is P + True + + """ + cdef path_homog_poly_t *H = self.data + cdef path_homog_poly_t *out + cdef path_term_t *T + cdef object one = self.base_ring().one() + cdef list L = [] + while H!=NULL: + T = H.poly.lead + while T!=NULL: + out = homog_poly_create(H.start, H.end) + out.poly.lead = term_create_blank(one) + mon_copy(out.poly.lead.mon, T.mon) + out.poly.lead.nxt = NULL + out.poly.nterms = 1 + L.append(self._new_(out)) + T = T.nxt + H = H.nxt + return L + + cpdef list terms(self): + """ + Returns the list of terms. + + .. NOTE:: + + The order in which the terms are returned corresponds to the order + in which they are printed. That is *not* the same as the + order given by the monomial order, since the terms are first + ordered according to initial and terminal vertices, before + applying the monomial order of the path algebra. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: p.terms() + [3*z*z*z, + 4*x*z*z, + 4*z*x*z, + 2*x*x*z, + 4*z*z*x, + 2*x*z*x, + 2*z*x*x, + x*x*x, + 2*z*z, + x*z, + z*x, + 3*x*x, + z, + 3*x, + e_1] + + """ + cdef path_homog_poly_t *H = self.data + cdef path_homog_poly_t *out + cdef path_term_t *T + cdef object one = self.base_ring().one() + cdef list L = [] + while H!=NULL: + T = H.poly.lead + while T!=NULL: + out = homog_poly_create(H.start, H.end) + out.poly.lead = term_copy(T) + out.poly.lead.nxt = NULL + out.poly.nterms = 1 + L.append(self._new_(out)) + T = T.nxt + H = H.nxt + return L + + cpdef list support(self): + """ + Returns the list of monomials, as elements of the underlying partial semigroup. + + .. NOTE:: + + The order in which the monomials are returned corresponds to the + order in which the element's terms are printed. That is *not* the + same as the order given by the monomial order, since the terms are + first ordered according to initial and terminal vertices, before + applying the monomial order of the path algebra. + + .. SEEALSO:: :meth:`monomials` + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: p.support() + [z*z*z, + x*z*z, + z*x*z, + x*x*z, + z*z*x, + x*z*x, + z*x*x, + x*x*x, + z*z, + x*z, + z*x, + x*x, + z, + x, + e_1] + sage: p.support()[1].parent() is P.semigroup() + True + + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef QuiverPath sample = self._parent.semigroup().gen(0) + cdef QuiverPath tmp + cdef list L = [] + while H!=NULL: + T = H.poly.lead + while T!=NULL: + tmp = sample._new_(H.start, H.end) + biseq_init_copy(tmp._path, T.mon.path) + L.append(tmp) + T = T.nxt + H = H.nxt + return L + + def support_of_term(self): + """ + If ``self`` consists of a single term, return the corresponding + element of the underlying path semigroup. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = 4*a*d*c*b*e + sage: x.support_of_term() + a*d*c*b*e + sage: x.support_of_term().parent() is A.semigroup() + True + sage: (x + f).support_of_term() + Traceback (most recent call last): + ... + ValueError: 4*a*d*c*b*e + f is not a single term + + """ + cdef QuiverPath sample = self._parent.semigroup().gen(0) + cdef QuiverPath tmp + if self.data != NULL and self.data.nxt == NULL: + if self.data.poly.lead != NULL: + tmp = sample._new_(self.data.start, self.data.end) + biseq_init_copy(tmp._path, self.data.poly.lead.mon.path) + return tmp + raise ValueError("{} is not a single term".format(self)) + + cpdef object coefficient(self, QuiverPath P): + """ + Return the coefficient of a monomial. + + INPUT: + + An element of the underlying partial semigroup. + + OUTPUT: + + The coefficient of the given semigroup element in ``self``, or zero if + it does not appear. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: p.coefficient(sage_eval('x*x*z', P.semigroup().gens_dict())) + 2 + sage: p.coefficient(sage_eval('z*x*x*x', P.semigroup().gens_dict())) + 0 + + """ + if self.data == NULL: + return self.base_ring().zero() + H = homog_poly_get_predecessor_of_component(self.data, P._start, P._end) + if H == NULL: + if self.data.start != P._start or self.data.end != P._end: + return self.base_ring().zero() + H = self.data + else: + H = H.nxt + if H == NULL: + return self.base_ring().zero() + # Now, H points to the component that belongs to K + cdef path_mon_t pM + mon_create_keep(pM, P._path, -1, 0, 0) + T = H.poly.lead + while T != NULL: + if self.cmp_terms(T.mon, pM) == 0: + return T.coef + T = T.nxt + return self.base_ring().zero() + + def __iter__(self): + """ + Iterate over the pairs (monomial, coefficient) appearing in ``self``. + + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: p + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: list(p) # indirect doctest + [(z*z*z, 3), + (x*z*z, 4), + (z*x*z, 4), + (x*x*z, 2), + (z*z*x, 4), + (x*z*x, 2), + (z*x*x, 2), + (x*x*x, 1), + (z*z, 2), + (x*z, 1), + (z*x, 1), + (x*x, 3), + (z, 1), + (x, 3), + (e_1, 1)] + """ + cdef path_homog_poly_t *H = self.data + cdef path_term_t *T + cdef QuiverPath sample = self._parent.semigroup().gen(0) + cdef QuiverPath tmp + while H!=NULL: + T = H.poly.lead + while T!=NULL: + sig_check() + tmp = sample._new_(H.start, H.end) + biseq_init_copy(tmp._path, T.mon.path) + yield (tmp, T.coef) + T = T.nxt + H = H.nxt + + cdef PathAlgebraElement _new_(self, path_homog_poly_t *h): + """ + Create a new path algebra element from C interface data. + """ + cdef PathAlgebraElement out = type(self).__new__(type(self)) + out._parent = self._parent + out.cmp_terms = self.cmp_terms + out.data = h + out._hash = -1 + return out + + def __copy__(self): + """ + EXAMPLES:: + + sage: P = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P.inject_variables() + Defining e_1, x, y, z + sage: p = (x+2*z+1)^3 + sage: copy(p) is p + False + sage: copy(p) == p # indirect doctest + True + + """ + return self._new_(homog_poly_copy(self.data)) + + def __getitem__(self, k): + """ + Either return the coefficient in ``self`` of an element of the + underlying partial semigroup, or the sum of terms of ``self`` whose + monomials have a given initial and terminal vertex. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: X = (a+2*b+3*c+5*e_0+3*e_2)^3 + sage: X[A.semigroup()('c')] + 75 + sage: X.sort_by_vertices() + [(125*e_0 + 30*a*c, 0, 0), + (25*a + 3*a*c*a, 0, 1), + (98*b + 6*a*c*b, 0, 2), + (75*c + 9*c*a*c, 1, 0), + (15*c*a, 1, 1), + (48*c*b, 1, 2), + (27*e_2, 2, 2)] + sage: X.sort_by_vertices() + [(125*e_0 + 30*a*c, 0, 0), + (25*a + 3*a*c*a, 0, 1), + (98*b + 6*a*c*b, 0, 2), + (75*c + 9*c*a*c, 1, 0), + (15*c*a, 1, 1), + (48*c*b, 1, 2), + (27*e_2, 2, 2)] + sage: X[0,2] + 98*b + 6*a*c*b + + """ + cdef path_homog_poly_t *H + cdef path_term_t *T + cdef path_mon_t kM + cdef PathAlgebraElement out + cdef QuiverPath K + if isinstance(k, tuple): + H = homog_poly_get_predecessor_of_component(self.data,k[0],k[1]) + if H == NULL: + if self.data.start == k[0] and self.data.end == k[1]: + out = self._new_(homog_poly_create(self.data.start, self.data.end)) + out.data.nxt = NULL + poly_icopy(out.data.poly, self.data.poly) + else: + return self._new_(NULL) + else: + if H.nxt == NULL or H.nxt.start != k[0] or H.nxt.end != k[1]: + return self._new_(NULL) + out = self._new_(homog_poly_create(H.nxt.start, H.nxt.end)) + out.data.nxt = NULL + poly_icopy(out.data.poly, H.nxt.poly) + return out + elif isinstance(k, QuiverPath): + if self.data == NULL: + return self.base_ring().zero() + K = k + H = homog_poly_get_predecessor_of_component(self.data, K._start, K._end) + if H == NULL: + if self.data.start != K._start or self.data.end != K._end: + return self.base_ring().zero() + H = self.data + else: + H = H.nxt + if H == NULL: + return self.base_ring().zero() + # Now, H points to the component that belongs to K + mon_create_keep(kM, K._path, -1, 0, 0) + T = H.poly.lead + while T != NULL: + sig_check() + if self.cmp_terms(T.mon, kM) == 0: + return T.coef + T = T.nxt + return self.base_ring().zero() + + def sort_by_vertices(self): + """ + Return a list of triples ``(element, v1, v2)``, where ``element`` is + an element whose monomials all have initial vertex ``v1`` and terminal + vertex ``v2``, so that the sum of elements is ``self``. + + EXAMPLES:: + + sage: A1 = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: A1.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = (b*e*b*e+4*b+e_0)^2 + sage: y = (a*c*b+1)^3 + sage: x.sort_by_vertices() + [(e_0 + 2*b*e*b*e + b*e*b*e*b*e*b*e, 0, 0), (4*b + 4*b*e*b*e*b, 0, 2)] + sage: sum(c[0] for c in x.sort_by_vertices()) == x + True + sage: y.sort_by_vertices() + [(e_0, 0, 0), (3*a*c*b, 0, 2), (e_1, 1, 1), (e_2, 2, 2)] + sage: sum(c[0] for c in y.sort_by_vertices()) == y + True + + """ + cdef path_homog_poly_t * H = self.data + cdef PathAlgebraElement out + cdef list C = [] + while H != NULL: + out = self._new_(homog_poly_create(H.start, H.end)) + out.data.nxt = NULL + sig_check() + poly_icopy(out.data.poly, H.poly) + C.append((out, H.start, H.end)) + H = H.nxt + return C + + #### + ## Arithmetics + # Hash and Comparison + def __hash__(self): + """ + The hash is cached, to make it faster. + + EXAMPLES:: + + sage: P1 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(3,'t')) + sage: P2 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(3,'t'), order="deglex") + sage: P1.inject_variables() + Defining e_1, x, y, z + sage: p = x+y + sage: P2.inject_variables() + Defining e_1, x, y, z + sage: q = x+y + sage: D = dict([(p^i,i) for i in range(1,8)]) + sage: len(D) + 7 + sage: hash(q^5) == hash(p^5) # indirect doctest + True + sage: D[q^6] + 6 + + """ + if self._hash==-1: + self._hash = hash(frozenset(self.monomial_coefficients().items())) + return self._hash + + cpdef int _cmp_(left, Element right) except -2: + """ + Helper for comparison of path algebra elements. + + NOTE: + + First, the comparison is by initial vertices of monomials. Then, the + terminal vertices are compared. Last, the given monomial order is + applied for monomials that have the same initial and terminal + vertices. + + EXAMPLES:: + + sage: A1 = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: A1.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = (b*e*b*e+4*b+e_0)^2 + sage: y = (a*c*b+1)^3 + sage: x.sort_by_vertices() + [(e_0 + 2*b*e*b*e + b*e*b*e*b*e*b*e, 0, 0), (4*b + 4*b*e*b*e*b, 0, 2)] + sage: y.sort_by_vertices() + [(e_0, 0, 0), (3*a*c*b, 0, 2), (e_1, 1, 1), (e_2, 2, 2)] + + The two elements are distinguished by monomials with initial and + terminal vertex `0`. Hence, `x` should evaluate bigger than `y`:: + + sage: x > y # indirect doctest + True + + """ + cdef PathAlgebraElement other = right + cdef PathAlgebraElement self = left + cdef path_homog_poly_t *H1 = self.data + cdef path_homog_poly_t *H2 = other.data + cdef int c + while H1 != NULL and H2 != NULL: + c = cmp(H1.start, H2.start) + if c != 0: + return c + c = cmp(H1.end, H2.end) + if c != 0: + return c + c = poly_cmp(H1.poly, H2.poly, self.cmp_terms) + if c != 0: + return c + H1 = H1.nxt + H2 = H2.nxt + if H1 == NULL: + if H2 == NULL: + return 0 + return -1 + return 1 + + # negation + cpdef ModuleElement _neg_(self): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(GF(3)) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = b*e*b*e+4*b*e+e_0 + sage: -x # indirect doctest + 2*e_0 + 2*b*e + 2*b*e*b*e + """ + return self._new_(homog_poly_neg(self.data)) + + # addition + cpdef ModuleElement _add_(self, ModuleElement other): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(GF(3)) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = b*e*b*e+4*b*e+e_0 + sage: y = a*c+1 + sage: x+y # indirect doctest + 2*e_0 + b*e + a*c + b*e*b*e + e_1 + e_2 + + """ + cdef PathAlgebraElement right = other + cdef path_homog_poly_t *H1 = self.data + cdef path_homog_poly_t *H2 = right.data + cdef path_poly_t *P + cdef path_homog_poly_t *out = NULL + cdef path_homog_poly_t *tmp + while True: + sig_check() + if H1 == NULL: + if out == NULL: + if H2 == NULL: + return self._new_(NULL) + return self._new_(homog_poly_copy(H2)) + else: + if H2 != NULL: + # If out is not NULL then tmp isn't either + tmp.nxt = homog_poly_copy(H2) + return self._new_(out) + elif H2 == NULL: + if out == NULL: + if H1 == NULL: + return self._new_(NULL) + return self._new_(homog_poly_copy(H1)) + else: + if H1 != NULL: + # If out is not NULL then tmp isn't either + tmp.nxt = homog_poly_copy(H1) + return self._new_(out) + else: + if (H1.start > H2.start) or (H1.start == H2.start and H1.end > H2.end): + if out == NULL: + out = homog_poly_create(H2.start, H2.end) + poly_icopy(out.poly, H2.poly) + tmp = out + else: + tmp.nxt = homog_poly_create(H2.start, H2.end) + tmp = tmp.nxt + poly_icopy(tmp.poly, H2.poly) + H2 = H2.nxt + elif (H1.start < H2.start) or (H1.end < H2.end): + if out == NULL: + out = homog_poly_create(H1.start, H1.end) + poly_icopy(out.poly, H1.poly) + tmp = out + else: + tmp.nxt = homog_poly_create(H1.start, H1.end) + tmp = tmp.nxt + poly_icopy(tmp.poly, H1.poly) + H1 = H1.nxt + else: + # start- and endpoints match + P = poly_add(H1.poly, H2.poly, self.cmp_terms) + if P.lead != NULL: + if out == NULL: + out = homog_poly_init_poly(H1.start, H1.end, P) + tmp = out + else: + tmp.nxt = homog_poly_init_poly(H1.start, H1.end, P) + tmp = tmp.nxt + else: + poly_free(P) + H1 = H1.nxt + H2 = H2.nxt + + cpdef ModuleElement _sub_(self, ModuleElement other): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(GF(3)) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = b*e*b*e+4*b*e+1 + sage: y = a*c-1 # indirect doctest + sage: x-y # indirect doctest + 2*e_0 + b*e + 2*a*c + b*e*b*e + + """ + cdef PathAlgebraElement right = other + cdef path_homog_poly_t *H1 = self.data + cdef path_homog_poly_t *H2 = right.data + cdef path_poly_t *P + cdef path_homog_poly_t *out = NULL + cdef path_homog_poly_t *tmp + while True: + sig_check() + if H1 == NULL: + if out == NULL: + if H2 == NULL: + return self._new_(NULL) + sig_check() + return self._new_(homog_poly_copy(H2)) + else: + if H2 != NULL: + # If out is not NULL then tmp isn't either + tmp.nxt = homog_poly_copy(H2) + return self._new_(out) + elif H2 == NULL: + if out == NULL: + if H1 == NULL: + return self._new_(NULL) + return self._new_(homog_poly_copy(H1)) + else: + if H1 != NULL: + # If out is not NULL then tmp isn't either + tmp.nxt = homog_poly_copy(H1) + return self._new_(out) + else: + if (H1.start > H2.start) or (H1.start == H2.start and H1.end > H2.end): + if out == NULL: + sig_on() + out = homog_poly_create(H2.start, H2.end) + poly_icopy(out.poly, H2.poly) + sig_off() + tmp = out + else: + sig_on() + tmp.nxt = homog_poly_create(H2.start, H2.end) + tmp = tmp.nxt + poly_icopy(tmp.poly, H2.poly) + sig_off() + H2 = H2.nxt + elif (H1.start < H2.start) or (H1.end < H2.end): + if out == NULL: + sig_on() + out = homog_poly_create(H1.start, H1.end) + poly_icopy(out.poly, H1.poly) + sig_off() + tmp = out + else: + sig_on() + tmp.nxt = homog_poly_create(H1.start, H1.end) + tmp = tmp.nxt + poly_icopy(tmp.poly, H1.poly) + sig_off() + H1 = H1.nxt + else: + # start- and endpoints match + sig_on() + P = poly_sub(H1.poly, H2.poly, self.cmp_terms) + if P.lead != NULL: + if out == NULL: + out = homog_poly_init_poly(H1.start, H1.end, P) + tmp = out + else: + tmp.nxt = homog_poly_init_poly(H1.start, H1.end, P) + tmp = tmp.nxt + else: + poly_free(P) + sig_off() + H1 = H1.nxt + H2 = H2.nxt + +## (scalar) multiplication + + cpdef ModuleElement _lmul_(self, RingElement right): + """ + EXAMPLES:: + + sage: from sage.quivers.algebra_elements import PathAlgebraElement + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: x = sage_eval('3*a+3*b+3*c+3*e_0+3*e_2', A.gens_dict()) + sage: x*2 # indirect doctest + 6*e_0 + 6*a + 6*b + 6*c + 6*e_2 + + :: + + sage: z = sage_eval('a+2*b+5*c+5*e_0+3*e_2', A.gens_dict()) + sage: z + 5*e_0 + a + 2*b + 5*c + 3*e_2 + sage: z*3 + 3*a + 6*b + 9*e_2 + + """ + cdef path_homog_poly_t * out = homog_poly_scale(self.data, right) + cdef path_homog_poly_t * outnxt + if out.poly.nterms == 0: + # homog_poly_scale will remove zero components, except the first. + # Thus, we can return self._new_(out.nxt), but need to free the + # memory occupied by out first. + outnxt = out.nxt + poly_free(out.poly) + sage_free(out) + return self._new_(outnxt) + return self._new_(out) + + cpdef ModuleElement _rmul_(self, RingElement left): + """ + EXAMPLES:: + + sage: from sage.quivers.algebra_elements import PathAlgebraElement + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: x = sage_eval('3*a+3*b+3*c+3*e_0+3*e_2', A.gens_dict()) + sage: 2*x # indirect doctest + 6*e_0 + 6*a + 6*b + 6*c + 6*e_2 + + :: + + sage: z = sage_eval('a+2*b+5*c+5*e_0+3*e_2', A.gens_dict()) + sage: z + 5*e_0 + a + 2*b + 5*c + 3*e_2 + sage: 3*z + 3*a + 6*b + 9*e_2 + + """ + cdef path_homog_poly_t * out = homog_poly_scale(self.data, left) + cdef path_homog_poly_t * outnxt + if out.poly.nterms == 0: + # homog_poly_scale will remove zero components, except the first. + # Thus, we can return self._new_(out.nxt), but need to free the + # memory occupied by out first. + outnxt = out.nxt + poly_free(out.poly) + sage_free(out) + return self._new_(outnxt) + return self._new_(out) + + def __div__(self, x): + """ + Division by coefficients. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: X = sage_eval('a+2*b+3*c+5*e_0+3*e_2', A.gens_dict()) + sage: X/2 + 10*e_0 + 8*a + b + 9*c + 9*e_2 + sage: (X/2)*2 == X # indirect doctest + True + + :: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: X = a+2*b+3*c+5*e_0+3*e_2 + sage: X/4 + 5/4*e_0 + 1/4*a + 1/2*b + 3/4*c + 3/4*e_2 + sage: (X/4).parent() + Path algebra of Looped multi-digraph on 3 vertices over Rational Field + sage: (X/4)*4 == X + True + + """ + cdef PathAlgebraElement sample + if isinstance(self, PathAlgebraElement): + sample = self + x = ~(sample._parent._base( x )) + if x.parent() is not sample._parent._base: + sample = sample._parent._semigroup.algebra(x.parent())(0) + return sample._new_(homog_poly_scale((self).data, x)) + raise TypeError("Don't know how to divide {} by {}".format(x, self)) + +## Multiplication in the algebra + + cpdef RingElement _mul_(self, RingElement other): + """ + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15)) + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: x = b*e*b*e+4*b*e+e_0 + sage: y = a*c+5*f*e + sage: x*y + a*c + 4*b*e*a*c + b*e*b*e*a*c + sage: y*x + a*c + 4*a*c*b*e + a*c*b*e*b*e + 5*f*e + 5*f*e*b*e + 5*f*e*b*e*b*e + sage: y*y + a*c*a*c + 5*f*e*a*c + sage: x*x + e_0 + 8*b*e + 3*b*e*b*e + 8*b*e*b*e*b*e + b*e*b*e*b*e*b*e + + :: + + sage: x = b*e*b*e+4*b*e+e_0 + sage: y = a*c+d*c*b*f + sage: x*(y+x) == x*y+x*x + True + + TESTS: + + We compare against the multiplication in free algebras, which is + implemented independently:: + + sage: F. = FreeAlgebra(GF(25,'t')) + sage: A = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: pF = x+2*y-z+1 + sage: pA = sage_eval('x+2*y-z+1', A.gens_dict()) + sage: pA^5 == sage_eval(repr(pF^5), A.gens_dict()) + True + + """ + cdef PathAlgebraElement right = other + cdef path_homog_poly_t *H1 = self.data + cdef path_homog_poly_t *H2 + cdef path_term_t *T2 + cdef path_poly_t *P + cdef path_homog_poly_t *out_orig = NULL + cdef path_homog_poly_t *out = NULL + cdef path_homog_poly_t *nxt + cdef path_term_t *P1start + cdef int c + while H1 != NULL: + H2 = right.data + while H2 != NULL: + sig_check() + if H2.start == H1.end: + out = homog_poly_get_predecessor_of_component(out_orig, H1.start, H2.end) + if out == NULL: + if out_orig == NULL: + out_orig = homog_poly_create(H1.start, H2.end) + else: + if out_orig.start != H1.start or out_orig.end != H2.end: + nxt = out_orig + out_orig = homog_poly_create(H1.start, H2.end) + out_orig.nxt = nxt + else: + if out.nxt==NULL or out.nxt.start != H1.start or out.nxt.end != H2.end: + nxt = out.nxt + out.nxt = homog_poly_create(H1.start, H2.end) + out.nxt.nxt = nxt + T2 = H2.poly.lead + # now, either out==NULL, and we need to put the product + # into out_orig; or out!=NULL, and we need to put the + # product into out.nxt + if out == NULL: + P1start = out_orig.poly.lead + while T2 != NULL: + P1start = poly_iadd_lmul(out_orig.poly, T2.coef, H1.poly, + T2.mon.path, self.cmp_terms, -1, 0, 0, P1start) + if P1start == H1.poly.lead: + P1start = out_orig.poly.lead + T2 = T2.nxt + else: + P1start = out.nxt.poly.lead + while T2 != NULL: + P1start = poly_iadd_lmul(out.nxt.poly, T2.coef, H1.poly, + T2.mon.path, self.cmp_terms, -1, 0, 0, P1start) + if P1start == H1.poly.lead: + P1start = out.nxt.poly.lead + T2 = T2.nxt + H2 = H2.nxt + H1 = H1.nxt + while out_orig != NULL and out_orig.poly.lead == NULL: + tmp = out_orig.nxt + sig_check() + sage_free(out_orig.poly) + sage_free(out_orig) + out_orig = tmp + if out_orig == NULL: + return self._new_(NULL) + tmp = out_orig + while tmp.nxt != NULL: + if tmp.nxt.poly.lead == NULL: + sig_check() + nxt = tmp.nxt.nxt + sage_free(tmp.nxt.poly) + sage_free(tmp.nxt) + tmp.nxt = nxt + else: + tmp = tmp.nxt + return self._new_(out_orig) + +cpdef PathAlgebraElement path_algebra_element_unpickle(P, list data): + """ + Auxiliary function for unpickling. + + EXAMPLES:: + + sage: A = DiGraph({0:{1:['a'], 2:['b']}, 1:{0:['c'], 1:['d']}, 2:{0:['e'],2:['f']}}).path_semigroup().algebra(ZZ.quo(15), order='negdeglex') + sage: A.inject_variables() + Defining e_0, e_1, e_2, a, b, c, d, e, f + sage: X = a+2*b+3*c+5*e_0+3*e_2 + sage: loads(dumps(X)) == X # indirect doctest + True + + """ + cdef PathAlgebraElement out = P.element_class.__new__(P.element_class) + out._parent = P + order = P.order_string() + if order=="negdegrevlex": + out.cmp_terms = negdegrevlex + elif order=="degrevlex": + out.cmp_terms = degrevlex + elif order=="negdeglex": + out.cmp_terms = negdeglex + elif order=="deglex": + out.cmp_terms = deglex + else: + raise ValueError("Unknown term order '{}'".format(order)) + out.data = homog_poly_unpickle(data) + out._hash = -1 + return out diff --git a/src/sage/quivers/path_semigroup.py b/src/sage/quivers/path_semigroup.py index bf14e6f6d68..1ff9ea3f849 100644 --- a/src/sage/quivers/path_semigroup.py +++ b/src/sage/quivers/path_semigroup.py @@ -738,7 +738,7 @@ def reverse(self): """ return self._quiver.reverse().path_semigroup() - def algebra(self, k): + def algebra(self, k, order = "negdegrevlex"): """ Return the path algebra of the underlying quiver. @@ -746,15 +746,41 @@ def algebra(self, k): - ``k`` -- a commutative ring + - ``order`` -- optional string, one of "negdegrevlex" (default), + "degrevlex", "negdeglex" or "deglex", defining the monomial order to + be used. + + NOTE: + + Monomial orders that are not degree orders are not supported. + EXAMPLES:: sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) sage: P = Q.path_semigroup() sage: P.algebra(GF(3)) Path algebra of Multi-digraph on 3 vertices over Finite Field of size 3 + + Now some example with different monomial orderings:: + + sage: P1 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t')) + sage: P2 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="degrevlex") + sage: P3 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="negdeglex") + sage: P4 = DiGraph({1:{1:['x','y','z']}}).path_semigroup().algebra(GF(25,'t'), order="deglex") + sage: P1.order_string() + 'negdegrevlex' + sage: sage_eval('(x+2*z+1)^3', P1.gens_dict()) + e_1 + z + 3*x + 2*z*z + x*z + z*x + 3*x*x + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + sage: sage_eval('(x+2*z+1)^3', P2.gens_dict()) + 3*z*z*z + 4*x*z*z + 4*z*x*z + 2*x*x*z + 4*z*z*x + 2*x*z*x + 2*z*x*x + x*x*x + 2*z*z + x*z + z*x + 3*x*x + z + 3*x + e_1 + sage: sage_eval('(x+2*z+1)^3', P3.gens_dict()) + e_1 + z + 3*x + 2*z*z + z*x + x*z + 3*x*x + 3*z*z*z + 4*z*z*x + 4*z*x*z + 2*z*x*x + 4*x*z*z + 2*x*z*x + 2*x*x*z + x*x*x + sage: sage_eval('(x+2*z+1)^3', P4.gens_dict()) + 3*z*z*z + 4*z*z*x + 4*z*x*z + 2*z*x*x + 4*x*z*z + 2*x*z*x + 2*x*x*z + x*x*x + 2*z*z + z*x + x*z + 3*x*x + z + 3*x + e_1 + """ from sage.quivers.algebra import PathAlgebra - return PathAlgebra(k, self) + return PathAlgebra(k, self, order) ########################################################################### # # diff --git a/src/sage/quivers/paths.pxd b/src/sage/quivers/paths.pxd index e75fac3a445..09fcddc380a 100644 --- a/src/sage/quivers/paths.pxd +++ b/src/sage/quivers/paths.pxd @@ -5,6 +5,7 @@ cdef class QuiverPath(MonoidElement): cdef biseq_t _path cdef int _start, _end cdef QuiverPath _new_(self, int start, int end) + cpdef tuple complement(self, QuiverPath subpath) cpdef bint has_subpath(self, QuiverPath subpath) except -1 cpdef bint has_prefix(self, QuiverPath subpath) except -1 diff --git a/src/sage/quivers/paths.pyx b/src/sage/quivers/paths.pyx index 200055de4cc..716da5bf250 100644 --- a/src/sage/quivers/paths.pyx +++ b/src/sage/quivers/paths.pyx @@ -568,6 +568,31 @@ cdef class QuiverPath(MonoidElement): return (None, None, None) return (self[:i], self[i:], P[self._path.length-i:]) + cpdef tuple complement(self, QuiverPath subpath): + """ + Return a pair ``(a,b)`` of paths s.t. ``self==a*subpath*b``, + or ``(None, None)`` if ``subpath`` is not a subpath of this path. + + NOTE: + + ``a`` is chosen of minimal length. + + EXAMPLES:: + + sage: S = DiGraph({1:{1:['a','b','c','d']}}).path_semigroup() + sage: S.inject_variables() + Defining e_1, a, b, c, d + sage: (b*c*a*d*b*a*d*d).complement(a*d) + (b*c, b*a*d*d) + sage: (b*c*a*d*b).complement(a*c) + (None, None) + + """ + cdef mp_size_t i = biseq_contains(self._path, subpath._path, 0) + if i == -1: + return (None, None) + return self[:i], self[i+len(subpath):] + cpdef bint has_subpath(self, QuiverPath subpath) except -1: """ Tells whether this path contains a given sub-path. @@ -602,7 +627,6 @@ cdef class QuiverPath(MonoidElement): raise ValueError("The two paths belong to different quivers") if subpath._path.length == 0: raise ValueError("We only consider sub-paths of positive length") - cdef int v cdef size_t i cdef size_t max_i, bitsize if self._path.length < subpath._path.length: @@ -713,12 +737,12 @@ cdef class QuiverPath(MonoidElement): out._parent = Q out._start = self._end out._end = self._start - sig_on() + sig_check() biseq_init(out._path, self._path.length, self._path.itembitsize) cdef mp_size_t l = self._path.length - 1 for i in range(self._path.length): + sig_check() biseq_inititem(out._path, i, biseq_getitem(self._path, l-i)) - sig_off() return out cpdef QuiverPath NewQuiverPath(Q, start, end, biseq_data): diff --git a/src/sage/quivers/representation.py b/src/sage/quivers/representation.py index cb7d9c3c56c..79bd2ee7ccf 100644 --- a/src/sage/quivers/representation.py +++ b/src/sage/quivers/representation.py @@ -193,7 +193,7 @@ a*d + e_2 sage: y = A(p) + A(r) sage: y - a*d + b*d + b*d + a*d :class:`~sage.quivers.algebra.QuiverAlgebras` are `\NN`-graded algebras. The grading is given by assigning to each basis element the length of the diff --git a/src/sage/repl/display/fancy_repr.py b/src/sage/repl/display/fancy_repr.py index 5c1aa6977fa..18f0906747b 100644 --- a/src/sage/repl/display/fancy_repr.py +++ b/src/sage/repl/display/fancy_repr.py @@ -251,7 +251,7 @@ def __call__(self, obj, p, cycle): a custom representer. Note that it is undesirable to have a trailing newline, and if we don't display it you can't fix it:: - + sage: class Newline(object): ....: def __repr__(self): ....: return 'newline\n' @@ -317,11 +317,36 @@ def __call__(self, obj, p, cycle): sage: format_list = TallListRepr().format_string sage: format_list([1, 2, identity_matrix(2)]) '[\n [1 0]\n1, 2, [0 1]\n]' + + Check that :trac:`18743` is fixed:: + + sage: class Foo(object): + ....: def __repr__(self): + ....: return '''BBB AA RRR + ....: B B A A R R + ....: BBB AAAA RRR + ....: B B A A R R + ....: BBB A A R R''' + ....: def _repr_option(self, key): + ....: return key == 'ascii_art' + sage: F = Foo() + sage: [F, F] + [ + BBB AA RRR BBB AA RRR + B B A A R R B B A A R R + BBB AAAA RRR BBB AAAA RRR + B B A A R R B B A A R R + BBB A A R R, BBB A A R R + ] """ if not (isinstance(obj, (tuple, list)) and len(obj) > 0): return False ascii_art_repr = False for o in obj: + try: + ascii_art_repr = ascii_art_repr or o._repr_option('ascii_art') + except (AttributeError, TypeError): + pass try: ascii_art_repr = ascii_art_repr or o.parent()._repr_option('element_ascii_art') except (AttributeError, TypeError): diff --git a/src/sage/repl/display/jsmol_iframe.py b/src/sage/repl/display/jsmol_iframe.py index f3cd8d4325a..2bad8132532 100644 --- a/src/sage/repl/display/jsmol_iframe.py +++ b/src/sage/repl/display/jsmol_iframe.py @@ -249,7 +249,7 @@ def iframe(self): String. EXAMPLES:: - + sage: from sage.repl.display.jsmol_iframe import JSMolHtml sage: from sage.repl.rich_output.output_graphics3d import OutputSceneJmol sage: jmol = JSMolHtml(OutputSceneJmol.example()) @@ -276,7 +276,7 @@ def outer_html(self): String EXAMPLES:: - + sage: from sage.repl.display.jsmol_iframe import JSMolHtml sage: from sage.repl.rich_output.output_graphics3d import OutputSceneJmol sage: jmol = JSMolHtml(OutputSceneJmol.example(), width=500, height=300) diff --git a/src/sage/repl/display/pretty_print.py b/src/sage/repl/display/pretty_print.py index ff3aebcefea..b895b1997c3 100644 --- a/src/sage/repl/display/pretty_print.py +++ b/src/sage/repl/display/pretty_print.py @@ -74,12 +74,12 @@ def __init__(self, output, max_width, newline, max_seq_length=None): See IPython documentation. EXAMPLES:: - + sage: 123 123 IPython pretty printers:: - + sage: set({1, 2, 3}) {1, 2, 3} sage: dict(zzz=123, aaa=99, xab=10) # sorted by keys @@ -88,7 +88,7 @@ def __init__(self, output, max_width, newline, max_seq_length=None): These are overridden in IPython in a way that we feel is somewhat confusing, and we prefer to print them like plain Python which is more informative. See :trac:`14466` :: - + sage: 'this is a string' 'this is a string' sage: type(123) diff --git a/src/sage/repl/image.py b/src/sage/repl/image.py index 34e065a094c..174703ca40d 100644 --- a/src/sage/repl/image.py +++ b/src/sage/repl/image.py @@ -14,8 +14,9 @@ sage: from sage.repl.image import Image sage: img = Image('RGB', (256, 256), 'white') sage: pixels = img.pixels() - sage: for x, y in CartesianProduct(range(img.width()), range(img.height())): - ....: pixels[x, y] = (x, y, 100) + sage: for x in range(img.width()): + ....: for y in range(img.height()): + ....: pixels[x, y] = (x, y, 100) sage: img 256x256px 24-bit RGB image sage: type(img) diff --git a/src/sage/repl/interpreter.py b/src/sage/repl/interpreter.py index 5b776d549aa..2933db8d4b9 100644 --- a/src/sage/repl/interpreter.py +++ b/src/sage/repl/interpreter.py @@ -79,11 +79,11 @@ .../sage/rings/integer_ring.pyx in sage.rings.integer_ring.IntegerRing_class._div (.../cythonized/sage/rings/integer_ring.c:...)() ... cdef rational.Rational x = rational.Rational.__new__(rational.Rational) ... if mpz_sgn(right.value) == 0: - ... raise ZeroDivisionError('Rational division by zero') + ... raise ZeroDivisionError('rational division by zero') ... mpz_set(mpq_numref(x.value), left.value) ... mpz_set(mpq_denref(x.value), right.value) - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero sage: shell.quit() """ @@ -104,8 +104,8 @@ import sys from sage.repl.preparse import preparse -from IPython import Config -from IPython.utils.traitlets import Bool, Type +from traitlets.config.loader import Config +from traitlets import Bool, Type from sage.env import SAGE_LOCAL @@ -372,7 +372,7 @@ def run_cell(self, *args, **kwds): --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero sage: rc is None True sage: shell.quit() @@ -755,7 +755,7 @@ def load_config_file(self, *args, **kwds): sage: from sage.misc.temporary_file import tmp_dir sage: from sage.repl.interpreter import SageTerminalApp sage: d = tmp_dir() - sage: from IPython.utils.path import get_ipython_dir + sage: from IPython.paths import get_ipython_dir sage: IPYTHONDIR = get_ipython_dir() sage: os.environ['IPYTHONDIR'] = d sage: SageTerminalApp().load_config_file() diff --git a/src/sage/repl/ipython_kernel/__main__.py b/src/sage/repl/ipython_kernel/__main__.py index 0dddb1fa919..a1657263bee 100644 --- a/src/sage/repl/ipython_kernel/__main__.py +++ b/src/sage/repl/ipython_kernel/__main__.py @@ -1,3 +1,3 @@ -from IPython.kernel.zmq.kernelapp import IPKernelApp +from ipykernel.kernelapp import IPKernelApp from sage.repl.ipython_kernel.kernel import SageKernel IPKernelApp.launch_instance(kernel_class=SageKernel) diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 160987fc25f..fe8aafbce85 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -1,41 +1,38 @@ """ -Installing the Sage IPython Kernel +Installing the SageMath Jupyter Kernel and extensions -Kernels have to register themselves with IPython so that they appear -in the IPython notebook's kernel drop-down. This is done by +Kernels have to register themselves with Jupyter so that they appear +in the Jupyter notebook's kernel drop-down. This is done by :class:`SageKernelSpec`. """ import os import errno -import copy - -from IPython.kernel.kernelspec import ( - get_kernel_spec, install_kernel_spec, NoSuchKernel) -from IPython.utils.path import get_ipython_dir from sage.env import ( SAGE_ROOT, SAGE_DOC, SAGE_LOCAL, SAGE_EXTCODE, SAGE_VERSION ) -from sage.misc.temporary_file import tmp_dir +from jupyter_core.paths import ENV_JUPYTER_PATH +JUPYTER_PATH = ENV_JUPYTER_PATH[0] class SageKernelSpec(object): def __init__(self): """ - Utility to manage Sage kernels + Utility to manage SageMath kernels and extensions EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._display_name # random output - 'Sage 6.6.beta2' + 'SageMath 6.9' """ - self._display_name = 'Sage {0}'.format(SAGE_VERSION) - self._ipython_dir = get_ipython_dir() + self._display_name = 'SageMath {0}'.format(SAGE_VERSION) + self.nbextensions_dir = os.path.join(JUPYTER_PATH, "nbextensions") + self.kernel_dir = os.path.join(JUPYTER_PATH, "kernels", self.identifier()) self._mkdirs() def _mkdirs(self): @@ -47,40 +44,33 @@ def _mkdirs(self): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._mkdirs() - sage: nbextensions = os.path.join(spec._ipython_dir, 'nbextensions') - sage: os.path.exists(nbextensions) + sage: os.path.isdir(spec.nbextensions_dir) True """ - def mkdir_p(*path_components): - path = os.path.join(*path_components) + def mkdir_p(path): try: os.makedirs(path) - except OSError as err: - if err.errno == errno.EEXIST and os.path.isdir(path): - pass - else: + except OSError: + if not os.path.isdir(path): raise - mkdir_p(self._ipython_dir, 'nbextensions') + mkdir_p(self.nbextensions_dir) + mkdir_p(self.kernel_dir) @classmethod - def identifier(self): + def identifier(cls): """ - Internal identifier for the Sage kernel - - OUTPUT: + Internal identifier for the SageMath kernel - String. + OUTPUT: the string ``"sagemath"``. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: SageKernelSpec.identifier() # random output - 'sage_6_6_beta3' - sage: SageKernelSpec.identifier().startswith('sage_') - True + sage: SageKernelSpec.identifier() + 'sagemath' """ - return 'Sage {0}'.format(SAGE_VERSION).lower().replace(' ', '_').replace('.', '_') - + return 'sagemath' + def symlink(self, src, dst): """ Symlink ``src`` to ``dst`` @@ -105,38 +95,48 @@ def symlink(self, src, dst): if err.errno == errno.EEXIST: return os.symlink(src, dst) - + def use_local_mathjax(self): """ - Symlink Sage's Mathjax Install to the IPython notebook. + Symlink SageMath's Mathjax install to the Jupyter notebook. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: from IPython.utils.path import get_ipython_dir sage: spec = SageKernelSpec() sage: spec.use_local_mathjax() - sage: ipython_dir = get_ipython_dir() - sage: mathjax = os.path.join(ipython_dir, 'nbextensions', 'mathjax') - sage: os.path.exists(mathjax) + sage: mathjax = os.path.join(spec.nbextensions_dir, 'mathjax') + sage: os.path.isdir(mathjax) True """ src = os.path.join(SAGE_LOCAL, 'share', 'mathjax') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'mathjax') + dst = os.path.join(self.nbextensions_dir, 'mathjax') self.symlink(src, dst) def use_local_jsmol(self): + """ + Symlink jsmol to the Jupyter notebook. + + EXAMPLES:: + + sage: from sage.repl.ipython_kernel.install import SageKernelSpec + sage: spec = SageKernelSpec() + sage: spec.use_local_jsmol() + sage: jsmol = os.path.join(spec.nbextensions_dir, 'jsmol') + sage: os.path.isdir(jsmol) + True + """ src = os.path.join(SAGE_LOCAL, 'share', 'jsmol') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'jsmol') + dst = os.path.join(self.nbextensions_dir, 'jsmol') self.symlink(src, dst) def _kernel_cmd(self): """ - Helper to construct the Sage kernel command. - + Helper to construct the SageMath kernel command. + OUTPUT: - List of strings. The command to start a new Sage kernel. + List of strings. The command to start a new SageMath kernel. EXAMPLES:: @@ -144,7 +144,7 @@ def _kernel_cmd(self): sage: spec = SageKernelSpec() sage: spec._kernel_cmd() ['/.../sage', - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', @@ -152,40 +152,34 @@ def _kernel_cmd(self): """ return [ os.path.join(SAGE_ROOT, 'sage'), - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', '{connection_file}', ] - + def kernel_spec(self): """ Return the kernel spec as Python dictionary OUTPUT: - A dictionary. See the IPython documentation for details. + A dictionary. See the Jupyter documentation for details. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec.kernel_spec() - {'argv': ..., 'display_name': 'Sage ...'} + {'argv': ..., 'display_name': 'SageMath ...'} """ return dict( argv=self._kernel_cmd(), display_name=self._display_name, ) - + def _install_spec(self): """ - Install the Sage IPython kernel - - It is safe to call this method multiple times, only one Sage - kernel spec is ever installed for any given Sage - version. However, it resets the IPython kernel spec directory - so additional resources symlinked there are lost. See - :meth:`symlink_resources`. + Install the SageMath Jupyter kernel EXAMPLES:: @@ -193,21 +187,17 @@ def _install_spec(self): sage: spec = SageKernelSpec() sage: spec._install_spec() # not tested """ + jsonfile = os.path.join(self.kernel_dir, "kernel.json") import json - temp = tmp_dir() - kernel_spec = os.path.join(temp, 'kernel.json') - with open(kernel_spec, 'w') as f: + with open(jsonfile, 'w') as f: json.dump(self.kernel_spec(), f) - identifier = self.identifier() - install_kernel_spec(temp, identifier, user=True, replace=True) - self._spec = get_kernel_spec(identifier) def _symlink_resources(self): """ Symlink miscellaneous resources - This method symlinks additional resources (like the Sage - documentation) into the Sage kernel directory. This is + This method symlinks additional resources (like the SageMath + documentation) into the SageMath kernel directory. This is necessary to make the help links in the notebook work. EXAMPLES:: @@ -217,25 +207,23 @@ def _symlink_resources(self): sage: spec._install_spec() # not tested sage: spec._symlink_resources() # not tested """ - assert self._spec, 'call _install_spec() first' - spec_dir = self._spec.resource_dir path = os.path.join(SAGE_EXTCODE, 'notebook-ipython') for filename in os.listdir(path): self.symlink( os.path.join(path, filename), - os.path.join(spec_dir, filename) + os.path.join(self.kernel_dir, filename) ) self.symlink( os.path.join(SAGE_DOC, 'output', 'html', 'en'), - os.path.join(spec_dir, 'doc') + os.path.join(self.kernel_dir, 'doc') ) - + @classmethod def update(cls): """ - Configure the IPython notebook for the Sage kernel - - This method does everything necessary to use the Sage kernel, + Configure the Jupyter notebook for the SageMath kernel + + This method does everything necessary to use the SageMath kernel, you should never need to call any of the other methods directly. @@ -243,7 +231,7 @@ def update(cls): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() - sage: spec.update() + sage: spec.update() # not tested """ instance = cls() instance.use_local_mathjax() @@ -251,13 +239,18 @@ def update(cls): instance._install_spec() instance._symlink_resources() - -def have_prerequisites(): + +def have_prerequisites(debug=True): """ - Check that we have all prerequisites to run the IPython notebook. + Check that we have all prerequisites to run the Jupyter notebook. - In particular, the IPython notebook requires OpenSSL whether or - not you are using https. See trac:`17318`. + In particular, the Jupyter notebook requires OpenSSL whether or + not you are using https. See :trac:`17318`. + + INPUT: + + ``debug`` -- boolean (default: ``True``). Whether to print debug + information in case that prerequisites are missing. OUTPUT: @@ -266,15 +259,14 @@ def have_prerequisites(): EXAMPLES:: sage: from sage.repl.ipython_kernel.install import have_prerequisites - sage: have_prerequisites() in [True, False] + sage: have_prerequisites(debug=False) in [True, False] True """ try: - from IPython.html.notebookapp import NotebookApp + from notebook.notebookapp import NotebookApp return True - except ImportError as err: + except ImportError: + if debug: + import traceback + traceback.print_exc() return False - - - - diff --git a/src/sage/repl/ipython_kernel/kernel.py b/src/sage/repl/ipython_kernel/kernel.py index 9d003e0ce9d..eb61032dbd7 100644 --- a/src/sage/repl/ipython_kernel/kernel.py +++ b/src/sage/repl/ipython_kernel/kernel.py @@ -1,8 +1,8 @@ """ The Sage ZMQ Kernel -Version of the IPython kernel when running Sage inside the IPython -notebook or remote IPython sessions. +Version of the Jupyter kernel when running Sage inside the Jupyter +notebook or remote Jupyter sessions. """ #***************************************************************************** @@ -15,9 +15,9 @@ #***************************************************************************** import sys -from IPython.kernel.zmq.ipkernel import IPythonKernel -from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell -from IPython.utils.traitlets import Type +from ipykernel.ipkernel import IPythonKernel +from ipykernel.zmqshell import ZMQInteractiveShell +from traitlets import Type from sage.env import SAGE_VERSION, SAGE_EXTCODE, SAGE_DOC from sage.repl.interpreter import SageNotebookInteractiveShell @@ -27,7 +27,7 @@ class SageZMQInteractiveShell(SageNotebookInteractiveShell, ZMQInteractiveShell) pass -class SageKernel(IPythonKernel): +class SageKernel(IPythonKernel): implementation = 'sage' implementation_version = SAGE_VERSION @@ -35,11 +35,11 @@ class SageKernel(IPythonKernel): def __init__(self, **kwds): """ - The Sage IPython Kernel + The Sage Jupyter Kernel INPUT: - See the IPython documentation + See the Jupyter documentation EXAMPLES:: @@ -54,8 +54,8 @@ def __init__(self, **kwds): def banner(self): r""" The Sage Banner - - The value of this property is displayed in the IPython + + The value of this property is displayed in the Jupyter notebook. OUTPUT: @@ -75,11 +75,11 @@ def banner(self): @property def help_links(self): r""" - Help in the IPython Notebook - + Help in the Jupyter Notebook + OUTPUT: - See the IPython documentation. + See the Jupyter documentation. EXAMPLES:: @@ -87,12 +87,12 @@ def help_links(self): sage: sk = SageKernel.__new__(SageKernel) sage: sk.help_links [{'text': 'Sage Documentation', - 'url': '/kernelspecs/sage_.../doc/index.html'}, + 'url': '../kernelspecs/sagemath/doc/index.html'}, ...] """ from sage.repl.ipython_kernel.install import SageKernelSpec identifier = SageKernelSpec.identifier() - kernel_url = lambda x: '/kernelspecs/{0}/{1}'.format(identifier, x) + kernel_url = lambda x: '../kernelspecs/{0}/{1}'.format(identifier, x) return [ { 'text': 'Sage Documentation', @@ -119,7 +119,7 @@ def help_links(self): 'url': kernel_url('doc/reference/index.html'), }, { - 'text': 'Developers Guide', + 'text': "Developer's Guide", 'url': kernel_url('doc/developer/index.html'), }, { @@ -159,3 +159,7 @@ def help_links(self): 'url': "http://help.github.com/articles/github-flavored-markdown", }, ] + + def pre_handler_hook(self): + from sage.ext.interrupt.interrupt import init_interrupts + self.saved_sigint_handler = init_interrupts() diff --git a/src/sage/repl/readline_extra_commands.pyx b/src/sage/repl/readline_extra_commands.pyx index 47f60b36113..20aa718730f 100644 --- a/src/sage/repl/readline_extra_commands.pyx +++ b/src/sage/repl/readline_extra_commands.pyx @@ -1,5 +1,5 @@ r""" -Extra readline commands +Extra Readline Commands The following extra readline commands are available in Sage: @@ -24,7 +24,7 @@ They are intended to replace the ``history-search-backward`` command and the ``h provided by the GNU readline library used in Sage. To bind these commands with keys, insert the relevant lines into the IPython configuration file -``$DOT_SAGE/ipython-*/profile_sage/ipython_config.py``. Note that ``$DOT_SAGE`` is ``$HOME/.sage`` +``$DOT_SAGE/ipython-*/profile_default/ipython_config.py``. Note that ``$DOT_SAGE`` is ``$HOME/.sage`` by default. For example, :: diff --git a/src/sage/repl/rich_output/backend_doctest.py b/src/sage/repl/rich_output/backend_doctest.py index e2f98c6c404..869089c2c6d 100644 --- a/src/sage/repl/rich_output/backend_doctest.py +++ b/src/sage/repl/rich_output/backend_doctest.py @@ -86,7 +86,7 @@ def install(self, **kwds): backend. EXAMPLES:: - + sage: from sage.repl.rich_output.backend_doctest import BackendDoctest sage: backend = BackendDoctest() sage: backend.install() @@ -104,7 +104,7 @@ def uninstall(self): should never call it by hand. EXAMPLES:: - + sage: from sage.repl.rich_output.backend_doctest import BackendDoctest sage: backend = BackendDoctest() sage: backend.install() @@ -137,6 +137,9 @@ def supported_output(self): OutputImagePng, OutputImageGif, OutputImageJpg, OutputImageSvg, OutputImagePdf, OutputImageDvi, OutputSceneJmol, OutputSceneCanvas3d, OutputSceneWavefront, + OutputVideoOgg, OutputVideoWebM, OutputVideoMp4, + OutputVideoFlash, OutputVideoMatroska, OutputVideoAvi, + OutputVideoWmv, OutputVideoQuicktime, ]) def displayhook(self, plain_text, rich_output): @@ -204,7 +207,7 @@ def display_immediately(self, plain_text, rich_output): sage: dm.display_immediately(plt) # indirect doctest """ self.validate(rich_output) - types_to_print = [OutputPlainText, OutputAsciiArt, OutputUnicodeArt] + types_to_print = [OutputPlainText, OutputAsciiArt, OutputUnicodeArt, OutputHtml] if isinstance(rich_output, OutputLatex): print(rich_output.mathjax(display=False)) elif any(isinstance(rich_output, cls) for cls in types_to_print): @@ -246,6 +249,14 @@ def validate(self, rich_output): sage: backend.validate(dm.types.OutputSceneJmol.example()) sage: backend.validate(dm.types.OutputSceneWavefront.example()) sage: backend.validate(dm.types.OutputSceneCanvas3d.example()) + sage: backend.validate(dm.types.OutputVideoOgg.example()) + sage: backend.validate(dm.types.OutputVideoWebM.example()) + sage: backend.validate(dm.types.OutputVideoMp4.example()) + sage: backend.validate(dm.types.OutputVideoFlash.example()) + sage: backend.validate(dm.types.OutputVideoMatroska.example()) + sage: backend.validate(dm.types.OutputVideoAvi.example()) + sage: backend.validate(dm.types.OutputVideoWmv.example()) + sage: backend.validate(dm.types.OutputVideoQuicktime.example()) """ if isinstance(rich_output, OutputPlainText): pass @@ -275,5 +286,34 @@ def validate(self, rich_output): assert rich_output.mtl.get().startswith('newmtl ') elif isinstance(rich_output, OutputSceneCanvas3d): assert rich_output.canvas3d.get().startswith('[{vertices:') + elif isinstance(rich_output, OutputVideoOgg): + assert rich_output.video.get().startswith('OggS') + elif isinstance(rich_output, OutputVideoWebM): + data = rich_output.video.get() + assert data.startswith('\x1a\x45\xdf\xa3') + assert '\x42\x82\x84webm' in data + elif isinstance(rich_output, OutputVideoMp4): + data = rich_output.video.get() + assert data[4:8] == 'ftyp' + assert data.startswith('\0\0\0') + # See http://www.ftyps.com/ + ftyps = [data[i:i+4] for i in range(8, ord(data[3]), 4)] + del ftyps[1] # version number, not an ftyp + expected = ['avc1', 'iso2', 'mp41', 'mp42'] + assert any(i in ftyps for i in expected) + elif isinstance(rich_output, OutputVideoFlash): + assert rich_output.video.get().startswith('FLV\x01') + elif isinstance(rich_output, OutputVideoMatroska): + data = rich_output.video.get() + assert data.startswith('\x1a\x45\xdf\xa3') + assert '\x42\x82\x88matroska' in data + elif isinstance(rich_output, OutputVideoAvi): + data = rich_output.video.get() + assert data[:4] == 'RIFF' and data[8:12] == 'AVI ' + elif isinstance(rich_output, OutputVideoWmv): + assert rich_output.video.get().startswith('\x30\x26\xb2\x75') + elif isinstance(rich_output, OutputVideoQuicktime): + data = rich_output.video.get() + assert data[4:12] == 'ftypqt ' or data[4:8] == 'moov' else: raise TypeError('rich_output type not supported') diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index 3c287683a1d..f5fa94bbee5 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -464,6 +464,7 @@ def supported_output(self): """ return set([ OutputPlainText, OutputAsciiArt, OutputUnicodeArt, OutputLatex, + OutputHtml, OutputImagePng, OutputImageJpg, OutputImageSvg, OutputImagePdf, OutputSceneJmol, @@ -512,6 +513,10 @@ def displayhook(self, plain_text, rich_output): return ({u'text/html': rich_output.mathjax(), u'text/plain': plain_text.text.get_unicode(), }, {}) + elif isinstance(rich_output, OutputHtml): + return ({u'text/html': rich_output.html.get(), + u'text/plain': plain_text.text.get(), + }, {}) elif isinstance(rich_output, OutputImagePng): return ({u'image/png': rich_output.png.get(), u'text/plain': plain_text.text.get_unicode(), diff --git a/src/sage/repl/rich_output/backend_sagenb.py b/src/sage/repl/rich_output/backend_sagenb.py index 8d2dd157243..e1a94799287 100644 --- a/src/sage/repl/rich_output/backend_sagenb.py +++ b/src/sage/repl/rich_output/backend_sagenb.py @@ -38,6 +38,21 @@ sage: os.path.exists('sage0.png') True sage: os.remove('sage0.png') + +Tables are typeset as html in SageNB:: + + sage: table([1, 2, 3]) +
+ + + + + + + + +
+
""" #***************************************************************************** @@ -52,10 +67,12 @@ import os import stat from sage.misc.cachefunc import cached_method +from sage.misc.html import html from sage.misc.temporary_file import graphics_filename from sage.doctest import DOCTEST_MODE from sage.repl.rich_output.backend_base import BackendBase from sage.repl.rich_output.output_catalog import * +from sage.repl.rich_output.output_video import OutputVideoBase def world_readable(filename): @@ -304,10 +321,12 @@ def supported_output(self): """ return set([ OutputPlainText, OutputAsciiArt, OutputLatex, + OutputHtml, OutputImagePng, OutputImageGif, OutputImageJpg, OutputImagePdf, OutputImageSvg, SageNbOutputSceneJmol, OutputSceneCanvas3d, + OutputVideoOgg, OutputVideoWebM, OutputVideoMp4, ]) def display_immediately(self, plain_text, rich_output): @@ -347,6 +366,8 @@ def display_immediately(self, plain_text, rich_output): rich_output.print_to_stdout() elif isinstance(rich_output, OutputLatex): print(rich_output.mathjax()) + elif isinstance(rich_output, OutputHtml): + print(rich_output.with_html_tag()) elif isinstance(rich_output, OutputImagePng): self.embed_image(rich_output.png, '.png') elif isinstance(rich_output, OutputImageGif): @@ -361,6 +382,8 @@ def display_immediately(self, plain_text, rich_output): rich_output.embed() elif isinstance(rich_output, OutputSceneCanvas3d): self.embed_image(rich_output.canvas3d, '.canvas3d') + elif isinstance(rich_output, OutputVideoBase): + self.embed_video(rich_output) else: raise TypeError('rich_output type not supported, got {0}'.format(rich_output)) @@ -400,4 +423,11 @@ def embed_image(self, output_buffer, file_ext): output_buffer.save_as(filename) world_readable(filename) - + def embed_video(self, video_output): + filename = graphics_filename(ext=video_output.ext) + video_output.video.save_as(filename) + world_readable(filename) + html(video_output.html_fragment( + url='cell://' + filename, + link_attrs='class="file_link"', + )) diff --git a/src/sage/repl/rich_output/output_browser.py b/src/sage/repl/rich_output/output_browser.py new file mode 100644 index 00000000000..9ac5323f599 --- /dev/null +++ b/src/sage/repl/rich_output/output_browser.py @@ -0,0 +1,88 @@ +# -*- encoding: utf-8 -*- +r""" +Rich Output for the Browser +""" + +from sage.repl.rich_output.output_basic import OutputBase +from sage.repl.rich_output.buffer import OutputBuffer + + +class OutputHtml(OutputBase): + + def __init__(self, html): + """ + HTML Output + + INPUT: + + - ``html`` -- + :class:`~sage.repl.rich_output.buffer.OutputBuffer`. Alternatively, + a string (bytes) can be passed directly which will then be + converted into an + :class:`~sage.repl.rich_output.buffer.OutputBuffer`. String + containing the html fragment code. Excludes the surrounding + ```` and ```` tag. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputHtml + sage: OutputHtml('
FooBar
') + OutputHtml container + """ + self.html = OutputBuffer(html) + + @classmethod + def example(cls): + r""" + Construct a sample Html output container + + This static method is meant for doctests, so they can easily + construt an example. + + OUTPUT: + + An instance of :class:`OutputHtml`. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputHtml + sage: OutputHtml.example() + OutputHtml container + sage: OutputHtml.example().html.get() + '
Hello World!
' + """ + return cls('
Hello World!
') + + def print_to_stdout(self): + r""" + Write the data to stdout. + + This is just a convenience method to help with debugging. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputHtml + sage: rich_output = OutputHtml.example() + sage: rich_output.print_to_stdout() +
Hello World!
+ """ + print(self.html.get()) + + def with_html_tag(self): + r""" + Return the HTML code surrounded by ```` tag + + This is just a convenience method. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputHtml + sage: rich_output = OutputHtml.example() + sage: rich_output.print_to_stdout() +
Hello World!
+ sage: rich_output.with_html_tag() + '
Hello World!
' + """ + return '{0}'.format(self.html.get()) + + diff --git a/src/sage/repl/rich_output/output_catalog.py b/src/sage/repl/rich_output/output_catalog.py index f0fcc3379a8..95890a99937 100644 --- a/src/sage/repl/rich_output/output_catalog.py +++ b/src/sage/repl/rich_output/output_catalog.py @@ -22,6 +22,10 @@ OutputLatex, ) +from .output_browser import ( + OutputHtml, +) + from .output_graphics import ( OutputImagePng, OutputImageGif, @@ -36,3 +40,14 @@ OutputSceneWavefront, OutputSceneCanvas3d, ) + +from .output_video import ( + OutputVideoOgg, + OutputVideoWebM, + OutputVideoMp4, + OutputVideoFlash, + OutputVideoMatroska, + OutputVideoAvi, + OutputVideoWmv, + OutputVideoQuicktime, +) diff --git a/src/sage/repl/rich_output/output_video.py b/src/sage/repl/rich_output/output_video.py new file mode 100644 index 00000000000..8abb64d0cfa --- /dev/null +++ b/src/sage/repl/rich_output/output_video.py @@ -0,0 +1,230 @@ +# -*- encoding: utf-8 -*- +r""" +Video Output Types + +This module defines the rich output types for video formats. +""" + +#***************************************************************************** +# Copyright (C) 2015 Martin von Gagern +# +# 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/ +#***************************************************************************** + + +import os + +from sage.repl.rich_output.output_basic import OutputBase +from sage.repl.rich_output.buffer import OutputBuffer + + +class OutputVideoBase(OutputBase): + + def __init__(self, video, loop=True): + """ + Abstract base class for rich video output + + INPUT: + + - ``video`` -- + :class:`~sage.repl.rich_output.buffer.OutputBuffer`. + The video data. + - ``loop`` -- boolean. Whether to repeat the video in an endless loop. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoOgg + sage: OutputVideoOgg.example() # indirect doctest + OutputVideoOgg container + """ + assert isinstance(video, OutputBuffer) + self.video = video + self.loop = loop + + @classmethod + def example(cls): + r""" + Construct a sample video output container + + This static method is meant for doctests, so they can easily + construct an example. The method is implemented in the abstract + :class:`OutputVideoBase` class, but should get invoked on a + concrete subclass for which an actual example can exist. + + OUTPUT: + + An instance of the class on which this method is called. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoOgg + sage: OutputVideoOgg.example() + OutputVideoOgg container + sage: OutputVideoOgg.example().video + buffer containing 5612 bytes + sage: OutputVideoOgg.example().ext + '.ogv' + sage: OutputVideoOgg.example().mimetype + 'video/ogg' + """ + from sage.env import SAGE_EXTCODE + filename = os.path.join(SAGE_EXTCODE, 'doctest', 'rich_output', + 'example' + cls.ext) + return cls(OutputBuffer.from_file(filename), + {'controls': True, 'loop': False}) + + def html_fragment(self, url, link_attrs=''): + r""" + Construct a HTML fragment for embedding this video + + INPUT: + + - ``url`` -- string. The URL where the data of this video can be found. + + - ``link_attrs`` -- string. Can be used to style the fallback link + which is presented to the user if the video is not supported. + + EXAMPLES:: + sage: from sage.repl.rich_output.output_catalog import OutputVideoOgg + sage: print(OutputVideoOgg.example().html_fragment + ....: ('foo', 'class="bar"').replace('><','>\n<')) + + """ + attrs = { + 'autoplay': 'autoplay', + 'controls': 'controls', + } + if self.loop: attrs['loop'] = 'loop' + attrs = ''.join(' {}="{}"'.format(k, v) + for k, v in sorted(attrs.items())) + return ('' + '

' + '' + 'Download {mimetype} video

' + ).format(url=url, + mimetype=self.mimetype, + attrs=attrs, + link_attrs=link_attrs, + ) + +class OutputVideoOgg(OutputVideoBase): + """ + Ogg video, Ogg Theora in particular + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoOgg + sage: OutputVideoOgg.example() + OutputVideoOgg container + """ + + ext = ".ogv" + mimetype = "video/ogg" + +class OutputVideoWebM(OutputVideoBase): + """ + WebM video + + The video can be encoded using VP8, VP9 or an even more recent codec. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoWebM + sage: OutputVideoWebM.example() + OutputVideoWebM container + """ + + ext = ".webm" + mimetype = "video/webm" + +class OutputVideoMp4(OutputVideoBase): + """ + MPEG 4 video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoMp4 + sage: OutputVideoMp4.example() + OutputVideoMp4 container + """ + + ext = ".mp4" + mimetype = "video/mp4" + +class OutputVideoFlash(OutputVideoBase): + """ + Flash video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoFlash + sage: OutputVideoFlash.example() + OutputVideoFlash container + """ + + ext = ".flv" + mimetype = "video/x-flv" + +class OutputVideoMatroska(OutputVideoBase): + """ + Matroska Video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoMatroska + sage: OutputVideoMatroska.example() + OutputVideoMatroska container + """ + + ext = ".mkv" + mimetype = "video/x-matroska" + +class OutputVideoAvi(OutputVideoBase): + """ + AVI video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoAvi + sage: OutputVideoAvi.example() + OutputVideoAvi container + """ + + ext = ".avi" + mimetype = "video/x-msvideo" + +class OutputVideoWmv(OutputVideoBase): + """ + Windows Media Video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoWmv + sage: OutputVideoWmv.example() + OutputVideoWmv container + """ + + ext = ".wmv" + mimetype = "video/x-ms-wmv" + +class OutputVideoQuicktime(OutputVideoBase): + """ + Quicktime video + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_catalog import OutputVideoQuicktime + sage: OutputVideoQuicktime.example() + OutputVideoQuicktime container + """ + + ext = ".mov" + mimetype = "video/quicktime" diff --git a/src/sage/repl/rich_output/pretty_print.py b/src/sage/repl/rich_output/pretty_print.py index dc8a5b15f3c..89b9db49250 100644 --- a/src/sage/repl/rich_output/pretty_print.py +++ b/src/sage/repl/rich_output/pretty_print.py @@ -227,6 +227,13 @@ def pretty_print(*args, **kwds): """ if len(args) == 1 and isinstance(args[0], (types.GeneratorType, collections.Iterator)): args = tuple(args[0]) + + # Support deprecation :trac:`18292` + if len(args) == 1: + import sage.misc.html + if sage.misc.html.WarnIfNotPrinted.skip_pretty_print(args[0]): + return + dm = get_display_manager() old_preferences_text = dm.preferences.text try: diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index 80b44109c02..f4cb9249e55 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -25,17 +25,11 @@ from principal_ideal_domain import PrincipalIdealDomain from euclidean_domain import EuclideanDomain -from commutative_algebra_element import CommutativeAlgebraElement - # Ring element base classes -from ring_element import RingElement -from commutative_ring_element import CommutativeRingElement -from integral_domain_element import IntegralDomainElement -from dedekind_domain_element import DedekindDomainElement -from principal_ideal_domain_element import PrincipalIdealDomainElement -from euclidean_domain_element import EuclideanDomainElement -from field_element import FieldElement - +from sage.structure.element import (CommutativeAlgebraElement, + RingElement, CommutativeRingElement, IntegralDomainElement, + DedekindDomainElement, PrincipalIdealDomainElement, + EuclideanDomainElement, FieldElement) # Ideals from ideal import Ideal @@ -89,6 +83,8 @@ from real_lazy import RealLazyField, RLF, ComplexLazyField, CLF +from sage.rings.real_arb import RealBallField, RBF + # Polynomial Rings and Polynomial Quotient Rings from polynomial.all import * @@ -117,6 +113,8 @@ from complex_mpc import MPComplexField +from sage.rings.complex_arb import ComplexBallField, CBF + # Power series rings from power_series_ring import PowerSeriesRing from power_series_ring_element import PowerSeries @@ -185,3 +183,6 @@ Hirzebruch_Jung_continued_fraction_list) # and deprecated continued fractions from sage.rings.contfrac import (CFF, ContinuedFractionField) + +# asymptotic ring +from asymptotic.all import * diff --git a/src/sage/rings/asymptotic/__init__.py b/src/sage/rings/asymptotic/__init__.py new file mode 100644 index 00000000000..c9fecacd721 --- /dev/null +++ b/src/sage/rings/asymptotic/__init__.py @@ -0,0 +1 @@ +import all diff --git a/src/sage/rings/asymptotic/all.py b/src/sage/rings/asymptotic/all.py new file mode 100644 index 00000000000..daf2b157307 --- /dev/null +++ b/src/sage/rings/asymptotic/all.py @@ -0,0 +1,2 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.asymptotic_ring', 'AsymptoticRing') diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py new file mode 100644 index 00000000000..a2bacd54ade --- /dev/null +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -0,0 +1,3295 @@ +r""" +Asymptotic Ring + +This module provides a ring (called :class:`AsymptoticRing`) for +computations with :wikipedia:`asymptotic expansions `. + + +.. _asymptotic_ring_definition: + +(Informal) Definition +===================== + +An asymptotic expansion is a sum such as + +.. MATH:: + + 5z^3 + 4z^2 + O(z) + +as `z \to \infty` or + +.. MATH:: + + 3x^{42}y^2 + 7x^3y^3 + O(x^2) + O(y) + +as `x` and `y` tend to `\infty`. It is a truncated series (after a +finite number of terms), which approximates a function. + +The summands of the asymptotic expansions are partially ordered. In +this module these summands are the following: + +- Exact terms `c\cdot g` with a coefficient `c` and an element `g` of + a growth group (:ref:`see below `). + +- `O`-terms `O(g)` (see :wikipedia:`Big O notation `; + also called *Bachmann--Landau notation*) for a growth group + element `g` (:ref:`again see below `). + +See +:wikipedia:`the Wikipedia article on asymptotic expansions ` +for more details. +Further examples of such elements can be found :ref:`here `. + + +.. _asymptotic_ring_growth: + +Growth Groups and Elements +-------------------------- + +The elements of a :doc:`growth group ` are equipped with +a partial order and usually contain a variable. Examples---the order +is described below these examples---are + +- elements of the form `z^q` for some integer or rational `q` + (growth groups with :ref:`description strings ` + ``z^ZZ`` or ``z^QQ``), + +- elements of the form `\log(z)^q` for some integer or rational `q` + (growth groups ``log(z)^ZZ`` or ``log(z)^QQ``), + +- elements of the form `a^z` for some + rational `a` (growth group ``QQ^z``), or + +- more sophisticated constructions like products + `x^r \cdot \log(x)^s \cdot a^y \cdot y^q` + (this corresponds to an element of the growth group + ``x^QQ * log(x)^ZZ * QQ^y * y^QQ``). + +The order in all these examples is induced by the magnitude of the +elements as `x`, `y`, or `z` (independently) tend to `\infty`. For +elements only using the variable `z` this means that `g_1 \leq g_2` if + +.. MATH:: + + \lim_{z\to\infty} \frac{g_1}{g_2} \leq 1. + +.. NOTE:: + + Asymptotic rings where the variable tend to some value distinct from + `\infty` are not yet implemented. + +To find out more about + +- growth groups, + +- on how they are created and + +- about the above used *descriptions strings* + +see the top of the module :doc:`growth group `. + + +.. WARNING:: + + As this code is experimental, a warning is thrown when an + asymptotic ring (or an associated structure) is created for the + first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(G, ZZ) + sage: R. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + + +.. _asymptotic_ring_intro: + +Introductory Examples +===================== + +We start this series of examples by defining two asymptotic rings. + + +Two Rings +--------- + +A Univariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +First, we construct the following (very simple) asymptotic ring in the variable `z`:: + + sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + +A typical element of this ring is +:: + + sage: A.an_element() + z^(3/2) + O(z^(1/2)) + +This element consists of two summands: the exact term with coefficient +`1` and growth `z^{3/2}` and the `O`-term `O(z^{1/2})`. Note that the +growth of `z^{3/2}` is larger than the growth of `z^{1/2}` as +`z\to\infty`, thus this expansion cannot be simplified (which would +be done automatically, see below). + +Elements can be constructed via the generator `z` and the function +:func:`~sage.rings.big_oh.O`, for example + +:: + + sage: 4*z^2 + O(z) + 4*z^2 + O(z) + +A Multivariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Next, we construct a more sophisticated asymptotic ring in the +variables `x` and `y` by +:: + + sage: B. = AsymptoticRing(growth_group='x^QQ * log(x)^ZZ * QQ^y * y^QQ', coefficient_ring=QQ); B + Asymptotic Ring over Rational Field + +Again, we can look at a typical (nontrivial) element:: + + sage: B.an_element() + 1/8*x^(3/2)*log(x)^3*(1/8)^y*y^(3/2) + O(x^(1/2)*log(x)*(1/2)^y*y^(1/2)) + +Again, elements can be created using the generators `x` and `y`, as well as +the function :func:`~sage.rings.big_oh.O`:: + + sage: log(x)*y/42 + O(1/2^y) + 1/42*log(x)*y + O((1/2)^y) + +Arithmetical Operations +----------------------- + +In this section we explain how to perform various arithmetical +operations with the elements of the asymptotic rings constructed +above. + + +The Ring Operations Plus and Times +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We start our calculations in the ring +:: + + sage: A + Asymptotic Ring over Integer Ring + +Of course, we can perform the usual ring operations `+` and `*`:: + + sage: z^2 + 3*z*(1-z) + -2*z^2 + 3*z + sage: (3*z + 2)^3 + 27*z^3 + 54*z^2 + 36*z + 8 + +In addition to that, special powers---our growth group ``z^QQ`` allows +the exponents to be out of `\QQ`---can also be computed:: + + sage: (z^(5/2)+z^(1/7)) * z^(-1/5) + z^(23/10) + z^(-2/35) + +The central concepts of computations with asymptotic expansions is +that the `O`-notation can be used. For example, we have +:: + + sage: z^3 + z^2 + z + O(z^2) + z^3 + O(z^2) + +where the result is simplified automatically. A more sophisticated example is +:: + + sage: (z+2*z^2+3*z^3+4*z^4) * (O(z)+z^2) + 4*z^6 + O(z^5) + + +Division +^^^^^^^^ + +The asymptotic expansions support division. For example, we can +expand `1/(z-1)` to a geometric series:: + + sage: 1 / (z-1) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + ... + z^(-20) + O(z^(-21)) + +A default precision (parameter ``default_prec`` of +:class:`AsymptoticRing`) is predefined. Thus, only the first `20` +summands are calculated. However, if we only want the first `5` exact +terms, we cut of the rest by using +:: + + sage: (1 / (z-1)).truncate(5) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +or + +:: + + sage: 1 / (z-1) + O(z^(-6)) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +Of course, we can work with more complicated expansions as well:: + + sage: (4*z+1) / (z^3+z^2+z+O(z^0)) + 4*z^(-2) - 3*z^(-3) - z^(-4) + O(z^(-5)) + +Not all elements are invertible, for instance, + +:: + + sage: 1 / O(z) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(z). + +is not invertible, since it includes `0`. + + +Powers, Expontials and Logarithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It works as simple as it can be; just use the usual operators ``^``, +``exp`` and ``log``. For example, we obtain the usual series expansion +of the logarithm +:: + + sage: -log(1-1/z) + z^(-1) + 1/2*z^(-2) + 1/3*z^(-3) + ... + O(z^(-21)) + +as `z \to \infty`. + +Similarly, we can apply the exponential function of an asymptotic expansion:: + + sage: exp(1/z) + 1 + z^(-1) + 1/2*z^(-2) + 1/6*z^(-3) + 1/24*z^(-4) + ... + O(z^(-20)) + +Arbitrary powers work as well; for example, we have +:: + + sage: (1 + 1/z + O(1/z^5))^(1 + 1/z) + 1 + z^(-1) + z^(-2) + 1/2*z^(-3) + 1/3*z^(-4) + O(z^(-5)) + + +Multivariate Arithmetic +^^^^^^^^^^^^^^^^^^^^^^^ + +Now let us move on to arithmetic in the multivariate ring + +:: + + sage: B + Asymptotic Ring over Rational Field + +.. TODO:: + + write this part + + +More Examples +============= + + +The mathematical constant e as a limit +-------------------------------------- + +The base of the natural logarithm `e` satisfies the equation + +.. MATH:: + + e = \lim_{n\to\infty} \left(1+\frac{1}{n}\right)^n + +By using asymptotic expansions, we obtain the more precise result +:: + + sage: E. = AsymptoticRing(growth_group='n^ZZ', coefficient_ring=SR, default_prec=5); E + Asymptotic Ring over Symbolic Ring + sage: (1 + 1/n)^n + e - 1/2*e*n^(-1) + 11/24*e*n^(-2) - 7/16*e*n^(-3) + 2447/5760*e*n^(-4) + O(n^(-5)) + + +Selected Technical Details +========================== + + +Coercions and Functorial Constructions +-------------------------------------- + +The :class:`AsymptoticRing` fully supports +`coercion <../../../../coercion/index.html>`_. For example, the coefficient ring is automatically extended when needed:: + + sage: A + Asymptotic Ring over Integer Ring + sage: (z + 1/2).parent() + Asymptotic Ring over Rational Field + +Here, the coefficient ring was extended to allow `1/2` as a +coefficent. Another example is +:: + + sage: C. = AsymptoticRing(growth_group='c^ZZ', coefficient_ring=ZZ['e']) + sage: C.an_element() + e^3*c^3 + O(c) + sage: C.an_element() / 7 + 1/7*e^3*c^3 + O(c) + +Here the result's coefficient ring is the newly found +:: + + sage: (C.an_element() / 7).parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + +Not only the coefficient ring can be extended, but the growth group as +well. For example, we can add/multiply elements of the asymptotic +rings ``A`` and ``C`` to get an expansion of new asymptotic ring:: + + sage: r = c*z + c/2 + O(z); r + c*z + 1/2*c + O(z) + sage: r.parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + + +Data Structures +--------------- + +The summands of an +:class:`asymptotic expansion ` are wrapped +:doc:`growth group elements `. +This wrapping is done by the +:doc:`term monoid module `. +However, inside an +:class:`asymptotic expansion ` these summands +(terms) are stored together with their growth-relationship, i.e., each +summand knows its direct predecessors and successors. As a data +structure a special poset (namely a +:mod:`mutable poset `) +is used. We can have a look at this:: + + sage: b = x^3*y + x^2*y + x*y^2 + O(x) + O(y) + sage: print b.summands.repr_full(reverse=True) + poset(x*y^2, x^3*y, x^2*y, O(x), O(y)) + +-- oo + | +-- no successors + | +-- predecessors: x*y^2, x^3*y + +-- x*y^2 + | +-- successors: oo + | +-- predecessors: O(x), O(y) + +-- x^3*y + | +-- successors: oo + | +-- predecessors: x^2*y + +-- x^2*y + | +-- successors: x^3*y + | +-- predecessors: O(x), O(y) + +-- O(x) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- O(y) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- null + | +-- successors: O(x), O(y) + | +-- no predecessors + + +Various +======= + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Classes and Methods +=================== +""" + +# ***************************************************************************** +# Copyright (C) 2015 Benjamin Hackl +# 2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.rings.ring import Algebra +from sage.structure.element import CommutativeAlgebraElement +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.superseded import experimental + +class AsymptoticExpansion(CommutativeAlgebraElement): + r""" + Class for asymptotic expansions, i.e., the elements of an + :class:`AsymptoticRing`. + + INPUT: + + - ``parent`` -- the parent of the asymptotic expansion. + + - ``summands`` -- the summands as a + :class:`~sage.data_structures.mutable_poset.MutablePoset`, which + represents the underlying structure. + + - ``simplify`` -- a boolean (default: ``True``). It controls + automatic simplification (absorption) of the asymptotic expansion. + + - ``convert`` -- a boolean (default: ``True``). If set, then the + ``summands`` are converted to the asymptotic ring (the parent of this + expansion). If not, then the summands are taken as they are. In + that case, the caller must ensure that the parent of the terms is + set correctly. + + EXAMPLES: + + There are several ways to create asymptotic expansions; usually + this is done by using the corresponding :class:`asymptotic rings `:: + + sage: R_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R_x + Asymptotic Ring over Rational Field + sage: R_y. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ); R_y + Asymptotic Ring over Integer Ring + + At this point, `x` and `y` are already asymptotic expansions:: + + sage: type(x) + + + The usual ring operations, but allowing rational exponents (growth + group ``x^QQ``) can be performed:: + + sage: x^2 + 3*(x - x^(2/5)) + x^2 + 3*x - 3*x^(2/5) + sage: (3*x^(1/3) + 2)^3 + 27*x + 54*x^(2/3) + 36*x^(1/3) + 8 + + One of the central ideas behind computing with asymptotic + expansions is that the `O`-notation (see + :wikipedia:`Big_O_notation`) can be used. For example, we have:: + + sage: (x+2*x^2+3*x^3+4*x^4) * (O(x)+x^2) + 4*x^6 + O(x^5) + + In particular, :meth:`~sage.rings.big_oh.O` can be used to + construct the asymptotic expansions. With the help of the + :meth:`summands`, we can also have a look at the inner structure + of an asymptotic expansion:: + + sage: expr1 = x + 2*x^2 + 3*x^3 + 4*x^4; expr2 = O(x) + x^2 + sage: print(expr1.summands.repr_full()) + poset(x, 2*x^2, 3*x^3, 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: x + +-- x + | +-- predecessors: null + | +-- successors: 2*x^2 + +-- 2*x^2 + | +-- predecessors: x + | +-- successors: 3*x^3 + +-- 3*x^3 + | +-- predecessors: 2*x^2 + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: 3*x^3 + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: print(expr2.summands.repr_full()) + poset(O(x), x^2) + +-- null + | +-- no predecessors + | +-- successors: O(x) + +-- O(x) + | +-- predecessors: null + | +-- successors: x^2 + +-- x^2 + | +-- predecessors: O(x) + | +-- successors: oo + +-- oo + | +-- predecessors: x^2 + | +-- no successors + sage: print((expr1 * expr2).summands.repr_full()) + poset(O(x^5), 4*x^6) + +-- null + | +-- no predecessors + | +-- successors: O(x^5) + +-- O(x^5) + | +-- predecessors: null + | +-- successors: 4*x^6 + +-- 4*x^6 + | +-- predecessors: O(x^5) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^6 + | +-- no successors + + In addition to the monomial growth elements from above, we can + also compute with logarithmic terms (simply by constructing the + appropriate growth group):: + + sage: R_log = AsymptoticRing(growth_group='log(x)^QQ', coefficient_ring=QQ) + sage: lx = R_log(log(SR.var('x'))) + sage: (O(lx) + lx^3)^4 + log(x)^12 + O(log(x)^10) + + .. SEEALSO:: + + :doc:`growth_group`, + :doc:`term_monoid`, + :mod:`~sage.data_structures.mutable_poset`. + """ + def __init__(self, parent, summands, simplify=True, convert=True): + r""" + See :class:`AsymptoticExpansion` for more information. + + TESTS:: + + sage: R_x. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R_y. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ) + sage: R_x is R_y + False + sage: ex1 = x + 2*x^2 + 3*x^3 + 4*x^4 + 5*x^5 + sage: ex2 = x + O(R_x(1)) + sage: ex1 * ex2 + 5*x^6 + O(x^5) + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) + sage: R = AsymptoticRing(G, ZZ) + sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] + sage: expr = R(lst, simplify=False); expr # indirect doctest + 4*x^4 + O(x^3) + 2*x^2 + x + sage: print expr.summands.repr_full() + poset(x, 2*x^2, O(x^3), 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: x + +-- x + | +-- predecessors: null + | +-- successors: 2*x^2 + +-- 2*x^2 + | +-- predecessors: x + | +-- successors: O(x^3) + +-- O(x^3) + | +-- predecessors: 2*x^2 + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: O(x^3) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: expr._simplify_(); expr + 4*x^4 + O(x^3) + sage: print expr.summands.repr_full() + poset(O(x^3), 4*x^4) + +-- null + | +-- no predecessors + | +-- successors: O(x^3) + +-- O(x^3) + | +-- predecessors: null + | +-- successors: 4*x^4 + +-- 4*x^4 + | +-- predecessors: O(x^3) + | +-- successors: oo + +-- oo + | +-- predecessors: 4*x^4 + | +-- no successors + sage: R(lst, simplify=True) # indirect doctest + 4*x^4 + O(x^3) + + :: + + sage: R. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ) + sage: e = R(x^2 + O(x)) + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticExpansion + sage: S = AsymptoticRing(growth_group='x^QQ', coefficient_ring=ZZ) + sage: for s in AsymptoticExpansion(S, e.summands).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Integer Ring + Exact Term Monoid x^QQ with coefficients in Integer Ring + sage: for s in AsymptoticExpansion(S, e.summands, + ....: convert=False).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Rational Field + Exact Term Monoid x^QQ with coefficients in Rational Field + + :: + + sage: AsymptoticExpansion(S, R(1/2).summands) + Traceback (most recent call last): + ... + ValueError: Cannot include 1/2 with parent + Exact Term Monoid x^QQ with coefficients in Rational Field in + Asymptotic Ring over Integer Ring + > *previous* ValueError: 1/2 is not a coefficient in + Exact Term Monoid x^QQ with coefficients in Integer Ring. + """ + super(AsymptoticExpansion, self).__init__(parent=parent) + + from sage.data_structures.mutable_poset import MutablePoset + if not isinstance(summands, MutablePoset): + raise TypeError('Summands %s are not in a mutable poset as expected ' + 'when creating an element of %s.' % (summands, parent)) + + if convert: + from misc import combine_exceptions + from term_monoid import TermMonoid + def convert_terms(element): + T = TermMonoid(term=element.parent(), asymptotic_ring=parent) + try: + return T(element) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Cannot include %s with parent %s in %s' % + (element, element.parent(), parent)), e) + new_summands = summands.copy() + new_summands.map(convert_terms, topological=True, reverse=True) + self._summands_ = new_summands + else: + self._summands_ = summands + + if simplify: + self._simplify_() + + + @property + def summands(self): + r""" + The summands of this asymptotic expansion stored in the + underlying data structure (a + :class:`~sage.data_structures.mutable_poset.MutablePoset`). + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr = 7*x^12 + x^5 + O(x^3) + sage: expr.summands + poset(O(x^3), x^5, 7*x^12) + + .. SEEALSO:: + + :class:`sage.data_structures.mutable_poset.MutablePoset` + """ + return self._summands_ + + + def __hash__(self): + r""" + A hash value for this element. + + .. WARNING:: + + This hash value uses the string representation and might not be + always right. + + TESTS:: + + sage: R_log = AsymptoticRing(growth_group='log(x)^QQ', coefficient_ring=QQ) + sage: lx = R_log(log(SR.var('x'))) + sage: elt = (O(lx) + lx^3)^4 + sage: hash(elt) # random + -4395085054568712393 + """ + return hash(str(self)) + + + def __nonzero__(self): + r""" + Return whether this asymptotic expansion is not identically zero. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: bool(R(0)) # indirect doctest + False + sage: bool(x) # indirect doctest + True + sage: bool(7*x^12 + x^5 + O(x^3)) # indirect doctest + True + """ + return bool(self._summands_) + + + def __eq__(self, other): + r""" + Return whether this asymptotic expansion is equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: (1 + 2*x + 3*x^2) == (3*x^2 + 2*x + 1) # indirect doctest + True + sage: O(x) == O(x) + False + + TESTS:: + + sage: x == None + False + + :: + + sage: x == 'x' + False + """ + if other is None: + return False + try: + return not bool(self - other) + except (TypeError, ValueError): + return False + + + def __ne__(self, other): + r""" + Return whether this asymptotic expansion is not equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: (1 + 2*x + 3*x^2) != (3*x^2 + 2*x + 1) # indirect doctest + False + sage: O(x) != O(x) + True + + TESTS:: + + sage: x != None + True + """ + return not self == other + + + def has_same_summands(self, other): + r""" + Return whether this asymptotic expansion and ``other`` have the + same summands. + + INPUT: + + - ``other`` -- an asymptotic expansion. + + OUTPUT: + + A boolean. + + .. NOTE:: + + While for example ``O(x) == O(x)`` yields ``False``, + these expansions *do* have the same summands and this method + returns ``True``. + + Moreover, this method uses the coercion model in order to + find a common parent for this asymptotic expansion and + ``other``. + + EXAMPLES:: + + sage: R_ZZ. = AsymptoticRing('x^ZZ', ZZ) + sage: R_QQ. = AsymptoticRing('x^ZZ', QQ) + sage: sum(x_ZZ^k for k in range(5)) == sum(x_QQ^k for k in range(5)) # indirect doctest + True + sage: O(x_ZZ) == O(x_QQ) + False + + TESTS:: + + sage: x_ZZ.has_same_summands(None) + False + """ + if other is None: + return False + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._has_same_summands_(other) + + from sage.structure.element import get_coercion_model + return get_coercion_model().bin_op(self, other, + lambda self, other: + self._has_same_summands_(other)) + + + def _has_same_summands_(self, other): + r""" + Return whether this :class:`AsymptoticExpansion` has the same + summands as ``other``. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method compares two :class:`AsymptoticExpansion` + with the same parent. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: O(x).has_same_summands(O(x)) + True + sage: (1 + x + 2*x^2).has_same_summands(2*x^2 + O(x)) # indirect doctest + False + """ + if len(self.summands) != len(other.summands): + return False + from itertools import izip + return all(s == o for s, o in + izip(self.summands.elements_topological(), + other.summands.elements_topological())) + + + def _simplify_(self): + r""" + Simplify this asymptotic expansion. + + INPUT: + + Nothing. + + OUTPUT: + + Nothing, but modifies this asymptotic expansion. + + .. NOTE:: + + This method is usually called during initialization of + this asymptotic expansion. + + .. NOTE:: + + This asymptotic expansion is simplified by letting + `O`-terms that are included in this expansion absorb all + terms with smaller growth. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) + sage: R = AsymptoticRing(G, ZZ) + sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] + sage: expr = R(lst, simplify=False); expr # indirect doctest + 4*x^4 + O(x^3) + 2*x^2 + x + sage: expr._simplify_(); expr + 4*x^4 + O(x^3) + sage: R(lst) # indirect doctest + 4*x^4 + O(x^3) + """ + self._summands_.merge(reverse=True) + + + def _repr_(self): + r""" + A representation string for this asymptotic expansion. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: (5*x^2+12*x) * (x^3+O(x)) # indirect doctest + 5*x^5 + 12*x^4 + O(x^3) + sage: (5*x^2-12*x) * (x^3+O(x)) # indirect doctest + 5*x^5 - 12*x^4 + O(x^3) + """ + s = ' + '.join(repr(elem) for elem in + self.summands.elements_topological(reverse=True)) + s = s.replace('+ -', '- ') + if not s: + return '0' + return s + + + def _add_(self, other): + r""" + Add ``other`` to this asymptotic expansion. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The sum as an :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr1 = x^123; expr2 = x^321 + sage: expr1._add_(expr2) + x^321 + x^123 + sage: expr1 + expr2 # indirect doctest + x^321 + x^123 + + If an `O`-term is added to an asymptotic expansion, then + the `O`-term absorbs everything it can:: + + sage: x^123 + x^321 + O(x^555) # indirect doctest + O(x^555) + + TESTS:: + + sage: x + O(x) + O(x) + sage: O(x) + x + O(x) + """ + return self.parent()(self.summands.union(other.summands), + simplify=True, convert=False) + + + def _sub_(self, other): + r""" + Subtract ``other`` from this asymptotic expansion. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The difference as an :class:`AsymptoticExpansion`. + + .. NOTE:: + + Subtraction of two asymptotic expansions is implemented + by means of addition: `e_1 - e_2 = e_1 + (-1)\cdot e_2`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: expr1 = x^123; expr2 = x^321 + sage: expr1 - expr2 # indirect doctest + -x^321 + x^123 + sage: O(x) - O(x) + O(x) + """ + return self + self.parent().coefficient_ring(-1)*other + + + def _mul_term_(self, term): + r""" + Helper method: multiply this asymptotic expansion by the + asymptotic term ``term``. + + INPUT: + + - ``term`` -- an asymptotic term (see + :doc:`term_monoid`). + + OUTPUT: + + The product as an :class:`AsymptoticExpansion`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: T = OTermMonoid(R.growth_group, ZZ) + sage: expr = 10*x^2 + O(x) + sage: t = T(R.growth_group.gen()) + sage: expr._mul_term_(t) + O(x^3) + """ + from term_monoid import ExactTerm + simplify = not isinstance(term, ExactTerm) + return self.parent()(self.summands.mapped(lambda element: term * element), + simplify=simplify, convert=False) + + + def _mul_(self, other): + r""" + Multiply this asymptotic expansion by another asymptotic expansion ``other``. + + INPUT: + + - ``other`` -- an :class:`AsymptoticExpansion`. + + OUTPUT: + + The product as an :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: ex1 = 5*x^12 + sage: ex2 = x^3 + O(x) + sage: ex1 * ex2 # indirect doctest + 5*x^15 + O(x^13) + + .. TODO:: + + The current implementation is the standard long + multiplication. More efficient variants like Karatsuba + multiplication, or methods that exploit the structure + of the underlying poset shall be implemented at a later + point. + """ + return sum(self._mul_term_(term_other) for + term_other in other.summands.elements()) + + + def _rmul_(self, other): + r""" + Multiply this asymptotic expansion by an element ``other`` of its + coefficient ring. + + INPUT: + + - ``other`` -- an element of the coefficient ring. + + OUTPUT: + + An :class:`AsymptoticExpansion`. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='QQ^a * a^QQ * log(a)^QQ', coefficient_ring=ZZ) + sage: 2*a # indirect doctest + 2*a + """ + if other.is_zero(): + return self.parent().zero() + + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self.parent()) + e = E(self.parent().growth_group.one(), coefficient=other) + return self._mul_term_(e) + + + _lmul_ = _rmul_ + + + def _div_(self, other): + r""" + Divide this element through ``other``. + + INPUT: + + - ``other`` -- an asymptotic expansion. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ, default_prec=5) + sage: 1/x^42 + x^(-42) + sage: (1 + 4*x) / (x + 2*x^2) + 2*x^(-1) - 1/2*x^(-2) + 1/4*x^(-3) - 1/8*x^(-4) + 1/16*x^(-5) + O(x^(-6)) + sage: x / O(x) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(x). + """ + return self * ~other + + + def __invert__(self, precision=None): + r""" + Return the multiplicative inverse of this element. + + INPUT: + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. WARNING:: + + Due to truncation of infinite expansions, the element + returned by this method might not fulfill + ``el * ~el == 1``. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ, default_prec=4) + sage: ~x + x^(-1) + sage: ~(x^42) + x^(-42) + sage: ex = ~(1 + x); ex + x^(-1) - x^(-2) + x^(-3) - x^(-4) + O(x^(-5)) + sage: ex * (1+x) + 1 + O(x^(-4)) + sage: ~(1 + O(1/x)) + 1 + O(x^(-1)) + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='a^ZZ', coefficient_ring=ZZ) + sage: (1 / a).parent() + Asymptotic Ring over Rational Field + sage: (a / 2).parent() + Asymptotic Ring over Rational Field + + :: + + sage: ~A(0) + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero in 0. + + :: + + sage: B. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: ~(s + t) + Traceback (most recent call last): + ... + ValueError: Expansion s + t cannot be inverted since there are + several maximal elements s, t. + """ + if not self.summands: + raise ZeroDivisionError('Division by zero in %s.' % (self,)) + + elif len(self.summands) == 1: + element = next(self.summands.elements()) + return self.parent()._create_element_in_extension_( + ~element, element.parent()) + + max_elem = tuple(self.summands.maximal_elements()) + if len(max_elem) != 1: + raise ValueError('Expansion %s cannot be inverted since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) + max_elem = max_elem[0] + + imax_elem = ~max_elem + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = self.parent()._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) + + expanding = True + result = one + while expanding: + new_result = (geom*result + one).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + return result._mul_term_(imax_elem) + + + invert = __invert__ + + + def truncate(self, precision=None): + r""" + Truncate this asymptotic expansion. + + INPUT: + + - ``precision`` -- a positive integer or ``None``. Number of + summands that are kept. If ``None`` (default value) is + given, then ``default_prec`` from the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + For example, truncating an asymptotic expansion with + ``precision=20`` does not yield an expansion with exactly 20 + summands! Rather than that, it keeps the 20 summands + with the largest growth, and adds appropriate + `O`-Terms. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: ex = sum(x^k for k in range(5)); ex + x^4 + x^3 + x^2 + x + 1 + sage: ex.truncate(precision=2) + x^4 + x^3 + O(x^2) + sage: ex.truncate(precision=0) + O(x^4) + sage: ex.truncate() + x^4 + x^3 + x^2 + x + 1 + """ + if precision is None: + precision = self.parent().default_prec + + if len(self.summands) <= precision: + return self + + summands = self.summands.copy() + from term_monoid import TermMonoid + def convert_terms(element): + if convert_terms.count < precision: + convert_terms.count += 1 + return element + T = TermMonoid(term='O', asymptotic_ring=self.parent()) + return T(element) + convert_terms.count = 0 + summands.map(convert_terms, topological=True, reverse=True) + return self.parent()(summands, simplify=True, convert=False) + + + def __pow__(self, exponent, precision=None): + r""" + Calculate the power of this asymptotic expansion to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + TESTS:: + + sage: R_QQ. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ) + sage: x^(1/7) + x^(1/7) + sage: R_ZZ. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ) + sage: y^(1/7) + y^(1/7) + sage: (y^(1/7)).parent() + Asymptotic Ring over Rational Field + sage: (x^(1/2) + O(x^0))^15 + x^(15/2) + O(x^7) + sage: (y^2 + O(y))^(1/2) # not tested, see #19316 + y + O(1) + sage: (y^2 + O(y))^(-2) + y^(-4) + O(y^(-5)) + + :: + + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ) + sage: (z^2 + O(z))^(1/2) + z + O(1) + + :: + + sage: A. = AsymptoticRing('QQ^x * x^SR * log(x)^ZZ', QQ) + sage: x * 2^x + 2^x*x + sage: 5^x * 2^x + 10^x + sage: 2^log(x) + x^(log(2)) + sage: 2^(x + 1/x) + 2^x + log(2)*2^x*x^(-1) + 1/2*log(2)^2*2^x*x^(-2) + ... + O(2^x*x^(-20)) + sage: _.parent() + Asymptotic Ring over Symbolic Ring + + See :trac:`19110`:: + + sage: O(x)^(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(x) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ, default_prec=5) + sage: z^(1+1/z) + z + log(z) + 1/2*z^(-1)*log(z)^2 + 1/6*z^(-2)*log(z)^3 + + 1/24*z^(-3)*log(z)^4 + O(z^(-4)*log(z)^5) + + :: + + sage: B(0)^(-7) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take 0 to the negative exponent -7. + sage: B(0)^SR.var('a') + Traceback (most recent call last): + ... + NotImplementedError: Taking 0 to the exponent a not implemented. + + :: + + sage: C. = AsymptoticRing(growth_group='s^QQ * t^QQ', coefficient_ring=QQ) + sage: (s + t)^s + Traceback (most recent call last): + ... + ValueError: Cannot take s + t to the exponent s. + > *previous* ValueError: log(s + t) cannot be constructed since + there are several maximal elements s, t. + """ + if not self.summands: + if exponent == 0: + return self.parent().one() + elif exponent > 0: + return self.parent().zero() + elif exponent < 0: + raise ZeroDivisionError('Cannot take %s to the negative exponent %s.' % + (self, exponent)) + else: + raise NotImplementedError('Taking %s to the exponent %s not implemented.' % + (self, exponent)) + + elif len(self.summands) == 1: + element = next(self.summands.elements()) + if isinstance(exponent, AsymptoticExpansion) and element.is_constant(): + return exponent.rpow(base=element.coefficient, precision=precision) + try: + return self.parent()._create_element_in_extension_( + element ** exponent, element.parent()) + except (ArithmeticError, TypeError, ValueError): + if not isinstance(exponent, AsymptoticExpansion): + raise + + from sage.rings.integer_ring import ZZ + try: + exponent = ZZ(exponent) + except (TypeError, ValueError): + pass + else: + return super(AsymptoticExpansion, self).__pow__(exponent) + + try: + return (exponent * self.log(precision=precision)).exp(precision=precision) + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + + + pow = __pow__ + + + def O(self): + r""" + Convert all terms in this asymptotic expansion to `O`-terms. + + INPUT: + + Nothing. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: O(x) + O(x) + sage: type(O(x)) + + sage: expr = 42*x^42 + x^10 + O(x^2); expr + 42*x^42 + x^10 + O(x^2) + sage: expr.O() + O(x^42) + sage: O(AR(0)) + 0 + sage: (2*x).O() + O(x) + + .. SEEALSO:: + + :func:`sage.rings.power_series_ring.PowerSeriesRing`, + :func:`sage.rings.laurent_series_ring.LaurentSeriesRing`. + """ + return sum(self.parent().create_summand('O', growth=element) + for element in self.summands.maximal_elements()) + + + def log(self, base=None, precision=None): + r""" + The logarithm of this asymptotic expansion. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + Computing the logarithm of an asymptotic expansion + is possible if and only if there is exactly one maximal + summand in the expansion. + + ALGORITHM: + + If the expansion has more than one summand, + the asymptotic expansion for `\log(1+t)` as `t` tends to `0` + is used. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ * log(x)^ZZ', coefficient_ring=QQ) + sage: log(x) + log(x) + sage: log(x^2) + 2*log(x) + sage: log(x-1) + log(x) - x^(-1) - 1/2*x^(-2) - 1/3*x^(-3) - ... + O(x^(-21)) + + TESTS:: + + sage: log(R(1)) + 0 + sage: log(R(0)) + Traceback (most recent call last): + ... + ArithmeticError: Cannot compute log(0) in + Asymptotic Ring over Rational Field. + sage: C. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: log(s + t) + Traceback (most recent call last): + ... + ValueError: log(s + t) cannot be constructed since there are + several maximal elements s, t. + """ + P = self.parent() + + if not self.summands: + raise ArithmeticError('Cannot compute log(0) in %s.' % (self.parent(),)) + + elif len(self.summands) == 1: + if self.is_one(): + return P.zero() + element = next(self.summands.elements()) + return sum(P._create_element_in_extension_(l, element.parent()) + for l in element.log_term(base=base)) + + max_elem = tuple(self.summands.maximal_elements()) + if len(max_elem) != 1: + raise ValueError('log(%s) cannot be constructed since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) + max_elem = max_elem[0] + + imax_elem = ~max_elem + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = P._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) + + from sage.rings.integer_ring import ZZ + expanding = True + result = -geom + geom_k = geom + k = ZZ(1) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result - geom_k * ~k).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + result += new_self.parent()(max_elem).log() + if base: + from sage.functions.log import log + result = result / log(base) + return result + + + def is_little_o_of_one(self): + r""" + Return whether this expansion is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ * log(x)^ZZ', QQ) + sage: (x^4 * log(x)^(-2) + x^(-4) * log(x)^2).is_little_o_of_one() + False + sage: (x^(-1) * log(x)^1234 + x^(-2) + O(x^(-3))).is_little_o_of_one() + True + sage: (log(x) - log(x-1)).is_little_o_of_one() + True + + :: + + sage: A. = AsymptoticRing('x^QQ * y^QQ * log(y)^ZZ', QQ) + sage: (x^(-1/16) * y^32 + x^32 * y^(-1/16)).is_little_o_of_one() + False + sage: (x^(-1) * y^(-3) + x^(-3) * y^(-1)).is_little_o_of_one() + True + sage: (x^(-1) * y / log(y)).is_little_o_of_one() + False + sage: (log(y-1)/log(y) - 1).is_little_o_of_one() + True + """ + return all(term.is_little_o_of_one() for term in self.summands.maximal_elements()) + + + def rpow(self, base, precision=None): + r""" + Return the power of ``base`` to this asymptotic expansion. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ', QQ) + sage: (1/x).rpow('e', precision=5) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + 1/24*x^(-4) + O(x^(-5)) + + TESTS:: + + sage: x.rpow(SR.var('y')) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct y^x in Growth Group x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ' and 'Growth Group SR^x' + """ + if isinstance(base, AsymptoticExpansion): + return base.__pow__(self, precision=precision) + + P = self.parent() + + # first: remove terms from a copy of this term such that a + # term in o(1) remains + + expr_o = self.summands.copy() + large_terms = [] + for term in self.summands.elements_topological(): + if not term.is_little_o_of_one(): + large_terms.append(term) + expr_o.remove(term.growth) + + expr_o = P(expr_o) + + # next: try to take the exponential function of the large elements + + try: + large_result = P.prod( + P._create_element_in_extension_(term.rpow(base), + term.parent()) + for term in large_terms) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot construct the power of %s to the ' + 'exponent %s in %s.' % + (base, self, self.parent())), e) + + # then: expand expr_o + + if not expr_o: + return large_result + + + if base == 'e': + geom = expr_o + else: + from sage.functions.log import log + geom = expr_o * log(base) + P = geom.parent() + + expanding = True + result = P.one() + geom_k = P.one() + from sage.rings.integer_ring import ZZ + k = ZZ(0) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result + geom_k / k.factorial()).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + return result * large_result + + + def exp(self, precision=None): + r""" + Return the exponential of (i.e., the power of `e` to) this asymptotic expansion. + + INPUT: + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + The exponential function of this expansion can only be + computed exactly if the respective growth element can be + constructed in the underlying growth group. + + ALGORITHM: + + If the corresponding growth can be constructed, return + the exact exponential function. Otherwise, if this term + is `o(1)`, try to expand the series and truncate + according to the given precision. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^ZZ * log(x)^ZZ', SR) + sage: exp(x) + e^x + sage: exp(2*x) + (e^x)^2 + sage: exp(x + log(x)) + e^x*x + + :: + + sage: (x^(-1)).exp(precision=7) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + ... + O(x^(-7)) + + TESTS:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^QQ * log(x)^QQ', SR) + sage: exp(log(x)) + x + sage: log(exp(x)) + x + + :: + + sage: exp(x+1) + e*e^x + """ + return self.rpow('e', precision=precision) + + + def substitute(self, rules=None, domain=None, **kwds): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + + - ``kwds`` -- keyword arguments will be added to the + substitution ``rules``. + + - ``domain`` -- (default: ``None``) a parent. The neutral + elements `0` and `1` (rules for the keys ``'_zero_'`` and + ``'_one_'``, see note box below) are taken out of this + domain. If ``None``, then this is determined automatically. + + OUTPUT: + + An object. + + .. NOTE:: + + The neutral element of the asymptotic ring is replaced by + the value to the key ``'_zero_'``; the neutral element of + the growth group is replaced by the value to the key + ``'_one_'``. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='(e^x)^QQ * x^ZZ * log(x)^ZZ', coefficient_ring=QQ, default_prec=5) + + :: + + sage: (e^x * x^2 + log(x)).subs(x=SR('s')) + s^2*e^s + log(s) + sage: _.parent() + Symbolic Ring + + :: + + sage: (x^3 + x + log(x)).subs(x=x+5).truncate(5) + x^3 + 15*x^2 + 76*x + log(x) + 130 + O(x^(-1)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field + + :: + + sage: (e^x * x^2 + log(x)).subs(x=2*x) + 4*(e^x)^2*x^2 + log(x) + log(2) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^QQ * log(x)^QQ> over Symbolic Ring + + :: + + sage: (x^2 + log(x)).subs(x=4*x+2).truncate(5) + 16*x^2 + 16*x + log(x) + log(4) + 4 + 1/2*x^(-1) + O(x^(-2)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Symbolic Ring + + :: + + sage: (e^x * x^2 + log(x)).subs(x=RIF(pi)) + 229.534211738584? + sage: _.parent() + Real Interval Field with 53 bits of precision + + .. SEEALSO:: + + :meth:`sage.symbolic.expression.Expression.subs` + + TESTS:: + + sage: x.subs({'y': -1}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute y in x since it is not a generator of + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field. + sage: B. = AsymptoticRing(growth_group='u^QQ * v^QQ * w^QQ', coefficient_ring=QQ) + sage: (1/u).subs({'u': 0}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + sage: (1/u).subs({'u': 0, 'v': SR.var('v')}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0, v: v} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + + :: + + sage: u.subs({u: 0, 'v': SR.var('v')}) + 0 + sage: v.subs({u: 0, 'v': SR.var('v')}) + v + sage: _.parent() + Symbolic Ring + + :: + + sage: u.subs({SR.var('u'): -1}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute u in u since it is neither an + asymptotic expansion nor a string + (but a ). + + :: + + sage: u.subs({u: 1, 'u': 1}) + 1 + sage: u.subs({u: 1}, u=1) + 1 + sage: u.subs({u: 1, 'u': 2}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. + sage: u.subs({u: 1}, u=3) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. + """ + # check if nothing to do + if not rules and not kwds: + return self + + # init and process keyword arguments + gens = self.parent().gens() + locals = kwds or dict() + + # update with rules + if isinstance(rules, dict): + for k, v in rules.iteritems(): + if not isinstance(k, str) and k not in gens: + raise TypeError('Cannot substitute %s in %s ' + 'since it is neither an ' + 'asymptotic expansion ' + 'nor a string (but a %s).' % + (k, self, type(k))) + k = str(k) + if k in locals and locals[k] != v: + raise ValueError('Cannot substitute in %s: ' + 'duplicate key %s.' % (self, k)) + locals[k] = v + elif rules is not None: + raise TypeError('Substitution rules %s have to be a dictionary.' % + (rules,)) + + # fill up missing rules + for g in gens: + locals.setdefault(str(g), g) + + # check if all keys are generators + gens_str = tuple(str(g) for g in gens) + for k in locals: + if str(k) not in gens_str: + raise ValueError('Cannot substitute %s in %s ' + 'since it is not a generator of %s.' % + (k, self, self.parent())) + + # determine 0 and 1 + if domain is None and \ + ('_zero_' not in locals or '_one_' not in locals): + P = self.parent() + for g in gens: + G = locals[str(g)].parent() + if G is not P: + domain = G + break + else: + domain = P + locals.setdefault('_zero_', domain.zero()) + locals.setdefault('_one_', domain.one()) + + # do the actual substitution + try: + return self._substitute_(locals) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + rules = '{' + ', '.join( + '%s: %s' % (k, v) + for k, v in sorted(locals.iteritems(), + key=lambda k: str(k[0])) + if not k.startswith('_') and + not any(k == str(g) and v is g for g in gens)) + '}' + raise combine_exceptions( + TypeError('Cannot apply the substitution rules %s on %s ' + 'in %s.' % (rules, self, self.parent())), e) + + + subs = substitute + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the asymptotic ring is replaced by the value + to key ``'_zero_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ) + sage: z._substitute_({'z': SR.var('a')}) + a + sage: _.parent() + Symbolic Ring + sage: A(0)._substitute_({'_zero_': 'zero'}) + 'zero' + sage: (1/z)._substitute_({'z': 4}) + 1/4 + sage: _.parent() + Rational Field + sage: (1/z)._substitute_({'z': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in z^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Exact Term Monoid z^QQ with coefficients in Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Growth Group z^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + """ + if not self.summands: + return rules['_zero_'] + from sage.symbolic.operators import add_vararg + try: + return add_vararg( + *tuple(s._substitute_(rules) + for s in self.summands.elements_topological())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + def symbolic_expression(self, R=None): + r""" + Return this asymptotic expansion as a symbolic expression. + + INPUT: + + - ``R`` -- (a subring of) the symbolic ring or ``None``. + The output is will be an element of ``R``. If ``None``, + then the symbolic ring is used. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^QQ * QQ^z * z^QQ', coefficient_ring=QQ) + sage: SR(A.an_element()) # indirect doctest + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + + TESTS:: + + sage: a = A.an_element(); a + 1/8*x^3*y^(3/2)*log(y)^(3/2)*(1/8)^z*z^(3/2) + + O(x*y^(1/2)*log(y)^(1/2)*(1/2)^z*z^(1/2)) + sage: a.symbolic_expression() + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + sage: _.parent() + Symbolic Ring + + :: + + sage: from sage.symbolic.ring import SymbolicRing + sage: class MySymbolicRing(SymbolicRing): + ....: pass + sage: mySR = MySymbolicRing() + sage: a.symbolic_expression(mySR).parent() is mySR + True + """ + if R is None: + from sage.symbolic.ring import SR + R = SR + + return self.substitute(dict((g, R(R.var(str(g)))) + for g in self.parent().gens()), + domain=R) + + + _symbolic_ = symbolic_expression # will be used by SR._element_constructor_ + + +class AsymptoticRing(Algebra, UniqueRepresentation): + r""" + A ring consisting of :class:`asymptotic expansions `. + + INPUT: + + - ``growth_group`` -- either a partially ordered group (see + :doc:`growth_group`) or a string + describing such a growth group (see + :class:`~sage.rings.asymptotic.growth_group.GrowthGroupFactory`). + + - ``coefficient_ring`` -- the ring which contains the + coefficients of the expansions. + + - ``default_prec`` -- a positive integer. This is the number of + summands that are kept before truncating an infinite series. + + - ``category`` -- the category of the parent can be specified + in order to broaden the base structure. It has to be a + subcategory of ``Category of rings``. This is also the default + category if ``None`` is specified. + + EXAMPLES: + + We begin with the construction of an asymptotic ring in various + ways. First, we simply pass a string specifying the underlying + growth group:: + + sage: R1_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R1_x + Asymptotic Ring over Rational Field + sage: x + x + + This is equivalent to the following code, which explicitly + specifies the underlying growth group:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_QQ = GrowthGroup('x^QQ') + sage: R2_x. = AsymptoticRing(growth_group=G_QQ, coefficient_ring=QQ); R2_x + Asymptotic Ring over Rational Field + + Of course, the coefficient ring of the asymptotic ring and the + base ring of the underlying growth group do not need to + coincide:: + + sage: R_ZZ_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=ZZ); R_ZZ_x + Asymptotic Ring over Integer Ring + + Note, we can also create and use logarithmic growth groups:: + + sage: R_log = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=QQ); R_log + Asymptotic Ring over Rational Field + + Other growth groups are available. See :doc:`asymptotic_ring` for + more examples. + + Below there are some technical details. + + According to the conventions for parents, uniqueness is ensured:: + + sage: R1_x is R2_x + True + + Furthermore, the coercion framework is also involved. Coercion + between two asymptotic rings is possible (given that the + underlying growth groups and coefficient rings are chosen + appropriately):: + + sage: R1_x.has_coerce_map_from(R_ZZ_x) + True + + Additionally, for the sake of convenience, the coefficient ring + also coerces into the asymptotic ring (representing constant + quantities):: + + sage: R1_x.has_coerce_map_from(QQ) + True + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRing as AR_class + sage: class AR(AR_class): + ....: class Element(AR_class.Element): + ....: __eq__ = AR_class.Element.has_same_summands + sage: A = AR(growth_group='z^QQ', coefficient_ring=QQ) + sage: from itertools import islice + sage: TestSuite(A).run( # not tested # long time # see #19424 + ....: verbose=True, + ....: elements=tuple(islice(A.some_elements(), 10)), + ....: skip=('_test_some_elements', # to many elements + ....: '_test_distributivity')) # due to cancellations: O(z) != O(z^2) + """ + + # enable the category framework for elements + Element = AsymptoticExpansion + + + @staticmethod + def __classcall__(cls, growth_group=None, coefficient_ring=None, + names=None, category=None, default_prec=None): + r""" + Normalizes the input in order to ensure a unique + representation of the parent. + + For more information see :class:`AsymptoticRing`. + + EXAMPLES: + + ``__classcall__`` unifies the input to the constructor of + :class:`AsymptoticRing` such that the instances generated + are unique. Also, this enables the use of the generation + framework:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: MG = GrowthGroup('x^ZZ') + sage: AR1 = AsymptoticRing(growth_group=MG, coefficient_ring=ZZ) + sage: AR2. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR1 is AR2 + True + + The bracket notation can only be used if the growth group + has a generator:: + + sage: AR. = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Growth Group log(x)^ZZ does not provide any + generators but name 'lx' given. + + The names of the generators have to agree with the names used in + the growth group except for univariate rings:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: icecream + x + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'y', 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'a', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Name 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'y', 'z' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + + TESTS:: + + sage: AsymptoticRing(growth_group=None, coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Growth group not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=None) + Traceback (most recent call last): + ... + ValueError: Coefficient ring not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring='icecream') + Traceback (most recent call last): + ... + ValueError: icecream is not a ring. Cannot continue. + """ + from sage.categories.sets_cat import Sets + from sage.categories.rings import Rings + + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + + if isinstance(growth_group, str): + from growth_group import GrowthGroup + growth_group = GrowthGroup(growth_group) + + if growth_group is None: + raise ValueError('Growth group not specified. Cannot continue.') + + if coefficient_ring is None: + raise ValueError('Coefficient ring not specified. Cannot continue.') + if coefficient_ring not in Rings(): + raise ValueError('%s is not a ring. Cannot continue.' % (coefficient_ring,)) + + strgens = tuple(str(g) for g in growth_group.gens_monomial()) + def format_names(N): + return ('s ' if len(N) != 1 else ' ') + ', '.join("'%s'" % n for n in N) + if names and not strgens: + raise ValueError('%s does not provide any generators but name%s given.' % + (growth_group, format_names(names))) + elif names is not None and len(names) == 1 and len(strgens) == 1: + pass + elif names is not None and names != strgens: + raise ValueError('Name%s do not coincide with generator%s of %s.' % + (format_names(names), format_names(strgens), growth_group)) + + if category is None: + from sage.categories.commutative_algebras import CommutativeAlgebras + from sage.categories.rings import Rings + category = CommutativeAlgebras(Rings()) + + if default_prec is None: + from sage.misc.defaults import series_precision + default_prec = series_precision() + + return super(AsymptoticRing, + cls).__classcall__(cls, growth_group, coefficient_ring, + category=category, + default_prec=default_prec) + + + @experimental(trac_number=17601) + def __init__(self, growth_group, coefficient_ring, category, default_prec): + r""" + See :class:`AsymptoticRing` for more information. + + TESTS:: + + sage: R1 = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); R1 + Asymptotic Ring over Integer Ring + sage: R2. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R2 + Asymptotic Ring over Rational Field + sage: R1 is R2 + False + + :: + + sage: R3 = AsymptoticRing('x^ZZ') + Traceback (most recent call last): + ... + ValueError: Coefficient ring not specified. Cannot continue. + """ + self._coefficient_ring_ = coefficient_ring + self._growth_group_ = growth_group + self._default_prec_ = default_prec + super(AsymptoticRing, self).__init__(base_ring=coefficient_ring, + category=category) + + + @property + def growth_group(self): + r""" + The growth group of this asymptotic ring. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.growth_group + Growth Group x^ZZ + + .. SEEALSO:: + + :doc:`growth_group` + """ + return self._growth_group_ + + + @property + def coefficient_ring(self): + r""" + The coefficient ring of this asymptotic ring. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.coefficient_ring + Integer Ring + """ + return self._coefficient_ring_ + + + @property + def default_prec(self): + r""" + The default precision of this asymptotic ring. + + This is the parameter used to determine how many summands + are kept before truncating an infinite series (which occur + when inverting asymptotic expansions). + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.default_prec + 20 + sage: AR = AsymptoticRing('x^ZZ', ZZ, default_prec=123) + sage: AR.default_prec + 123 + """ + return self._default_prec_ + + + def change_parameter(self, **kwds): + r""" + Return an asymptotic ring with a change in one or more of the given parameters. + + INPUT: + + - ``growth_group`` -- (default: ``None``) the new growth group. + + - ``coefficient_ring`` -- (default: ``None``) the new coefficient ring. + + - ``category`` -- (default: ``None``) the new category. + + - ``default_prec`` -- (default: ``None``) the new default precision. + + OUTPUT: + + An asymptotic ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: A.change_parameter(coefficient_ring=QQ) + Asymptotic Ring over Rational Field + + TESTS:: + + sage: A.change_parameter(coefficient_ring=ZZ) is A + True + """ + parameters = ('growth_group', 'coefficient_ring', 'default_prec') + values = dict() + for parameter in parameters: + values[parameter] = kwds.get(parameter, getattr(self, parameter)) + values['category'] = self.category() + if isinstance(values['growth_group'], str): + from growth_group import GrowthGroup + values['growth_group'] = GrowthGroup(values['growth_group']) + if all(values[parameter] is getattr(self, parameter) + for parameter in parameters) and values['category'] is self.category(): + return self + from misc import underlying_class + return underlying_class(self)(**values) + + + @staticmethod + def _create_empty_summands_(): + r""" + Create an empty data structure suitable for storing and working + with summands. + + INPUT: + + Nothing. + + OUTPUT: + + A :class:`~sage.data_structures.mutable_poset.MutablePoset`. + + TESTS:: + + sage: AsymptoticRing._create_empty_summands_() + poset() + """ + from sage.data_structures.mutable_poset import MutablePoset + from term_monoid import can_absorb, absorption + return MutablePoset(key=lambda element: element.growth, + can_merge=can_absorb, + merge=absorption) + + + def _create_element_in_extension_(self, term, old_term_parent=None): + r""" + Create an element in an extension of this asymptotic ring which + is chosen according to the input. + + INPUT: + + - ``term`` -- the element data. + + - ``old_term_parent`` -- the parent of ``term`` is compared to this + parent. If both are the same or ``old_parent`` is ``None``, + then the result is an expansion in this (``self``) asymptotic ring. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: A = AsymptoticRing('z^ZZ', ZZ) + sage: a = next(A.an_element().summands.elements_topological()) + sage: B = AsymptoticRing('z^QQ', QQ) + sage: b = next(B.an_element().summands.elements_topological()) + sage: c = A._create_element_in_extension_(a, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^ZZ with implicit coefficients in Integer Ring + sage: c = A._create_element_in_extension_(b, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + + TESTS:: + + sage: c = A._create_element_in_extension_(b, None) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + """ + if old_term_parent is None or term.parent() is old_term_parent: + parent = self + else: + # Insert an 'if' here once terms can have different + # coefficient rings, as this will be for L-terms. + parent = self.change_parameter( + growth_group=term.parent().growth_group, + coefficient_ring=term.parent().coefficient_ring) + return parent(term, simplify=False, convert=False) + + + def _element_constructor_(self, data, simplify=True, convert=True): + r""" + Convert a given object to this asymptotic ring. + + INPUT: + + - ``data`` -- an object representing the element to be + initialized. + + - ``simplify`` -- (default: ``True``) if set, then the constructed + element is simplified (terms are absorbed) automatically. + + - ``convert`` -- (default: ``True``) passed on to the element + constructor. If set, then the ``summands`` are converted to + the asymptotic ring (the parent of this expansion). If not, + then the summands are taken as they are. In that case, the + caller must ensure that the parent of the terms is set + correctly. + + OUTPUT: + + An element of this asymptotic ring. + + TESTS:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR(5) # indirect doctest + 5 + sage: AR(3*x^2) # indirect doctest + 3*x^2 + sage: x = ZZ['x'].gen(); x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: AR(x) + x + sage: y = ZZ['y'].gen(); AR(y) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Polynomial y is not in + Asymptotic Ring over Integer Ring + > *previous* ValueError: Growth y is not in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: y is not in Growth Group x^ZZ. + + :: + + sage: A = AsymptoticRing(growth_group='p^ZZ', coefficient_ring=QQ) + sage: P.

= QQ[] + sage: A(p) # indirect doctest + p + sage: A(p^11) # indirect doctest + p^11 + sage: A(2*p^11) # indirect doctest + 2*p^11 + sage: A(3*p^4 + 7/3*p - 8) # indirect doctest + 3*p^4 + 7/3*p - 8 + + :: + + sage: S = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=QQ) + sage: var('x, y') + (x, y) + sage: S(x + y) # indirect doctest + x + y + sage: S(2*x - 4*x*y^6) # indirect doctest + -4*x*y^6 + 2*x + + :: + + sage: A. = AsymptoticRing('a^ZZ * b^ZZ', QQ) + sage: 1/a + a^(-1) + + :: + + sage: P. = ZZ[] + sage: A(a + b) + a + b + sage: A(a + c) + Traceback (most recent call last): + ... + ValueError: Polynomial a + c is not in + Asymptotic Ring over Rational Field + > *previous* ValueError: Growth c is not in + Exact Term Monoid a^ZZ * b^ZZ with coefficients in Rational Field. + >> *previous* ValueError: c is not in Growth Group a^ZZ * b^ZZ. + >...> *previous* ValueError: c is not in any of the factors of + Growth Group a^ZZ * b^ZZ + + :: + + sage: M = AsymptoticRing('m^ZZ', ZZ) + sage: N = AsymptoticRing('n^ZZ', QQ) + sage: N(M.an_element()) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Cannot include m^3 with parent + Exact Term Monoid m^ZZ with coefficients in Integer Ring + in Asymptotic Ring over Rational Field + > *previous* ValueError: m^3 is not in Growth Group n^ZZ + + :: + + sage: M([1]) # indirect doctest + Traceback (most recent call last): + ... + TypeError: Not all list entries of [1] are asymptotic terms, + so cannot create an asymptotic expansion in + Asymptotic Ring over Integer Ring. + sage: M(SR.var('a') + 1) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Symbolic expression a + 1 is not in + Asymptotic Ring over Integer Ring. + > *previous* ValueError: a is not in + Exact Term Monoid m^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: Factor a of a is neither a coefficient + (in Integer Ring) nor growth (in Growth Group m^ZZ). + """ + from sage.data_structures.mutable_poset import MutablePoset + if isinstance(data, MutablePoset): + return self.element_class(self, data, simplify=simplify, convert=convert) + + if type(data) == self.element_class and data.parent() == self: + return data + + if isinstance(data, AsymptoticExpansion): + return self.element_class(self, data.summands, + simplify=simplify, convert=convert) + + from term_monoid import GenericTerm + if isinstance(data, GenericTerm): + data = (data,) + + if isinstance(data, (list, tuple)): + if not all(isinstance(elem, GenericTerm) for elem in data): + raise TypeError('Not all list entries of %s ' + 'are asymptotic terms, so cannot create an ' + 'asymptotic expansion in %s.' % (data, self)) + summands = AsymptoticRing._create_empty_summands_() + summands.union_update(data) + return self.element_class(self, summands, + simplify=simplify, convert=convert) + + if not data: + summands = AsymptoticRing._create_empty_summands_() + return self.element_class(self, summands, + simplify=simplify, convert=False) + + try: + P = data.parent() + except AttributeError: + return self.create_summand('exact', data) + + from misc import combine_exceptions + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + from sage.rings.polynomial.multi_polynomial_ring_generic import is_MPolynomialRing + from sage.rings.power_series_ring import is_PowerSeriesRing + + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import add_vararg + if data.operator() == add_vararg: + summands = [] + for summand in data.operands(): + # TODO: check if summand is an O-Term here + # (see #19425, #19426) + try: + summands.append(self.create_summand('exact', summand)) + except ValueError as e: + raise combine_exceptions( + ValueError('Symbolic expression %s is not in %s.' % + (data, self)), e) + return sum(summands) + + elif is_PolynomialRing(P): + p = P.gen() + try: + return sum(self.create_summand('exact', growth=p**i, + coefficient=c) + for i, c in enumerate(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_MPolynomialRing(P): + try: + return sum(self.create_summand('exact', growth=g, coefficient=c) + for c, g in iter(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_PowerSeriesRing(P): + raise NotImplementedError( + 'Cannot convert %s from the %s to an asymptotic expansion ' + 'in %s, since growths at other points than +oo are not yet ' + 'supported.' % (data, P, self)) + # Delete lines above as soon as we can deal with growths + # other than the that at going to +oo. + p = P.gen() + try: + result = self(data.polynomial()) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % (data, self)), e) + prec = data.precision_absolute() + if prec < sage.rings.infinity.PlusInfinity(): + try: + result += self.create_summand('O', growth=p**prec) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % + (data, self)), e) + return result + + return self.create_summand('exact', data) + + + def _coerce_map_from_(self, R): + r""" + Return whether ``R`` coerces into this asymptotic ring. + + INPUT: + + - ``R`` -- a parent. + + OUTPUT: + + A boolean. + + .. NOTE:: + + There are two possible cases: either ``R`` coerces in the + :meth:`coefficient_ring` of this asymptotic ring, or ``R`` + itself is an asymptotic ring, where both the + :meth:`growth_group` and the :meth:`coefficient_ring` coerce into + the :meth:`growth_group` and the :meth:`coefficient_ring` of this + asymptotic ring, respectively. + + TESTS:: + + sage: AR_ZZ = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); AR_ZZ + Asymptotic Ring over Integer Ring + sage: x_ZZ = AR_ZZ.gen() + sage: AR_QQ = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); AR_QQ + Asymptotic Ring over Rational Field + sage: x_QQ = AR_QQ.gen() + sage: AR_QQ.has_coerce_map_from(AR_ZZ) # indirect doctest + True + sage: x_ZZ * x_QQ + x^2 + + :: + + sage: AR_QQ.has_coerce_map_from(QQ) + True + sage: AR_QQ.has_coerce_map_from(ZZ) + True + """ + from sage.data_structures.mutable_poset import MutablePoset + if R == MutablePoset: + return + if self.coefficient_ring.has_coerce_map_from(R): + return True + if self.growth_group.has_coerce_map_from(R): + return True + elif isinstance(R, AsymptoticRing): + if self.growth_group.has_coerce_map_from(R.growth_group) and \ + self.coefficient_ring.has_coerce_map_from(R.coefficient_ring): + return True + + + def _repr_(self): + r""" + A representation string of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: AR = AsymptoticRing(growth_group='x^ZZ', + ....: coefficient_ring=ZZ) + sage: repr(AR) # indirect doctest + 'Asymptotic Ring over Integer Ring' + """ + try: + G = '<' + self.growth_group._repr_(condense=True) + '>' + except TypeError: + G = repr(self.growth_group) + return 'Asymptotic Ring %s over %s' % (G, self.coefficient_ring) + + + def _an_element_(self): + r""" + Return an element of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + An :class:`AsymptoticExpansion`. + + EXAMPLES:: + + sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ).an_element() + z^(3/2) + O(z^(1/2)) + sage: AsymptoticRing(growth_group='z^ZZ', coefficient_ring=QQ).an_element() + 1/8*z^3 + O(z) + sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ).an_element() + 1/8*z^(3/2) + O(z^(1/2)) + """ + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return self(E.an_element(), simplify=False, convert=False)**3 + \ + self(O.an_element(), simplify=False, convert=False) + + + def some_elements(self): + r""" + Return some elements of this term monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: A = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ) + sage: tuple(islice(A.some_elements(), 10)) + (z^(3/2) + O(z^(1/2)), + O(z^(1/2)), + z^(3/2) + O(z^(-1/2)), + -z^(3/2) + O(z^(1/2)), + O(z^(-1/2)), + O(z^2), + z^6 + O(z^(1/2)), + -z^(3/2) + O(z^(-1/2)), + O(z^2), + z^(3/2) + O(z^(-2))) + """ + from sage.misc.mrange import cantor_product + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return iter(self(e, simplify=False, convert=False)**3 + + self(o, simplify=False, convert=False) + for e, o in cantor_product( + E.some_elements(), O.some_elements())) + + + def gens(self): + r""" + Return a tuple with generators of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of asymptotic expansions. + + .. NOTE:: + + Generators do not necessarily exist. This depends on the + underlying growth group. For example, + :class:`monomial growth groups ` + have a generator, and exponential growth groups + do not. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.gens() + (x,) + sage: B. = AsymptoticRing(growth_group='y^ZZ * z^ZZ', coefficient_ring=QQ) + sage: B.gens() + (y, z) + """ + return tuple(self.create_summand('exact', + growth=g, + coefficient=self.coefficient_ring(1)) + for g in self.growth_group.gens_monomial()) + + + def gen(self, n=0): + r""" + Return the ``n``-th generator of this asymptotic ring. + + INPUT: + + - ``n`` -- (default: `0`) a non-negative integer. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R.gen() + x + """ + return self.gens()[n] + + + def ngens(self): + r""" + Return the number of generators of this asymptotic ring. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: AR.ngens() + 1 + """ + return len(self.growth_group.gens_monomial()) + + + def create_summand(self, type, data=None, **kwds): + r""" + Create a simple asymptotic expansion consisting of a single + summand. + + INPUT: + + - ``type`` -- 'O' or 'exact'. + + - ``data`` -- the element out of which a summand has to be created. + + - ``growth`` -- an element of the :meth:`growth_group`. + + - ``coefficient`` -- an element of the :meth:`coefficient_ring`. + + .. NOTE:: + + Either ``growth`` and ``coefficient`` or ``data`` have to + be specified. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + This method calls the factory :class:`TermMonoid + ` + with the appropriate arguments. + + EXAMPLES:: + + sage: R = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: R.create_summand('O', x^2) + O(x^2) + sage: R.create_summand('exact', growth=x^456, coefficient=123) + 123*x^456 + sage: R.create_summand('exact', data=12*x^13) + 12*x^13 + + TESTS:: + + sage: R.create_summand('exact', data='12*x^13') + 12*x^13 + sage: R.create_summand('exact', data='x^13 * 12') + 12*x^13 + sage: R.create_summand('exact', data='x^13') + x^13 + sage: R.create_summand('exact', data='12') + 12 + sage: R.create_summand('exact', data=12) + 12 + + :: + + sage: R.create_summand('O', growth=42*x^2, coefficient=1) + Traceback (most recent call last): + ... + ValueError: Growth 42*x^2 is not in O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ValueError: 42*x^2 is not in Growth Group x^ZZ. + + :: + + sage: AR. = AsymptoticRing('z^QQ', QQ) + sage: AR.create_summand('exact', growth='z^2') + Traceback (most recent call last): + ... + TypeError: Cannot create exact term: only 'growth' but + no 'coefficient' specified. + """ + from term_monoid import TermMonoid + TM = TermMonoid(type, asymptotic_ring=self) + + if data is None: + try: + data = kwds.pop('growth') + except KeyError: + raise TypeError("Neither 'data' nor 'growth' are specified.") + if type == 'exact' and kwds.get('coefficient') is None: + raise TypeError("Cannot create exact term: only 'growth' " + "but no 'coefficient' specified.") + + + if type == 'exact' and kwds.get('coefficient') == 0: + return self.zero() + + return self(TM(data, **kwds), simplify=False, convert=False) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.variable_names() + ('x', 'y') + """ + return self.growth_group.variable_names() + + + def construction(self): + r""" + Return the construction of this asymptotic ring. + + OUTPUT: + + A pair whose first entry is an + :class:`asymptotic ring construction functor ` + and its second entry the coefficient ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.construction() + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`AsymptoticRingFunctor`. + """ + return AsymptoticRingFunctor(self.growth_group), self.coefficient_ring + + +from sage.categories.pushout import ConstructionFunctor +class AsymptoticRingFunctor(ConstructionFunctor): + r""" + A :class:`construction functor ` + for :class:`asymptotic rings `. + + INPUT: + + - ``growth_group`` -- a partially ordered group (see + :class:`AsymptoticRing` or + :doc:`growth_group` for details). + + EXAMPLES:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction() # indirect doctest + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`sage.rings.asymptotic.growth_group.AbstractGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.MonomialGrowthGroupFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: cm = sage.structure.element.get_coercion_model() + sage: cm.record_exceptions() + sage: cm.common_parent(X, Y) + Asymptotic Ring over Rational Field + sage: sage.structure.element.coercion_traceback() # not tested + + :: + + sage: from sage.categories.pushout import pushout + sage: pushout(AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ), QQ) + Asymptotic Ring over Rational Field + """ + + rank = 13 + + + def __init__(self, growth_group): + r""" + See :class:`AsymptoticRingFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRingFunctor + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: AsymptoticRingFunctor(GrowthGroup('x^ZZ')) + AsymptoticRing + """ + self.growth_group = growth_group + + from sage.categories.rings import Rings + super(ConstructionFunctor, self).__init__( + Rings(), Rings()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction()[0] # indirect doctest + AsymptoticRing + """ + return 'AsymptoticRing<%s>' % (self.growth_group._repr_(condense=True),) + + + def _apply_functor(self, coefficient_ring): + r""" + Apply this functor to the given ``coefficient_ring``. + + INPUT: + + - ``base`` - anything :class:`~sage.rings.asymptotic.growth_group.MonomialGrowthGroup` accepts. + + OUTPUT: + + An :class:`AsymptoticRing`. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: F, C = A.construction() + sage: F(C) # indirect doctest + Asymptotic Ring over Rational Field + """ + return AsymptoticRing(growth_group=self.growth_group, + coefficient_ring=coefficient_ring) + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X.merge(F_X) + AsymptoticRing + sage: F_X.merge(F_Y) + AsymptoticRing + """ + if self == other: + return self + + if isinstance(other, AsymptoticRingFunctor): + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + try: + G = cm.common_parent(self.growth_group, other.growth_group) + except TypeError: + pass + else: + return AsymptoticRingFunctor(G) + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X == F_X + True + sage: F_X == F_Y + False + """ + return type(self) == type(other) and \ + self.growth_group == other.growth_group + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X != F_X + False + sage: F_X != F_Y + True + """ + return not self.__eq__(other) diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py new file mode 100644 index 00000000000..ac2f3517f9c --- /dev/null +++ b/src/sage/rings/asymptotic/growth_group.py @@ -0,0 +1,4051 @@ +r""" +(Asymptotic) Growth Groups + +This module provides support for (asymptotic) growth groups. + +Such groups are equipped with a partial order: the elements can be +seen as functions, and the behavior as their argument (or arguments) +gets large (tend to `\infty`) is compared. + +Growth groups are used for the calculations done in the +:doc:`asymptotic ring `. There, take a look at the +:ref:`informal definition `, where +examples of growth groups and elements are given as well. + + +.. WARNING:: + + As this code is experimental, warnings are thrown when a growth + group is created for the first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import \ + ....: GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(ZZ) + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group Generic(ZZ) + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group x^ZZ * log(x)^ZZ + +.. _growth_group_description: + +Description of Growth Groups +============================ + +Many growth groups can be described by a string, which can also be used to +create them. For example, the string ``'x^QQ * log(x)^ZZ * QQ^y * y^QQ'`` +represents a growth group with the following properties: + +- It is a growth group in the two variables `x` and `y`. + +- Its elements are of the form + + .. MATH:: + + x^r \cdot \log(x)^s \cdot a^y \cdot y^q + + for `r\in\QQ`, `s\in\ZZ`, `a\in\QQ` and `q\in\QQ`. + +- The order is with respect to `x\to\infty` and `y\to\infty` independently + of each other. + +- To compare such elements, they are split into parts belonging to + only one variable. In the example above, + + .. MATH:: + + x^{r_1} \cdot \log(x)^{s_1} \leq x^{r_2} \cdot \log(x)^{s_2} + + if `(r_1, s_1) \leq (r_2, s_2)` lexicographically. This reflects the fact + that elements `x^r` are larger than elements `\log(x)^s` as `x\to\infty`. + The factors belonging to the variable `y` are compared analogously. + + The results of these comparisons are then put together using the + :wikipedia:`product order `, i.e., `\leq` if each component + satisfies `\leq`. + + +Each description string consists of ordered factors---yes, this means +``*`` is noncommutative---of strings describing "elementary" growth +groups (see the examples below). As stated in the example above, these +factors are split by their variable; factors with the same variable are +grouped. Reading such factors from left to right determines the order: +Comparing elements of two factors (growth groups) `L` and `R`, then all +elements of `L` are considered to be larger than each element of `R`. + + +.. _growth_group_creating: + +Creating a Growth Group +======================= + +For many purposes the factory ``GrowthGroup`` (see +:class:`GrowthGroupFactory`) is the most convenient way to generate a +growth group. +:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + +Here are some examples:: + + sage: GrowthGroup('z^ZZ') + Growth Group z^ZZ + sage: M = GrowthGroup('z^QQ'); M + Growth Group z^QQ + +Each of these two generated groups is a :class:`MonomialGrowthGroup`, +whose elements are powers of a fixed symbol (above ``'z'``). +For the order of the elements it is assumed that `z\to\infty`. + +.. NOTE:: + + Growth groups where the variable tend to some value distinct from + `\infty` are not yet implemented. + +To create elements of `M`, a generator can be used:: + + sage: z = M.gen() + sage: z^(3/5) + z^(3/5) + +Strings can also be parsed:: + + sage: M('z^7') + z^7 + +Similarly, we can construct logarithmic factors by:: + + sage: GrowthGroup('log(z)^QQ') + Growth Group log(z)^QQ + +which again creates a +:class:`MonomialGrowthGroup`. An :class:`ExponentialGrowthGroup` is generated in the same way. Our factory gives +:: + + sage: E = GrowthGroup('QQ^z'); E + Growth Group QQ^z + +and a typical element looks like this:: + + sage: E.an_element() + (1/2)^z + +More complex groups are created in a similar fashion. For example +:: + + sage: C = GrowthGroup('QQ^z * z^QQ * log(z)^QQ'); C + Growth Group QQ^z * z^QQ * log(z)^QQ + +This contains elements of the form +:: + + sage: C.an_element() + (1/2)^z*z^(1/2)*log(z)^(1/2) + +The group `C` itself is a cartesian product; to be precise a +:class:`~sage.rings.asymptotic.growth_group_cartesian.UnivariateProduct`. We +can see its factors:: + + sage: C.cartesian_factors() + (Growth Group QQ^z, Growth Group z^QQ, Growth Group log(z)^QQ) + +Multivariate constructions are also possible:: + + sage: GrowthGroup('x^QQ * y^QQ') + Growth Group x^QQ * y^QQ + +This gives a +:class:`~sage.rings.asymptotic.growth_group_cartesian.MultivariateProduct`. + +Both these cartesian products are derived from the class +:class:`~sage.rings.asymptotic.growth_group_cartesian.GenericProduct`. Moreover +all growth groups have the abstract base class +:class:`GenericGrowthGroup` in common. + +Some Examples +^^^^^^^^^^^^^ + +:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ'); G_x + Growth Group x^ZZ + sage: G_xy = GrowthGroup('x^ZZ * y^ZZ'); G_xy + Growth Group x^ZZ * y^ZZ + sage: G_xy.an_element() + x*y + sage: x = G_xy('x'); y = G_xy('y') + sage: x^2 + x^2 + sage: elem = x^21*y^21; elem^2 + x^42*y^42 + +A monomial growth group itself is totally ordered, all elements +are comparable. However, this does **not** hold for cartesian +products:: + + sage: e1 = x^2*y; e2 = x*y^2 + sage: e1 <= e2 or e2 <= e1 + False + +In terms of uniqueness, we have the following behaviour:: + + sage: GrowthGroup('x^ZZ * y^ZZ') is GrowthGroup('y^ZZ * x^ZZ') + True + +The above is ``True`` since the order of the factors does not play a role here; they use different variables. But when using the same variable, it plays a role:: + + sage: GrowthGroup('x^ZZ * log(x)^ZZ') is GrowthGroup('log(x)^ZZ * x^ZZ') + False + +In this case the components are ordered lexicographically, which +means that in the second growth group, ``log(x)`` is assumed to +grow faster than ``x`` (which is nonsense, mathematically). See +:class:`CartesianProduct ` +for more details or see :ref:`above ` +for a more extensive description. + +Short notation also allows the construction of more complicated +growth groups:: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^QQ * y^QQ') + sage: G.an_element() + (1/2)^x*x*log(x)^(1/2)*y^(1/2) + sage: x, y = var('x y') + sage: G(2^x * log(x) * y^(1/2)) * G(x^(-5) * 5^x * y^(1/3)) + 10^x*x^(-5)*log(x)*y^(5/6) + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.growth_group_cartesian', 'CartesianProductGrowthGroups') + + +class Variable(sage.structure.unique_representation.CachedRepresentation, + sage.structure.sage_object.SageObject): + r""" + A class managing the variable of a growth group. + + INPUT: + + - ``var`` -- an object whose representation string is used as the + variable. It has to be a valid Python identifier. ``var`` can + also be a tuple (or other iterable) of such objects. + + - ``repr`` -- (default: ``None``) if specified, then this string + will be displayed instead of ``var``. Use this to get + e.g. ``log(x)^ZZ``: ``var`` is then used to specify the variable `x`. + + - ``ignore`` -- (default: ``None``) a tuple (or other iterable) + of strings which are not variables. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: v = Variable('x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x1'); repr(v), v.variable_names() + ('x1', ('x1',)) + sage: v = Variable('x_42'); repr(v), v.variable_names() + ('x_42', ('x_42',)) + sage: v = Variable(' x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x '); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable(''); repr(v), v.variable_names() + ('', ()) + + :: + + sage: v = Variable(('x', 'y')); repr(v), v.variable_names() + ('x, y', ('x', 'y')) + sage: v = Variable(('x', 'log(y)')); repr(v), v.variable_names() + ('x, log(y)', ('x', 'y')) + sage: v = Variable(('x', 'log(x)')); repr(v), v.variable_names() + Traceback (most recent call last): + ... + ValueError: Variable names ('x', 'x') are not pairwise distinct. + + :: + + sage: v = Variable('log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) + sage: v = Variable('log(log(x))'); repr(v), v.variable_names() + ('log(log(x))', ('x',)) + + :: + + sage: v = Variable('x', repr='log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) + + :: + + sage: v = Variable('e^x', ignore=('e',)); repr(v), v.variable_names() + ('e^x', ('x',)) + + :: + + sage: v = Variable('(e^n)', ignore=('e',)); repr(v), v.variable_names() + ('e^n', ('n',)) + sage: v = Variable('(e^(n*log(n)))', ignore=('e',)); repr(v), v.variable_names() + ('e^(n*log(n))', ('n',)) + """ + def __init__(self, var, repr=None, ignore=None): + r""" + See :class:`Variable` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') + blub + sage: Variable('blub') is Variable('blub') + True + + :: + + sage: Variable('(:-)') + Traceback (most recent call last): + ... + TypeError: Malformed expression: : !!! - + sage: Variable('(:-)', repr='icecream') + Traceback (most recent call last): + ... + ValueError: ':-' is not a valid name for a variable. + """ + from sage.symbolic.ring import isidentifier + from misc import split_str_by_op + + if not isinstance(var, (list, tuple)): + var = (var,) + var = tuple(''.join(split_str_by_op(str(v), None)) for v in var) # we strip off parentheses + + if ignore is None: + ignore = tuple() + + if repr is None: + var_bases = tuple(i for i in sum(iter( + self.extract_variable_names(v) + if not isidentifier(v) else (v,) + for v in var), tuple()) if i not in ignore) + var_repr = ', '.join(var) + else: + for v in var: + if not isidentifier(v): + raise ValueError("'%s' is not a valid name for a variable." % (v,)) + var_bases = var + var_repr = str(repr).strip() + + if len(var_bases) != len(set(var_bases)): + raise ValueError('Variable names %s are not pairwise distinct.' % + (var_bases,)) + self.var_bases = var_bases + self.var_repr = var_repr + + + def __hash__(self): + r""" + Return the hash of this variable. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: hash(Variable('blub')) # random + -123456789 + """ + return hash((self.var_repr,) + self.var_bases) + + + def __eq__(self, other): + r""" + Compare whether this variable equals ``other``. + + INPUT: + + - ``other`` -- another variable. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') == Variable('x') + True + sage: Variable('x') == Variable('y') + False + """ + return self.var_repr == other.var_repr and self.var_bases == other.var_bases + + + def __ne__(self, other): + r""" + Return whether this variable does not equal ``other``. + + INPUT: + + - ``other`` -- another variable. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') != Variable('x') + False + sage: Variable('x') != Variable('y') + True + """ + return not self == other + + + def _repr_(self): + r""" + Return a representation string of this variable. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') # indirect doctest + blub + """ + return self.var_repr + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').variable_names() + ('x',) + sage: Variable('log(x)').variable_names() + ('x',) + """ + return self.var_bases + + + def is_monomial(self): + r""" + Return whether this is a monomial variable. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').is_monomial() + True + sage: Variable('log(x)').is_monomial() + False + """ + return len(self.var_bases) == 1 and self.var_bases[0] == self.var_repr + + + @staticmethod + def extract_variable_names(s): + r""" + Determine the name of the variable for the given string. + + INPUT: + + - ``s`` -- a string. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable.extract_variable_names('') + () + sage: Variable.extract_variable_names('x') + ('x',) + sage: Variable.extract_variable_names('exp(x)') + ('x',) + sage: Variable.extract_variable_names('sin(cos(ln(x)))') + ('x',) + + :: + + sage: Variable.extract_variable_names('log(77w)') + ('w',) + sage: Variable.extract_variable_names('log(x') + Traceback (most recent call last): + .... + TypeError: Bad function call: log(x !!! + sage: Variable.extract_variable_names('x)') + Traceback (most recent call last): + .... + TypeError: Malformed expression: x) !!! + sage: Variable.extract_variable_names('log)x(') + Traceback (most recent call last): + .... + TypeError: Malformed expression: log) !!! x( + sage: Variable.extract_variable_names('log(x)+y') + ('x', 'y') + sage: Variable.extract_variable_names('icecream(summer)') + ('summer',) + + :: + + sage: Variable.extract_variable_names('a + b') + ('a', 'b') + sage: Variable.extract_variable_names('a+b') + ('a', 'b') + sage: Variable.extract_variable_names('a +b') + ('a', 'b') + sage: Variable.extract_variable_names('+a') + ('a',) + sage: Variable.extract_variable_names('a+') + Traceback (most recent call last): + ... + TypeError: Malformed expression: a+ !!! + sage: Variable.extract_variable_names('b!') + ('b',) + sage: Variable.extract_variable_names('-a') + ('a',) + sage: Variable.extract_variable_names('a*b') + ('a', 'b') + sage: Variable.extract_variable_names('2^q') + ('q',) + sage: Variable.extract_variable_names('77') + () + + :: + + sage: Variable.extract_variable_names('a + (b + c) + d') + ('a', 'b', 'c', 'd') + """ + from sage.symbolic.ring import SR + if s == '': + return () + return tuple(str(s) for s in SR(s).variables()) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this variable. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x^2')._substitute_({'x': SR.var('z')}) + z^2 + sage: _.parent() + Symbolic Ring + + :: + + sage: Variable('1/x')._substitute_({'x': 'z'}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in 1/x in + . + > *previous* TypeError: unsupported operand parent(s) for '/': + 'Integer Ring' and '' + sage: Variable('1/x')._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 1/x in + . + > *previous* ZeroDivisionError: rational division by zero + """ + from sage.misc.sage_eval import sage_eval + try: + return sage_eval(self.var_repr, locals=rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _is_lt_one_(self): + r""" + Return whether this element is less than `1`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G(x) + sage: (x^42).is_lt_one() # indirect doctest + False + sage: (x^(-42)).is_lt_one() # indirect doctest + True + """ + one = self.parent().one() + return self <= one and self != one + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_(self, base=None): + r""" + Return the logarithm of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: x, = G.gens_monomial() + sage: log(x) # indirect doctest + log(x) + sage: log(x^5) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(x^5) a factor 5 != 1 appeared, + which is not contained in Growth Group x^ZZ * log(x)^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x, = G.gens_monomial() + sage: el = x.rpow(2); el + 2^x + sage: log(el) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(2^x) a factor log(2) != 1 + appeared, which is not contained in Growth Group QQ^x * x^ZZ. + sage: log(el, base=2) # indirect doctest + x + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: x = GenericGrowthGroup(ZZ).an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1) in abstract base class. + + :: + + sage: x = GrowthGroup('x^ZZ').an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^ZZ. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^QQ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^QQ * x^ZZ. + + :: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^ZZ * x^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Calculating log(x*y) results in a sum, + which is not contained in + Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + """ + from misc import log_string + + log_factor = self.log_factor(base=base) + if not log_factor: + raise ArithmeticError('%s is zero, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + + if len(log_factor) != 1: + raise ArithmeticError('Calculating %s results in a sum, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + g, c = log_factor[0] + if c != 1: + raise ArithmeticError('When calculating %s a factor %s != 1 ' + 'appeared, which is not contained in %s.' % + (log_string(self, base), c, self.parent())) + return g + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_factor_(self, base=None): + r""" + Return the logarithm of the factorization of this + element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is a growth + element and the second a multiplicative coefficient. + + ALGORITHM: + + This function factors the given element and calculates + the logarithm of each of these factors. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + sage: (x^123).log_factor() # indirect doctest + ((log(x), 123),) + sage: (G('2^x') * x^2).log_factor(base=2) # indirect doctest + ((x, 1), (log(x), 2/log(2))) + + :: + + sage: G(1).log_factor() + () + + :: + + sage: log(x).log_factor() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(log(x)) since log(log(x)) is + not in Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + + .. SEEALSO:: + + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.factors`, + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.log`. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ * log(x)^ZZ") + sage: x, = G.gens_monomial() + sage: (exp(x) * x).log_factor() # indirect doctest + ((x, 1), (log(x), 1)) + """ + log_factor = self._log_factor_(base=base) + + for g, c in log_factor: + if hasattr(g, 'parent') and \ + isinstance(g.parent(), GenericGrowthGroup): + continue + from misc import log_string + raise ArithmeticError('Cannot build %s since %s ' + 'is not in %s.' % (log_string(self, base), + g, self.parent())) + + return log_factor + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _rpow_(self, base): + r""" + Calculate the power of ``base`` to this element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x = G('x') + sage: x.rpow(2) # indirect doctest + 2^x + sage: x.rpow(1/2) # indirect doctest + (1/2)^x + + :: + + sage: x.rpow(0) # indirect doctest + Traceback (most recent call last): + ... + ValueError: 0 is not an allowed base for calculating the power to x. + sage: (x^2).rpow(2) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in Growth Group QQ^x * x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ' and 'Growth Group ZZ^(x^2)' + + :: + + sage: G = GrowthGroup('QQ^(x*log(x)) * x^ZZ * log(x)^ZZ') + sage: x = G('x') + sage: (x * log(x)).rpow(2) # indirect doctest + 2^(x*log(x)) + """ + if base == 0: + raise ValueError('%s is not an allowed base for calculating the ' + 'power to %s.' % (base, self)) + + var = str(self) + + try: + element = self._rpow_element_(base) + except ValueError: + if base == 'e': + from sage.rings.integer_ring import ZZ + from misc import repr_op + M = MonomialGrowthGroup(ZZ, repr_op('e', '^', var), + ignore_variables=('e',)) + element = M(raw_element=ZZ(1)) + else: + E = ExponentialGrowthGroup(base.parent(), var) + element = E(raw_element=base) + + try: + return self.parent().one() * element + except (TypeError, ValueError) as e: + from misc import combine_exceptions, repr_op + raise combine_exceptions( + ArithmeticError('Cannot construct %s in %s' % + (repr_op(base, '^', var), self.parent())), e) + + +class GenericGrowthElement(sage.structure.element.MultiplicativeGroupElement): + r""" + A basic implementation of a generic growth element. + + Growth elements form a group by multiplication, and (some of) the + elements can be compared to each other, i.e., all elements form a + poset. + + INPUT: + + - ``parent`` -- a :class:`GenericGrowthGroup`. + + - ``raw_element`` -- an element from the base of the parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GenericGrowthElement) + sage: G = GenericGrowthGroup(ZZ) + sage: g = GenericGrowthElement(G, 42); g + GenericGrowthElement(42) + sage: g.parent() + Growth Group Generic(ZZ) + sage: G(raw_element=42) == g + True + """ + + def __init__(self, parent, raw_element): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) + GenericGrowthElement(42) + + TESTS:: + + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthElement + sage: GenericGrowthElement(None, 0) + Traceback (most recent call last): + ... + ValueError: The parent must be provided + """ + if parent is None: + raise ValueError('The parent must be provided') + super(GenericGrowthElement, self).__init__(parent=parent) + + self._raw_element_ = parent.base()(raw_element) + + + def _repr_(self): + r""" + A representation string for this generic element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) # indirect doctest + GenericGrowthElement(42) + sage: H = GenericGrowthGroup(ZZ, 'h') + sage: H(raw_element=42) # indirect doctest + GenericGrowthElement(42, h) + """ + vars = ', '.join(self.parent()._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'GenericGrowthElement(%s%s)' % (self._raw_element_, vars) + + + def __hash__(self): + r""" + Return the hash of this element. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); + sage: hash(G(raw_element=42)) # random + 5656565656565656 + """ + return hash((self.parent(), self._raw_element_)) + + + def _mul_(self, other): + r""" + Abstract multiplication method for generic elements. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A :class:`GenericGrowthElement` representing the product with + ``other``. + + .. NOTE:: + + Inherited classes must override this. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: g = G.an_element() + sage: g*g + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + def __invert__(self): + r""" + Return the inverse of this growth element. + + OUTPUT: + + An instance of :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: ~G.an_element() + Traceback (most recent call last): + ... + NotImplementedError: Inversion of GenericGrowthElement(1) not implemented + (in this abstract method). + sage: G.an_element()^7 + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + sage: P = GrowthGroup('x^ZZ') + sage: ~P.an_element() + x^(-1) + """ + raise NotImplementedError('Inversion of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def __eq__(self, other): + r""" + Return whether this growth element is equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_eq_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G.an_element() == G.an_element() + True + sage: G(raw_element=42) == G(raw_element=7) + False + + :: + + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: G_QQ = GenericGrowthGroup(QQ) + sage: G_ZZ(raw_element=1) == G_QQ(raw_element=1) + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() == P_QQ.gen() + True + sage: ~P_ZZ.gen() == P_ZZ.gen() + False + sage: ~P_ZZ(1) == P_ZZ(1) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._eq_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.eq) + except TypeError: + return False + + + def _eq_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is equal to ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=1) + sage: e1._eq_(P.gen()) + True + sage: e2 = e1^4 + sage: e2 == e1^2*e1*e1 + True + sage: e2 == e1 + False + """ + return self._raw_element_ == other._raw_element_ + + + def __ne__(self, other): + r""" + Return whether this growth element is not equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.one() != G(1) + False + sage: G.one() != G.one() + False + sage: G(1) != G(1) + False + """ + return not self == other + + + def __le__(self, other): + r""" + Return whether this growth element is at most (less than or equal + to) ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_le_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 + True + sage: ~P_ZZ.gen() <= P_ZZ.gen() + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def _le_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is at most (less + than or equal to) ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: e1 = G(raw_element=1); e2 = G(raw_element=2) + sage: e1 <= e2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(QQ) + sage: G.an_element().log_factor() # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1/2) in abstract base class. + """ + raise NotImplementedError('Cannot determine logarithmized factorization ' + 'of %s in abstract base class.' % (self,)) + + + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + Nothing since a ``ValueError`` is raised in this generic method. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: x = G(raw_element=3) + sage: x._rpow_element_(2) is None + Traceback (most recent call last): + ... + ValueError: Cannot compute 2 to the generic element 3^x. + """ + raise ValueError('Cannot compute %s to the generic element %s.' % + (base, self)) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.an_element().factors() + (x,) + """ + return (self,) + + + is_lt_one = _is_lt_one_ + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic growth element. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42)._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in GenericGrowthElement(42) in + Growth Group Generic(ZZ). + > *previous* TypeError: Cannot substitute in the abstract base class + Growth Group Generic(ZZ). + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericGrowthGroup( + sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): + r""" + A basic implementation for growth groups. + + INPUT: + + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). + + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + + - ``ignore_variables`` -- (default: ``None``) a tuple (or other + iterable) of strings. The specified names are not considered as + variables. + + .. NOTE:: + + This class should be derived for concrete implementations. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); G + Growth Group Generic(ZZ) + + .. SEEALSO:: + + :class:`MonomialGrowthGroup`, + :class:`ExponentialGrowthGroup` + """ + # TODO: implement some sort of 'assume', where basic assumptions + # for the variables can be stored. --> within the cartesian product + + # enable the category framework for elements + Element = GenericGrowthElement + + + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas + + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False)] + + _determine_category_axiom_mapping_ = [] + + + @staticmethod + def __classcall__(cls, base, var=None, category=None, ignore_variables=None): + r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`GenericGrowthGroup`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P1 = MonomialGrowthGroup(ZZ, 'x') + sage: P2 = MonomialGrowthGroup(ZZ, ZZ['x'].gen()) + sage: P3 = MonomialGrowthGroup(ZZ, SR.var('x')) + sage: P1 is P2 and P2 is P3 + True + sage: P4 = MonomialGrowthGroup(ZZ, buffer('xylophone', 0, 1)) + sage: P1 is P4 + True + sage: P5 = MonomialGrowthGroup(ZZ, 'x ') + sage: P1 is P5 + True + + :: + + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + + Test determining of the category (GenericGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ, 'x').category() # indirect doctest + Category of posets + sage: GenericGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + + Test determining of the category (MonomialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: MonomialGrowthGroup(ZZ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + sage: W = Words([0, 1]) + sage: W.category() + Category of sets + sage: MonomialGrowthGroup(W, 'x').category() # indirect doctest + Category of sets + + Test determining of the category (ExponentialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative monoids and Category of posets + sage: ExponentialGrowthGroup(QQ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: ExponentialGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + sage: ExponentialGrowthGroup(QQ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + """ + if not isinstance(base, sage.structure.parent.Parent): + raise TypeError('%s is not a valid base.' % (base,)) + + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var, ignore=ignore_variables) + + from sage.categories.posets import Posets + if category is None: + # The following block can be removed once #19269 is fixed. + from sage.rings.integer_ring import ZZ + from sage.rings.rational_field import QQ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if base is ZZ or base is QQ or \ + is_PolynomialRing(base) and \ + (base.base_ring() is ZZ or base.base_ring() is QQ): + initial_category = Posets() + else: + initial_category = None + + from misc import transform_category + category = transform_category( + base.category(), + cls._determine_category_subcategory_mapping_, + cls._determine_category_axiom_mapping_, + initial_category=initial_category) + + return super(GenericGrowthGroup, cls).__classcall__( + cls, base, var, category) + + + @sage.misc.superseded.experimental(trac_number=17601) + def __init__(self, base, var, category): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).category() + Category of posets + + :: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x') + Growth Group x^ZZ + sage: MonomialGrowthGroup(QQ, SR.var('n')) + Growth Group n^QQ + sage: MonomialGrowthGroup(ZZ, ZZ['y'].gen()) + Growth Group y^ZZ + sage: MonomialGrowthGroup(QQ, 'log(x)') + Growth Group log(x)^QQ + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'x') + Growth Group QQ^x + sage: ExponentialGrowthGroup(SR, ZZ['y'].gen()) + Growth Group SR^y + + TESTS:: + + sage: G = GenericGrowthGroup(ZZ) + sage: G.is_parent_of(G(raw_element=42)) + True + sage: G2 = GenericGrowthGroup(ZZ, category=FiniteGroups() & Posets()) + sage: G2.category() + Join of Category of finite groups and Category of finite posets + + :: + + sage: G = GenericGrowthGroup('42') + Traceback (most recent call last): + ... + TypeError: 42 is not a valid base. + + :: + + sage: MonomialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: MonomialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + :: + + sage: ExponentialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: ExponentialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + """ + self._var_ = var + super(GenericGrowthGroup, self).__init__(category=category, + base=base) + + + def _repr_short_(self): + r""" + A short representation string of this abstract growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(QQ)._repr_short_() + 'Generic(QQ)' + sage: GenericGrowthGroup(QQ) + Growth Group Generic(QQ) + sage: GenericGrowthGroup(QQ, ('a', 'b')) + Growth Group Generic(QQ, a, b) + """ + from misc import parent_to_repr_short + vars = ', '.join(self._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'Generic(%s%s)' % (parent_to_repr_short(self.base()), vars) + + + def _repr_(self, condense=False): + r""" + A representations string of this growth group. + + INPUT: + + - ``condense`` -- (default: ``False``) if set, then a shorter + output is returned, e.g. the prefix-string ``Growth Group`` + is not show in this case. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') # indirect doctest + Growth Group x^ZZ + sage: GrowthGroup('log(x)^QQ') # indirect doctest + Growth Group log(x)^QQ + + TESTS:: + + sage: GrowthGroup('log(x)^QQ')._repr_(condense=True) + 'log(x)^QQ' + """ + pre = 'Growth Group ' if not condense else '' + return '%s%s' % (pre, self._repr_short_()) + + + def __hash__(self): + r""" + Return the hash of this group. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: hash(GenericGrowthGroup(ZZ)) # random + 4242424242424242 + + :: + + sage: P = GrowthGroup('x^ZZ') + sage: hash(P) # random + -1234567890123456789 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: hash(P) # random + -1234567890123456789 + """ + return hash((self.__class__, self.base(), self._var_)) + + + def _an_element_(self): + r""" + Return an element of ``self``. + + INPUT: + + Nothing. + + OUTPUT: + + An element of ``self``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: GenericGrowthGroup(ZZ).an_element() # indirect doctest + GenericGrowthElement(1) + sage: GrowthGroup('z^ZZ').an_element() # indirect doctest + z + sage: GrowthGroup('log(z)^QQ').an_element() # indirect doctest + log(z)^(1/2) + sage: GrowthGroup('QQ^(x*log(x))').an_element() # indirect doctest + (1/2)^(x*log(x)) + """ + return self.element_class(self, self.base().an_element()) + + + def some_elements(self): + r""" + Return some elements of this growth group. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('z^ZZ').some_elements()) + (1, z, z^(-1), z^2, z^(-2), z^3, z^(-3), + z^4, z^(-4), z^5, z^(-5), ...) + sage: tuple(GrowthGroup('z^QQ').some_elements()) + (z^(1/2), z^(-1/2), z^2, z^(-2), + 1, z, z^(-1), z^42, + z^(2/3), z^(-2/3), z^(3/2), z^(-3/2), + z^(4/5), z^(-4/5), z^(5/4), z^(-5/4), ...) + """ + return iter(self.element_class(self, e) + for e in self.base().some_elements()) + + + def _create_element_in_extension_(self, raw_element): + r""" + Create an element in an extension of this growth group which + is chosen according to the input ``raw_element``. + + INPUT: + + - ``raw_element`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: G._create_element_in_extension_(3).parent() + Growth Group z^ZZ + sage: G._create_element_in_extension_(1/2).parent() + Growth Group z^QQ + """ + if raw_element.parent() is self.base(): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(raw_element.parent(), self._var_, + category=self.category()) + return parent(raw_element=raw_element) + + + def le(self, left, right): + r""" + Return whether the growth of ``left`` is at most (less than or + equal to) the growth of ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G.gen() + sage: G.le(x, x^2) + True + sage: G.le(x^2, x) + False + sage: G.le(x^0, 1) + True + """ + return self(left) <= self(right) + + + def _element_constructor_(self, data, raw_element=None): + r""" + Convert a given object to this growth group. + + INPUT: + + - ``data`` -- an object representing the element to be + initialized. + + - ``raw_element`` -- (default: ``None``) if given, then this is + directly passed to the element constructor (i.e., no conversion + is performed). + + OUTPUT: + + An element of this growth group. + + .. NOTE:: + + Either ``data`` or ``raw_element`` has to be given. If + ``raw_element`` is specified, then no positional argument + may be passed. + + This method calls :meth:`_convert_`, which does the actual + conversion from ``data``. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: z = G_ZZ(raw_element=42); z # indirect doctest + GenericGrowthElement(42) + sage: z is G_ZZ(z) # indirect doctest + True + + :: + + sage: G_QQ = GenericGrowthGroup(QQ) + sage: q = G_QQ(raw_element=42) # indirect doctest + sage: q is z + False + sage: G_ZZ(q) # indirect doctest + GenericGrowthElement(42) + sage: G_QQ(z) # indirect doctest + GenericGrowthElement(42) + sage: q is G_ZZ(q) # indirect doctest + False + + :: + + sage: G_ZZ() + Traceback (most recent call last): + ... + ValueError: No input specified. Cannot continue. + sage: G_ZZ('blub') # indirect doctest + Traceback (most recent call last): + ... + ValueError: blub is not in Growth Group Generic(ZZ). + sage: G_ZZ('x', raw_element=42) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Input is ambigous: x as well as raw_element=42 are specified. + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: x = GrowthGroup('x^ZZ')(raw_element=1) # indirect doctest + sage: G_y = GrowthGroup('y^ZZ') + sage: G_y(x) # indirect doctest + Traceback (most recent call last): + ... + ValueError: x is not in Growth Group y^ZZ. + + :: + + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')('2^x')) + 2^x + """ + from misc import underlying_class, combine_exceptions + + if raw_element is None: + if isinstance(data, int) and data == 0: + raise ValueError('No input specified. Cannot continue.') + + elif isinstance(data, self.element_class): + if data.parent() == self: + return data + if self._var_ != data.parent()._var_: + raise ValueError('%s is not in %s.' % (data, self)) + raw_element = data._raw_element_ + + elif isinstance(data, self.Element): + if self._var_ == data.parent()._var_: + try: + raw_element = self.base()(data._raw_element_) + except (TypeError, ValueError) as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + elif isinstance(data, GenericGrowthElement): + if data.is_one(): + return self.one() + + else: + raw_element = self._convert_(data) + + if raw_element is None: + raise ValueError('%s is not in %s.' % (data, self)) + elif not isinstance(data, int) or data != 0: + raise ValueError('Input is ambigous: ' + '%s as well as raw_element=%s ' + 'are specified.' % (data, raw_element)) + + return self.element_class(self, raw_element) + + + def _convert_(self, data): + r""" + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + An element of the base ring or ``None`` (when no such element + can be constructed). + + .. NOTE:: + + This method always returns ``None`` in this abstract base + class, and should be overridden in inherited class. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G._convert_('icecream') is None + True + """ + pass + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: G_ZZ.has_coerce_map_from(G_QQ) # indirect doctest + False + sage: G_QQ.has_coerce_map_from(G_ZZ) # indirect doctest + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('x^ZZ') + sage: P_x_QQ = GrowthGroup('x^QQ') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('y^ZZ') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('ZZ^x') + sage: P_x_QQ = GrowthGroup('QQ^x') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('ZZ^y') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: GrowthGroup('x^QQ').has_coerce_map_from(GrowthGroup('QQ^x')) # indirect doctest + False + """ + from misc import underlying_class + if isinstance(S, underlying_class(self)) and self._var_ == S._var_: + if self.base().has_coerce_map_from(S.base()): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = GrowthGroup('y^ZZ') + + When using growth groups with disjoint variable lists, then a + pushout can be constructed:: + + sage: A._pushout_(B) + Growth Group QQ^x * y^ZZ + sage: cm.common_parent(A, B) + Growth Group QQ^x * y^ZZ + + In general, growth groups of the same variable cannot be + combined automatically, since there is no order relation between the two factors:: + + sage: C = GrowthGroup('x^QQ') + sage: cm.common_parent(A, C) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x' and 'Growth Group x^QQ' + + However, combining is possible if the factors with the same variable + overlap:: + + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('exp(x)^ZZ * x^ZZ')) + Growth Group exp(x)^ZZ * x^ZZ * log(x)^ZZ + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('y^ZZ * x^ZZ')) + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + :: + + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + + :: + + sage: cm.record_exceptions() + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + sage: sage.structure.element.coercion_traceback() # not tested + """ + if not isinstance(other, GenericGrowthGroup) and \ + not (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + return + + if set(self.variable_names()).isdisjoint(set(other.variable_names())): + from sage.categories.cartesian_product import cartesian_product + return cartesian_product([self, other]) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth + group. + + INPUT: + + Nothing. + + OUTPUT: + + An empty tuple. + + .. NOTE:: + + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).gens_monomial() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^QQ').gens_monomial() + (x,) + sage: GrowthGroup('QQ^x').gens_monomial() + () + """ + return tuple() + + + def gens(self): + r""" + Return a tuple of all generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple whose entries are growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gens() + (x,) + sage: GrowthGroup('log(x)^ZZ').gens() + (log(x),) + """ + return (self(raw_element=self.base().one()),) + + + def gen(self, n=0): + r""" + Return the `n`-th generator (as a group) of this growth group. + + INPUT: + + - ``n`` -- default: `0`. + + OUTPUT: + + A :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gen() + x + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.gen() + Traceback (most recent call last): + ... + IndexError: tuple index out of range + """ + return self.gens()[n] + + + def ngens(self): + r""" + Return the number of generators (as a group) of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A Python integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.ngens() + 1 + sage: GrowthGroup('log(x)^ZZ').ngens() + 1 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.ngens() + 0 + """ + return len(self.gens()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).variable_names() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').variable_names() + ('x',) + sage: GrowthGroup('log(x)^ZZ').variable_names() + ('x',) + + :: + + sage: GrowthGroup('QQ^x').variable_names() + ('x',) + sage: GrowthGroup('QQ^(x*log(x))').variable_names() + ('x',) + """ + return self._var_.variable_names() + + + CartesianProduct = CartesianProductGrowthGroups + + +from sage.categories.pushout import ConstructionFunctor +class AbstractGrowthGroupFunctor(ConstructionFunctor): + r""" + A base class for the functors constructing growth groups. + + INPUT: + + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + + - ``domain`` -- a category. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('z^QQ').construction()[0] # indirect doctest + MonomialGrowthGroup[z] + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + """ + + _functor_name = 'AbstractGrowthGroup' + + rank = 13 + + def __init__(self, var, domain): + r""" + See :class:`AbstractGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import AbstractGrowthGroupFunctor + sage: AbstractGrowthGroupFunctor('x', Groups()) + AbstractGrowthGroup[x] + """ + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var) + self.var = var + super(ConstructionFunctor, self).__init__( + domain, sage.categories.monoids.Monoids() & sage.categories.posets.Posets()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^t').construction()[0] # indirect doctest + ExponentialGrowthGroup[t] + """ + return '%s[%s]' % (self._functor_name, self.var) + + + def merge(self, other): + r""" + Merge this functor with ``other`` of possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F.merge(F) + ExponentialGrowthGroup[t] + sage: F.merge(G) is None + True + """ + if self == other: + return self + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F == F + True + sage: F == G + False + """ + return type(self) == type(other) and self.var == other.var + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F != F + False + sage: F != G + True + """ + return not self == other + + +class MonomialGrowthElement(GenericGrowthElement): + r""" + An implementation of monomial growth elements. + + INPUT: + + - ``parent`` -- a :class:`MonomialGrowthGroup`. + + - ``raw_element`` -- an element from the base ring of the parent. + + This ``raw_element`` is the exponent of the created monomial + growth element. + + A monomial growth element represents a term of the type + `\operatorname{variable}^{\operatorname{exponent}}`. The multiplication + corresponds to the addition of the exponents. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x') + sage: e1 = P(1); e1 + 1 + sage: e2 = P(raw_element=2); e2 + x^2 + sage: e1 == e2 + False + sage: P.le(e1, e2) + True + sage: P.le(e1, P.gen()) and P.le(P.gen(), e2) + True + """ + + @property + def exponent(self): + r""" + The exponent of this growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P(x^42).exponent + 42 + """ + return self._raw_element_ + + + def _repr_(self): + r""" + A representation string for this monomial growth element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: P(1)._repr_() + '1' + sage: P(x^5) # indirect doctest + x^5 + sage: P(x^(1/2)) # indirect doctest + x^(1/2) + + TESTS:: + + sage: P(x^-1) # indirect doctest + x^(-1) + sage: P(x^-42) # indirect doctest + x^(-42) + """ + from sage.rings.integer_ring import ZZ + from misc import repr_op + + var = repr(self.parent()._var_) + if self.exponent.is_zero(): + return '1' + elif self.exponent.is_one(): + return var + elif self.exponent in ZZ and self.exponent > 0: + return repr_op(var, '^') + str(self.exponent) + else: + return repr_op(var, '^') + '(' + str(self.exponent) + ')' + + + def _mul_(self, other): + r""" + Multiply this monomial growth element with another. + + INPUT: + + - ``other`` -- a :class:`MonomialGrowthElement` + + OUTPUT: + + The product as a :class:`MonomialGrowthElement`. + + .. NOTE:: + + Two monomial growth elements are multiplied by adding + their exponents. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: a = P(x^2) + sage: b = P(x^3) + sage: c = a._mul_(b); c + x^5 + sage: c == a*b + True + sage: a*b*a # indirect doctest + x^7 + """ + return self.parent()(raw_element=self.exponent + other.exponent) + + + def __invert__(self): + r""" + Return the multiplicative inverse of this monomial growth element. + + INPUT: + + Nothing. + + OUTPUT: + + The multiplicative inverse as a :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=2) + sage: e2 = e1.__invert__(); e2 + x^(-2) + sage: e2 == ~e1 + True + sage: Q = GrowthGroup('x^NN'); Q + Growth Group x^((Non negative integer semiring)) + sage: e3 = ~Q('x'); e3 + x^(-1) + sage: e3.parent() + Growth Group x^ZZ + """ + return self.parent()._create_element_in_extension_(-self.exponent) + + + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- a number. This can be anything that is a + valid right hand side of ``*`` with elements of the + parent's base. + + OUTPUT: + + The result of this exponentiation, a :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: x = P.gen() + sage: a = x^7; a + x^7 + sage: a^(1/2) + x^(7/2) + sage: (a^(1/2)).parent() + Growth Group x^QQ + sage: a^(1/7) + x + sage: (a^(1/7)).parent() + Growth Group x^QQ + sage: P = GrowthGroup('x^QQ') + sage: b = P.gen()^(7/2); b + x^(7/2) + sage: b^12 + x^42 + """ + return self.parent()._create_element_in_extension_(self.exponent * exponent) + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ') + sage: G('x').log_factor() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^QQ. + + :: + + sage: G = GrowthGroup('exp(x)^ZZ * x^ZZ') + sage: log(G('exp(x)'), base=2) + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(exp(x), base=2) a factor + 1/log(2) != 1 appeared, which is not contained in + Growth Group exp(x)^ZZ * x^ZZ. + """ + if self.is_one(): + return tuple() + coefficient = self.exponent + + var = str(self.parent()._var_) + + from misc import split_str_by_op + split = split_str_by_op(var, '^') + if len(split) == 2: + b, e = split + if base is None and b == 'e' or \ + base is not None and b == str(base): + return ((e, coefficient),) + + if var.startswith('exp('): + assert(var[-1] == ')') + v = var[4:-1] + else: + v = 'log(%s)' % (var,) + + if base is not None: + from sage.functions.log import log + coefficient = coefficient / log(base) + return ((v, coefficient),) + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + .. NOTE:: + + The parent of the result can be different from the parent + of this element. + + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G('x') + sage: x._rpow_element_(2) + Traceback (most recent call last): + ... + ValueError: Variable x is not a log of something. + + The previous example does not work since the result would not + live in a monomial growth group. When using + :meth:`~GenericGrowthElement.rpow`, this + case is handeled by the calling method :meth:`_rpow_`. + + :: + + sage: G = GrowthGroup('log(x)^ZZ') + sage: lx = G(raw_element=1); lx + log(x) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() + Growth Group x^ZZ + + :: + + sage: G = GrowthGroup('log(x)^SR') + sage: lx = G('log(x)') + sage: lx._rpow_element_(2) + x^(log(2)) + """ + var = str(self.parent()._var_) + if not(var.startswith('log(') and self.exponent.is_one()): + raise ValueError('Variable %s is not a log of something.' % (var,)) + new_var = var[4:-1] + if base == 'e': + from sage.rings.integer_ring import ZZ + M = MonomialGrowthGroup(ZZ, new_var) + return M(raw_element=ZZ(1)) + else: + from sage.functions.log import log + new_exponent = log(base) + M = MonomialGrowthGroup(new_exponent.parent(), new_var) + return M(raw_element=new_exponent) + + + def _le_(self, other): + r""" + Return whether this :class:`MonomialGrowthElement` is at most + (less than or equal to) ``other``. + + INPUT: + + - ``other`` -- a :class:`MonomialGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`MonomialGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 # indirect doctest + True + """ + return self.exponent <= other.exponent + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this monomial growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G(x^42)._substitute_({'x': SR.var('z')}) + z^42 + sage: _.parent() + Symbolic Ring + sage: G(x^3)._substitute_({'x': 2}) + 8 + sage: _.parent() + Integer Ring + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + > *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + try: + return self.parent()._var_._substitute_(rules) ** self.exponent + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class MonomialGrowthGroup(GenericGrowthGroup): + r""" + A growth group dealing with powers of a fixed object/symbol. + + The elements :class:`MonomialGrowthElement` of this group represent powers + of a fixed base; the group law is the multiplication, which corresponds + to the addition of the exponents of the monomials. + + INPUT: + + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). + + As monomials are represented by this group, the elements in + ``base`` are the exponents of these monomials. + + - ``var`` -- an object. + + The string representation of ``var`` acts as a base of the + monomials represented by this group. + + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x'); P + Growth Group x^ZZ + sage: MonomialGrowthGroup(ZZ, log(SR.var('y'))) + Growth Group log(y)^ZZ + + .. SEEALSO:: + + :class:`GenericGrowthGroup` + + TESTS:: + + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + """ + + # enable the category framework for elements + Element = MonomialGrowthElement + + + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas + + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (AdditiveMagmas(), Magmas(), False)] + + _determine_category_axiom_mapping_ = [ + ('AdditiveAssociative', 'Associative', False), + ('AdditiveUnital', 'Unital', False), + ('AdditiveInverse', 'Inverse', False), + ('AdditiveCommutative', 'Commutative', False)] + + + def _repr_short_(self): + r""" + A short representation string of this monomial growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'a') # indirect doctest + Growth Group a^ZZ + + + TESTS:: + + sage: MonomialGrowthGroup(ZZ, 'a')._repr_short_() + 'a^ZZ' + sage: MonomialGrowthGroup(QQ, 'a')._repr_short_() + 'a^QQ' + sage: MonomialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'a^QQ[x]' + """ + from misc import parent_to_repr_short, repr_op + return repr_op(self._var_, '^', parent_to_repr_short(self.base())) + + + def _convert_(self, data): + r""" + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + An element of the base ring or ``None`` (when no such element + can be constructed). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P._convert_('icecream') is None + True + sage: P(1) # indirect doctest + 1 + sage: P('x') # indirect doctest + x + + :: + + sage: P(x) # indirect doctest + x + sage: P(x^-333) # indirect doctest + x^(-333) + sage: P(log(x)^2) # indirect doctest + Traceback (most recent call last): + ... + ValueError: log(x)^2 is not in Growth Group x^ZZ. + + :: + + sage: PR. = ZZ[]; x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: P(x^2) # indirect doctest + x^2 + + :: + + sage: PSR. = ZZ[[]] + sage: P(x^42) # indirect doctest + x^42 + sage: P(x^12 + O(x^17)) + Traceback (most recent call last): + ... + ValueError: x^12 + O(x^17) is not in Growth Group x^ZZ. + + :: + + sage: R. = ZZ[] + sage: P(x^4242) # indirect doctest + x^4242 + sage: P(w^4242) # indirect doctest + Traceback (most recent call last): + ... + ValueError: w^4242 is not in Growth Group x^ZZ. + + :: + + sage: PSR. = ZZ[[]] + sage: P(x^7) # indirect doctest + x^7 + sage: P(w^7) # indirect doctest + Traceback (most recent call last): + ... + ValueError: w^7 is not in Growth Group x^ZZ. + + :: + + sage: P('x^7') + x^7 + sage: P('1/x') + x^(-1) + sage: P('x^(-2)') + x^(-2) + sage: P('x^-2') + x^(-2) + + :: + + sage: P('1') + 1 + + :: + + sage: GrowthGroup('x^QQ')(GrowthGroup('x^ZZ')(1)) + 1 + """ + if data == 1 or data == '1': + return self.base().zero() + var = repr(self._var_) + if str(data) == var: + return self.base().one() + + try: + P = data.parent() + except AttributeError: + if var not in str(data): + return # this has to end here + from sage.symbolic.ring import SR + return self._convert_(SR(data)) + + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import PolynomialRing_general + from sage.rings.polynomial.multi_polynomial_ring_generic import \ + MPolynomialRing_generic + from sage.rings.power_series_ring import PowerSeriesRing_generic + import operator + if isinstance(P, SymbolicRing): + if data.operator() == operator.pow: + base, exponent = data.operands() + if str(base) == var: + return exponent + elif isinstance(P, (PolynomialRing_general, MPolynomialRing_generic)): + if data.is_monomial() and len(data.variables()) == 1: + if var == str(data.variables()[0]): + return data.degree() + elif isinstance(P, PowerSeriesRing_generic): + if hasattr(data, 'variables') and len(data.variables()) == 1: + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + if var == str(data.variables()[0]): + return data.degree() + elif len(P.variable_names()) == 1 and \ + var == str(data.variable()[0]): + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + return data.degree() + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth + group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple containing elements of this growth group. + + .. NOTE:: + + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').gens_monomial() + (x,) + sage: GrowthGroup('log(x)^QQ').gens_monomial() + () + """ + if not self._var_.is_monomial(): + return tuple() + return (self(raw_element=self.base().one()),) + + + def construction(self): + r""" + Return the construction of this growth group. + + OUTPUT: + + A pair whose first entry is a + :class:`monomial construction functor ` + and its second entry the base. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').construction() + (MonomialGrowthGroup[x], Integer Ring) + """ + return MonomialGrowthGroupFunctor(self._var_), self.base() + + +class MonomialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`monomial growth groups `. + + INPUT: + + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: GrowthGroup('z^QQ').construction()[0] + MonomialGrowthGroup[z] + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('x^QQ') + sage: B = MonomialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group x^QQ[t] + """ + + _functor_name = 'MonomialGrowthGroup' + + + def __init__(self, var): + r""" + See :class:`MonomialGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroupFunctor + sage: MonomialGrowthGroupFunctor('x') + MonomialGrowthGroup[x] + """ + super(MonomialGrowthGroupFunctor, self).__init__(var, + sage.categories.commutative_additive_monoids.CommutativeAdditiveMonoids()) + + + def _apply_functor(self, base): + r""" + Apply this functor to the given ``base``. + + INPUT: + + - ``base`` - anything :class:`MonomialGrowthGroup` accepts. + + OUTPUT: + + A monomial growth group. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('z^QQ').construction() + sage: F(R) # indirect doctest + Growth Group z^QQ + """ + return MonomialGrowthGroup(base, self.var) + + +class ExponentialGrowthElement(GenericGrowthElement): + r""" + An implementation of exponential growth elements. + + INPUT: + + - ``parent`` -- an :class:`ExponentialGrowthGroup`. + + - ``raw_element`` -- an element from the base ring of the parent. + + This ``raw_element`` is the base of the created exponential + growth element. + + An exponential growth element represents a term of the type + `\operatorname{base}^{\operatorname{variable}}`. The multiplication + corresponds to the multiplication of the bases. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: e1 = P(1); e1 + 1 + sage: e2 = P(raw_element=2); e2 + 2^x + sage: e1 == e2 + False + sage: P.le(e1, e2) + True + sage: P.le(e1, P(1)) and P.le(P(1), e2) + True + """ + + @property + def base(self): + r""" + The base of this exponential growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: P(42^x).base + 42 + """ + return self._raw_element_ + + + def _repr_(self): + r""" + A representation string for this exponential growth element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') + sage: P(1)._repr_() + '1' + sage: P(5^x) # indirect doctest + 5^x + sage: P((1/2)^x) # indirect doctest + (1/2)^x + + TESTS:: + + sage: P((-1)^x) # indirect doctest + (-1)^x + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: G = ExponentialGrowthGroup(ZZ['x'], 'y'); G + Growth Group ZZ[x]^y + sage: G('(1-x)^y') + (-x + 1)^y + sage: G('(1+x)^y') + (x + 1)^y + """ + from sage.rings.integer_ring import ZZ + from misc import repr_op + + var = repr(self.parent()._var_) + if self.base.is_one(): + return '1' + return repr_op(str(self.base), '^', var) + + + def _mul_(self, other): + r""" + Multiply this exponential growth element with another. + + INPUT: + + - ``other`` -- a :class:`ExponentialGrowthElement` + + OUTPUT: + + The product as a :class:`ExponentialGrowthElement`. + + .. NOTE:: + + Two exponential growth elements are multiplied by + multiplying their bases. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(2^x) + sage: b = P(3^x) + sage: c = a._mul_(b); c + 6^x + sage: c == a*b + True + sage: a*b*a # indirect doctest + 12^x + """ + return self.parent()(raw_element=self.base * other.base) + + + def __invert__(self): + r""" + Return the multiplicative inverse of this exponential growth element. + + INPUT: + + Nothing. + + OUTPUT: + + The multiplicative inverse as a :class:`ExponentialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: e1 = P(raw_element=2) + sage: e2 = e1.__invert__(); e2 + (1/2)^x + sage: e2 == ~e1 + True + sage: e2.parent() + Growth Group QQ^x + + :: + + sage: (~P(raw_element=1)).parent() + Growth Group QQ^x + """ + return self.parent()._create_element_in_extension_(1 / self.base) + + + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- a number. This can be anything that is valid to be + on the right hand side of ``*`` with an elements of the + parent's base. + + OUTPUT: + + The result of this exponentiation as an :class:`ExponentialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(7^x); a + 7^x + sage: b = a^(1/2); b + sqrt(7)^x + sage: b.parent() + Growth Group SR^x + sage: b^12 + 117649^x + """ + return self.parent()._create_element_in_extension_(self.base ** exponent) + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second is a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G('4^x').log_factor(base=2) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(4^x, base=2) since x is not in + Growth Group QQ^x. + """ + if self.is_one(): + return tuple() + b = self.base + if base is None and hasattr(b, 'is_monomial') and b.is_monomial() and \ + b.variable_name() == 'e': + coefficient = b.valuation() + elif base is None and str(b) == 'e': + coefficient = self.parent().base().one() + else: + from sage.functions.log import log + coefficient = log(b, base=base) + + return ((str(self.parent()._var_), coefficient),) + + + def _le_(self, other): + r""" + Return whether this :class:`ExponentialGrowthElement` is at most + (less than or equal to) ``other``. + + INPUT: + + - ``other`` -- a :class:`ExponentialGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`ExponentialGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('ZZ^x') + sage: P_SR = GrowthGroup('SR^x') + sage: P_ZZ(2^x) <= P_SR(sqrt(3)^x)^2 # indirect doctest + True + """ + return bool(abs(self.base) <= abs(other.base)) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exponential growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G((1/2)^x)._substitute_({'x': SR.var('z')}) + (1/2)^z + sage: _.parent() + Symbolic Ring + sage: G((1/2)^x)._substitute_({'x': 2}) + 1/4 + sage: _.parent() + Rational Field + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + try: + return self.base ** self.parent()._var_._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class ExponentialGrowthGroup(GenericGrowthGroup): + r""" + A growth group dealing with expressions involving a fixed + variable/symbol as the exponent. + + The elements :class:`ExponentialGrowthElement` of this group + represent exponential functions with bases from a fixed base + ring; the group law is the multiplication. + + INPUT: + + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). + + As exponential expressions are represented by this group, + the elements in ``base`` are the bases of these exponentials. + + - ``var`` -- an object. + + The string representation of ``var`` acts as an exponent of the + elements represented by this group. + + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: P = ExponentialGrowthGroup(QQ, 'x'); P + Growth Group QQ^x + + .. SEEALSO:: + + :class:`GenericGrowthGroup` + """ + + # enable the category framework for elements + Element = ExponentialGrowthElement + + + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.groups import Groups + from sage.categories.division_rings import DivisionRings + + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (Magmas(), Magmas(), False), + (DivisionRings(), Groups(), False)] + + _determine_category_axiom_mapping_ = [ + ('Associative', 'Associative', False), + ('Unital', 'Unital', False), + ('Inverse', 'Inverse', False), + ('Commutative', 'Commutative', False)] + + + def _repr_short_(self): + r""" + A short representation string of this exponential growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'a') # indirect doctest + Growth Group QQ^a + + + TESTS:: + + sage: ExponentialGrowthGroup(QQ, 'a')._repr_short_() + 'QQ^a' + sage: ExponentialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'QQ[x]^a' + """ + from misc import parent_to_repr_short, repr_op + return repr_op(parent_to_repr_short(self.base()), '^', self._var_) + + + def _convert_(self, data): + r""" + Converts given ``data`` to something the constructor of the + element class accepts (``raw_element``). + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + An element of the base ring or ``None`` (when no such element + can be constructed). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') + sage: P._convert_('icecream') is None + True + sage: P(1) # indirect doctest + 1 + sage: P('2^x') # indirect doctest + 2^x + + :: + + sage: P(2^x) # indirect doctest + 2^x + sage: P((-333)^x) # indirect doctest + (-333)^x + sage: P(0) # indirect doctest + Traceback (most recent call last): + ... + ValueError: 0 is not in Growth Group QQ^x. + + :: + + sage: P('7^x') + 7^x + sage: P('(-2)^x') + (-2)^x + + :: + + sage: P = GrowthGroup('SR^x') + sage: P(sqrt(3)^x) + sqrt(3)^x + sage: P((3^(1/3))^x) + (3^(1/3))^x + sage: P(e^x) + e^x + sage: P(exp(2*x)) + (e^2)^x + + :: + + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')(1)) + 1 + """ + if data == '1' or isinstance(data, int) and data == 1: + return self.base().one() + var = repr(self._var_) + try: + P = data.parent() + except AttributeError: + if data == 1: + return self.base().one() + s = str(data) + if var not in s: + return # this has to end here + + elif s.endswith('^' + var): + return self.base()(s.replace('^' + var, '') + .replace('(', '').replace(')', '')) + else: + return # end of parsing + + from sage.symbolic.ring import SymbolicRing + import operator + from sage.symbolic.operators import mul_vararg + if isinstance(P, SymbolicRing): + op = data.operator() + if op == operator.pow: + base, exponent = data.operands() + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + elif isinstance(op, sage.functions.log.Function_exp): + from sage.functions.log import exp + base = exp(1) + exponent = data.operands()[0] + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + + elif data == 1: # can be expensive, so let's put it at the end + return self.base().one() + + + def some_elements(self): + r""" + Return some elements of this exponential growth group. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('QQ^z').some_elements()) + ((1/2)^z, (-1/2)^z, 2^z, (-2)^z, 1, (-1)^z, + 42^z, (2/3)^z, (-2/3)^z, (3/2)^z, (-3/2)^z, ...) + """ + return iter(self.element_class(self, e) + for e in self.base().some_elements() if e != 0) + + + def gens(self): + r""" + Return a tuple of all generators of this exponential growth + group. + + INPUT: + + Nothing. + + OUTPUT: + + An empty tuple. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: E = GrowthGroup('ZZ^x') + sage: E.gens() + () + """ + return tuple() + + + def construction(self): + r""" + Return the construction of this growth group. + + OUTPUT: + + A pair whose first entry is an + :class:`exponential construction functor ` + and its second entry the base. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^x').construction() + (ExponentialGrowthGroup[x], Rational Field) + """ + return ExponentialGrowthGroupFunctor(self._var_), self.base() + + +class ExponentialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`exponential growth groups `. + + INPUT: + + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: GrowthGroup('QQ^z').construction()[0] + ExponentialGrowthGroup[z] + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = ExponentialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group QQ[t]^x + """ + + _functor_name = 'ExponentialGrowthGroup' + + + def __init__(self, var): + r""" + See :class:`ExponentialGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroupFunctor + sage: ExponentialGrowthGroupFunctor('x') + ExponentialGrowthGroup[x] + """ + super(ExponentialGrowthGroupFunctor, self).__init__(var, + sage.categories.monoids.Monoids()) + + + def _apply_functor(self, base): + r""" + Apply this functor to the given ``base``. + + INPUT: + + - ``base`` - anything :class:`ExponentialGrowthGroup` accepts. + + OUTPUT: + + An exponential growth group. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('QQ^z').construction() + sage: F(R) # indirect doctest + Growth Group QQ^z + """ + return ExponentialGrowthGroup(base, self.var) + + +class GrowthGroupFactory(sage.structure.factory.UniqueFactory): + r""" + A factory creating asymptotic growth groups. + + INPUT: + + - ``specification`` -- a string. + + - keyword arguments are passed on to the growth group + constructor. + If the keyword ``ignore_variables`` is not specified, then + ``ignore_variables=('e',)`` (to ignore ``e`` as a variable name) + is used. + + OUTPUT: + + An asymptotic growth group. + + .. NOTE:: + + An instance of this factory is available as ``GrowthGroup``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') + Growth Group x^ZZ + sage: GrowthGroup('log(x)^QQ') + Growth Group log(x)^QQ + + This factory can also be used to construct Cartesian products + of growth groups:: + + sage: GrowthGroup('x^ZZ * y^ZZ') + Growth Group x^ZZ * y^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + Growth Group x^ZZ * log(x)^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ') + Growth Group x^ZZ * log(x)^ZZ * y^QQ + sage: GrowthGroup('QQ^x * x^ZZ * y^QQ * QQ^z') + Growth Group QQ^x * x^ZZ * y^QQ * QQ^z + sage: GrowthGroup('exp(x)^ZZ * x^ZZ') + Growth Group exp(x)^ZZ * x^ZZ + sage: GrowthGroup('(e^x)^ZZ * x^ZZ') + Growth Group (e^x)^ZZ * x^ZZ + + TESTS:: + + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ') + sage: G, G._var_ + (Growth Group (e^(n*log(n)))^ZZ, e^(n*log(n))) + sage: G = GrowthGroup('(e^n)^ZZ') + sage: G, G._var_ + (Growth Group (e^n)^ZZ, e^n) + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ') + sage: G, tuple(F._var_ for F in G.cartesian_factors()) + (Growth Group (e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ, + (e^(n*log(n)), e^n, n, log(n))) + + sage: TestSuite(GrowthGroup('x^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('QQ^y')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('x^QQ * log(x)^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + """ + def create_key_and_extra_args(self, specification, **kwds): + r""" + Given the arguments and keyword, create a key that uniquely + determines this object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup.create_key_and_extra_args('asdf') + Traceback (most recent call last): + ... + ValueError: 'asdf' is not a valid substring of 'asdf' describing a growth group. + """ + from misc import split_str_by_op + factors = split_str_by_op(specification, '*') + factors = tuple(f.replace('**', '^') for f in factors) + + for f in factors: + if '^' not in f: + raise ValueError("'%s' is not a valid substring of '%s' describing " + "a growth group." % (f, specification)) + + kwds.setdefault('ignore_variables', ('e',)) + + return factors, kwds + + + def create_object(self, version, factors, **kwds): + r""" + Create an object from the given arguments. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('as^df') # indirect doctest + Traceback (most recent call last): + ... + ValueError: 'as^df' is not a valid substring of as^df + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'as'. + >> *previous* SyntaxError: unexpected EOF while parsing (, line 1) + > *and* ValueError: Cannot create a parent out of 'df'. + >> *previous* NameError: name 'df' is not defined + sage: GrowthGroup('x^y^z') + Traceback (most recent call last): + ... + ValueError: 'x^y^z' is an ambigous substring of + a growth group description of 'x^y^z'. + Use parentheses to make it unique. + sage: GrowthGroup('(x^y)^z') + Traceback (most recent call last): + ... + ValueError: '(x^y)^z' is not a valid substring of (x^y)^z + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x^y'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'z'. + >> *previous* NameError: name 'z' is not defined + sage: GrowthGroup('x^(y^z)') + Traceback (most recent call last): + ... + ValueError: 'x^(y^z)' is not a valid substring of x^(y^z) + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'y^z'. + >> *previous* NameError: name 'y' is not defined + """ + from misc import repr_short_to_parent, split_str_by_op + groups = [] + for factor in factors: + split = split_str_by_op(factor, '^') + if len(split) != 2: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'. Use parentheses to make it " + "unique." % (factor, ' * '.join(factors))) + + b, e = split + try: + B = repr_short_to_parent(b) + except ValueError as exc_b: + B = None + try: + E = repr_short_to_parent(e) + except ValueError as exc_e: + E = None + + if B is None and E is None: + from misc import combine_exceptions + raise combine_exceptions( + ValueError("'%s' is not a valid substring of %s describing " + "a growth group." % (factor, ' * '.join(factors))), + exc_b, exc_e) + elif B is None and E is not None: + groups.append(MonomialGrowthGroup(E, b, **kwds)) + elif B is not None and E is None: + groups.append(ExponentialGrowthGroup(B, e, **kwds)) + else: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'." % (factor, ' * '.join(factors))) + + if len(groups) == 1: + return groups[0] + + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(groups) + + +GrowthGroup = GrowthGroupFactory("GrowthGroup") +r""" +A factory for growth groups. +This is an instance of :class:`GrowthGroupFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/asymptotic/growth_group_cartesian.py b/src/sage/rings/asymptotic/growth_group_cartesian.py new file mode 100644 index 00000000000..05d5f2da9b0 --- /dev/null +++ b/src/sage/rings/asymptotic/growth_group_cartesian.py @@ -0,0 +1,1257 @@ +r""" +Cartesian Products of Growth Groups + +See :doc:`growth_group` for a description. + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + +.. WARNING:: + + As this code is experimental, warnings are thrown when a growth + group is created for the first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(ZZ) + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group Generic(ZZ) + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group x^ZZ * log(x)^ZZ + +TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^ZZ'); A + Growth Group QQ^x * x^ZZ + sage: A.construction() + (The cartesian_product functorial construction, + (Growth Group QQ^x, Growth Group x^ZZ)) + sage: A.construction()[1][0].construction() + (ExponentialGrowthGroup[x], Rational Field) + sage: A.construction()[1][1].construction() + (MonomialGrowthGroup[x], Integer Ring) + sage: B = GrowthGroup('x^ZZ * y^ZZ'); B + Growth Group x^ZZ * y^ZZ + sage: B.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group y^ZZ)) + sage: C = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ'); C + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: C.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ * log(x)^ZZ, Growth Group y^ZZ)) + sage: C.construction()[1][0].construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group log(x)^ZZ)) + sage: C.construction()[1][1].construction() + (MonomialGrowthGroup[y], Integer Ring) + +:: + + sage: cm = sage.structure.element.get_coercion_model() + sage: D = GrowthGroup('QQ^x * x^QQ') + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^QQ + sage: E = GrowthGroup('ZZ^x * x^QQ') + sage: cm.record_exceptions() # not tested, see #19411 + sage: cm.common_parent(A, E) + Growth Group QQ^x * x^QQ + sage: for t in cm.exception_stack(): # not tested, see #19411 + ....: print t + +:: + + sage: A.an_element() + (1/2)^x*x + sage: tuple(E.an_element()) + (1, x^(1/2)) + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +class CartesianProductFactory(sage.structure.factory.UniqueFactory): + r""" + Create various types of cartesian products depending on its input. + + INPUT: + + - ``growth_groups`` -- a tuple (or other iterable) of growth groups. + + - ``order`` -- (default: ``None``) if specified, then this order + is taken for comparing two cartesian product elements. If ``order`` is + ``None`` this is determined automatically. + + .. NOTE:: + + The cartesian product of growth groups is again a growth + group. In particular, the resulting structure is partially + ordered. + + The order on the product is determined as follows: + + - Cartesian factors with respect to the same variable are + ordered lexicographically. This causes + ``GrowthGroup('x^ZZ * log(x)^ZZ')`` and + ``GrowthGroup('log(x)^ZZ * x^ZZ')`` to produce two + different growth groups. + + - Factors over different variables are equipped with the + product order (i.e. the comparison is component-wise). + + Also, note that the sets of variables of the cartesian + factors have to be either equal or disjoint. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ'); A + Growth Group x^ZZ + sage: B = GrowthGroup('log(x)^ZZ'); B + Growth Group log(x)^ZZ + sage: C = cartesian_product([A, B]); C # indirect doctest + Growth Group x^ZZ * log(x)^ZZ + sage: C._le_ == C.le_lex + True + sage: D = GrowthGroup('y^ZZ'); D + Growth Group y^ZZ + sage: E = cartesian_product([A, D]); E # indirect doctest + Growth Group x^ZZ * y^ZZ + sage: E._le_ == E.le_product + True + sage: F = cartesian_product([C, D]); F # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: F._le_ == F.le_product + True + sage: cartesian_product([A, E]); G # indirect doctest + Traceback (most recent call last): + ... + ValueError: The growth groups (Growth Group x^ZZ, Growth Group x^ZZ * y^ZZ) + need to have pairwise disjoint or equal variables. + sage: cartesian_product([A, B, D]) # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: CartesianProductFactory('factory')([A, B], category=Groups() & Posets()) + Growth Group x^ZZ * log(x)^ZZ + sage: CartesianProductFactory('factory')([], category=Sets()) + Traceback (most recent call last): + ... + TypeError: Cannot create cartesian product without factors. + """ + def create_key_and_extra_args(self, growth_groups, category, **kwds): + r""" + Given the arguments and keywords, create a key that uniquely + determines this object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ') + sage: CartesianProductFactory('factory').create_key_and_extra_args( + ....: [A], category=Sets(), order='blub') + (((Growth Group x^ZZ,), Category of sets), {'order': 'blub'}) + """ + return (tuple(growth_groups), category), kwds + + + def create_object(self, version, args, **kwds): + r""" + Create an object from the given arguments. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: cartesian_product([GrowthGroup('x^ZZ')]) # indirect doctest + Growth Group x^ZZ + """ + growth_groups, category = args + if not growth_groups: + raise TypeError('Cannot create cartesian product without factors.') + order = kwds.pop('order', None) + if order is not None: + return GenericProduct(growth_groups, category, order=order, **kwds) + + vg = tuple((g.variable_names(), g) for g in growth_groups) + + # check if all groups have a variable + if not all(v for v, _ in vg): + raise NotImplementedError('Growth groups %s have no variable.' % + tuple(g for g in growth_groups + if not g.variable_names())) + + # sort by variables + from itertools import groupby, product + vgs = tuple((v, tuple(gs)) for v, gs in + groupby(sorted(vg, key=lambda k: k[0]), key=lambda k: k[0])) + + # check whether variables are pairwise disjoint + for u, w in product(iter(v for v, _ in vgs), repeat=2): + if u != w and not set(u).isdisjoint(set(w)): + raise ValueError('The growth groups %s need to have pairwise ' + 'disjoint or equal variables.' % (growth_groups,)) + + # build cartesian products + u_groups = list() + for _, gs in vgs: + gs = tuple(g for _, g in gs) + if len(gs) > 1: + u_groups.append(UnivariateProduct(gs, category, **kwds)) + else: + u_groups.append(gs[0]) + + if len(u_groups) > 1: + m_group = MultivariateProduct(tuple(u_groups), category, **kwds) + else: + m_group = u_groups[0] + return m_group + + +CartesianProductGrowthGroups = CartesianProductFactory('CartesianProductGrowthGroups') + + +from sage.combinat.posets.cartesian_product import CartesianProductPoset +from growth_group import GenericGrowthGroup +class GenericProduct(CartesianProductPoset, GenericGrowthGroup): + r""" + A cartesian product of growth groups. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: C = cartesian_product([P, L], order='lex'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ + sage: C.an_element() + x^(1/2)*log(x) + + :: + + sage: Px = GrowthGroup('x^QQ') + sage: Lx = GrowthGroup('log(x)^ZZ') + sage: Cx = cartesian_product([Px, Lx], order='lex') # indirect doctest + sage: Py = GrowthGroup('y^QQ') + sage: C = cartesian_product([Cx, Py], order='product'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ * y^QQ + sage: C.an_element() + x^(1/2)*log(x)*y^(1/2) + + .. SEEALSO:: + + :class:`~sage.sets.cartesian_product.CartesianProduct`, + :class:`~sage.combinat.posets.cartesian_product.CartesianProductPoset`. + """ + + __classcall__ = CartesianProductPoset.__classcall__ + + + def __init__(self, sets, category, **kwds): + r""" + See :class:`GenericProduct` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * y^ZZ') # indirect doctest + Growth Group x^ZZ * y^ZZ + """ + order = kwds.pop('order') + CartesianProductPoset.__init__(self, sets, category, order, **kwds) + + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + from growth_group import Variable + Vars = Variable(tuple(v for v, _ in groupby(vars)), repr=self._repr_short_()) + + GenericGrowthGroup.__init__(self, sets[0], Vars, self.category(), **kwds) + + + __hash__ = CartesianProductPoset.__hash__ + + + def some_elements(self): + r""" + Return some elements of this cartesian product of growth groups. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^y * x^QQ * log(x)^ZZ') + sage: tuple(islice(G.some_elements(), 10)) + (x^(1/2)*(1/2)^y, + x^(-1/2)*log(x)*(-1/2)^y, + x^2*log(x)^(-1)*2^y, + x^(-2)*log(x)^2*(-2)^y, + log(x)^(-2), + x*log(x)^3*(-1)^y, + x^(-1)*log(x)^(-3)*42^y, + x^42*log(x)^4*(2/3)^y, + x^(2/3)*log(x)^(-4)*(-2/3)^y, + x^(-2/3)*log(x)^5*(3/2)^y) + """ + from itertools import izip + return iter( + self(c) for c in + izip(*tuple(F.some_elements() for F in self.cartesian_factors()))) + + + def _create_element_in_extension_(self, element): + r""" + Create an element in an extension of this cartesian product of + growth groups which is chosen according to the input ``element``. + + INPUT: + + - ``element`` -- a tuple. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ * log(z)^ZZ') + sage: z = G('z')[0] + sage: lz = G('log(z)')[1] + sage: G._create_element_in_extension_((z^3, lz)).parent() + Growth Group z^ZZ * log(z)^ZZ + sage: G._create_element_in_extension_((z^(1/2), lz)).parent() + Growth Group z^QQ * log(z)^ZZ + + :: + + sage: G._create_element_in_extension_((3, 3, 3)) + Traceback (most recent call last): + ... + ValueError: Cannot create (3, 3, 3) as a cartesian product like + Growth Group z^ZZ * log(z)^ZZ. + """ + factors = self.cartesian_factors() + if len(element) != len(factors): + raise ValueError('Cannot create %s as a cartesian product like %s.' % + (element, self)) + + if all(n.parent() is f for n, f in zip(element, factors)): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(tuple(n.parent() for n in element), + category=self.category()) + return parent(element) + + + def _element_constructor_(self, data): + r""" + Converts the given object to an element of this cartesian + product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G_log = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + + Conversion from the symbolic ring works:: + + sage: x,y = var('x y') + sage: G(x^-3*y^2) + x^(-3)*y^2 + sage: G(x^4), G(y^2) + (x^4, y^2) + sage: G(1) + 1 + + Even more complex expressions can be parsed:: + + sage: G_log(x^42*log(x)^-42*y^42) + x^42*log(x)^(-42)*y^42 + + TESTS:: + + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G('x'), G('y') + (x, y) + + :: + + sage: G_log(log(x)) + log(x) + + :: + + sage: G(G.cartesian_factors()[0].gen()) + x + + :: + + sage: GrowthGroup('QQ^x * x^QQ')(['x^(1/2)']) + x^(1/2) + sage: l = GrowthGroup('x^ZZ * log(x)^ZZ')(['x', 'log(x)']); l + x*log(x) + sage: type(l) + + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)', 'x^55']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)', 'x^55'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + + TESTS:: + + sage: n = GrowthGroup('n^ZZ * log(n)^ZZ')('n') + sage: G = GrowthGroup('QQ^n * n^ZZ * log(n)^ZZ') + sage: G(n).value + (1, n, 1) + """ + def convert_factors(data, raw_data): + try: + return self._convert_factors_(data) + except ValueError as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('%s is not in %s.' % (raw_data, self)), e) + + if data == 1: + return self.one() + + elif data is None: + raise ValueError('%s cannot be converted.' % (data,)) + + elif type(data) == self.element_class and data.parent() == self: + return data + + elif isinstance(data, str): + from misc import split_str_by_op + return convert_factors(split_str_by_op(data, '*'), data) + + elif hasattr(data, 'parent'): + P = data.parent() + + if P is self: + return data + + elif P is sage.symbolic.ring.SR: + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return convert_factors(data.operands(), data) + + # room for other parents (e.g. polynomial ring et al.) + + try: + return super(GenericProduct, self)._element_constructor_(data) + except (TypeError, ValueError): + pass + if isinstance(data, (tuple, list, + sage.sets.cartesian_product.CartesianProduct.Element)): + return convert_factors(tuple(data), data) + + return convert_factors((data,), data) + + + _repr_ = GenericGrowthGroup._repr_ + + + def _repr_short_(self): + r""" + A short (shorter than :meth:`._repr_`) representation string + for this cartesian product of growth groups. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex')._repr_short_() + 'x^QQ * log(x)^ZZ' + """ + return ' * '.join(S._repr_short_() for S in self.cartesian_factors()) + + + def _convert_factors_(self, factors): + r""" + Helper method. Try to convert some ``factors`` to an + element of one of the cartesian factors and return the product of + all these factors. + + INPUT: + + - ``factors`` -- a tuple or other iterable. + + OUTPUT: + + An element of this cartesian product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^QQ * y^QQ') + sage: e1 = G._convert_factors_([x^2]) + sage: (e1, e1.parent()) + (x^2, Growth Group x^ZZ * log(x)^QQ * y^QQ) + """ + from sage.misc.misc_c import prod + + def get_factor(data): + for factor in self.cartesian_factors(): + try: + return factor, factor(data) + except (ValueError, TypeError): + pass + raise ValueError('%s is not in any of the factors of %s' % (data, self)) + + return prod(self.cartesian_injection(*get_factor(f)) + for f in factors) + + + def cartesian_injection(self, factor, element): + r""" + Inject the given element into this cartesian product at the given factor. + + INPUT: + + - ``factor`` -- a growth group (a factor of this cartesian product). + + - ``element`` -- an element of ``factor``. + + OUTPUT: + + An element of this cartesian product. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ') + sage: G.cartesian_injection(G.cartesian_factors()[1], 'y^7') + y^7 + """ + return self(tuple((f.one() if f != factor else element) + for f in self.cartesian_factors())) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^QQ') + sage: B = GrowthGroup('QQ^x * x^ZZ') + sage: A.has_coerce_map_from(B) # indirect doctest + True + sage: B.has_coerce_map_from(A) # indirect doctest + False + """ + if CartesianProductPoset.has_coerce_map_from(self, S): + return True + + elif isinstance(S, GenericProduct): + factors = S.cartesian_factors() + else: + factors = (S,) + + if all(any(g.has_coerce_map_from(f) for g in self.cartesian_factors()) + for f in factors): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x * x^ZZ') + sage: B = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: A._pushout_(B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: pushout(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: cm.discover_coercion(A, B) + ((map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group QQ^x * x^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ, + (map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group x^ZZ * log(x)^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ) + sage: cm.common_parent(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + + :: + + sage: C = GrowthGroup('QQ^x * x^QQ * y^ZZ') + sage: D = GrowthGroup('x^ZZ * log(x)^QQ * QQ^z') + sage: C._pushout_(D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: cm.common_parent(C, D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: A._pushout_(D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(B, D) + Growth Group x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, C) + Growth Group QQ^x * x^QQ * y^ZZ + sage: E = GrowthGroup('log(x)^ZZ * y^ZZ') + sage: cm.common_parent(A, E) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x * x^ZZ' and 'Growth Group log(x)^ZZ * y^ZZ' + + :: + + sage: F = GrowthGroup('z^QQ') + sage: pushout(C, F) + Growth Group QQ^x * x^QQ * y^ZZ * z^QQ + + :: + + sage: pushout(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + sage: cm.common_parent(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + """ + from growth_group import GenericGrowthGroup, AbstractGrowthGroupFunctor + from misc import merge_overlapping + from misc import underlying_class + + if isinstance(other, GenericProduct): + Ofactors = other.cartesian_factors() + elif isinstance(other, GenericGrowthGroup): + Ofactors = (other,) + elif (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + Ofactors = (other,) + else: + return + + def pushout_univariate_factors(self, other, var, Sfactors, Ofactors): + try: + return merge_overlapping( + Sfactors, Ofactors, + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + cm = sage.structure.element.get_coercion_model() + try: + Z = cm.common_parent(*Sfactors+Ofactors) + return (Z,), (Z,) + except TypeError: + pass + + def subfactors(F): + for f in F: + if isinstance(f, GenericProduct): + for g in subfactors(f.cartesian_factors()): + yield g + else: + yield f + + try: + return merge_overlapping( + tuple(subfactors(Sfactors)), tuple(subfactors(Ofactors)), + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + from sage.structure.coerce_exceptions import CoercionException + raise CoercionException( + 'Cannot construct the pushout of %s and %s: The factors ' + 'with variables %s are not overlapping, ' + 'no common parent was found, and ' + 'splitting the factors was unsuccessful.' % (self, other, var)) + + + class it: + def __init__(self, it): + self.it = it + self.var = None + self.factors = None + def next(self): + try: + self.var, factors = next(self.it) + self.factors = tuple(factors) + except StopIteration: + self.var = None + self.factors = tuple() + + from itertools import groupby + S = it(groupby(self.cartesian_factors(), key=lambda k: k.variable_names())) + O = it(groupby(Ofactors, key=lambda k: k.variable_names())) + + newS = [] + newO = [] + + S.next() + O.next() + while S.var is not None or O.var is not None: + if S.var is not None and S.var < O.var: + newS.extend(S.factors) + newO.extend(S.factors) + S.next() + elif O.var is not None and S.var > O.var: + newS.extend(O.factors) + newO.extend(O.factors) + O.next() + else: + SL, OL = pushout_univariate_factors(self, other, S.var, + S.factors, O.factors) + newS.extend(SL) + newO.extend(OL) + S.next() + O.next() + + assert(len(newS) == len(newO)) + + if (len(self.cartesian_factors()) == len(newS) and + len(other.cartesian_factors()) == len(newO)): + # We had already all factors in each of the self and + # other, thus splitting it in subproblems (one for + # each factor) is the strategy to use. If a pushout is + # possible :func:`sage.categories.pushout.pushout` + # will manage this by itself. + return + + from sage.categories.pushout import pushout + from sage.categories.cartesian_product import cartesian_product + return pushout(cartesian_product(newS), cartesian_product(newO)) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple containing elements of this growth group. + + .. NOTE:: + + This method calls the ``gens_monomial()`` method on the + individual factors of this cartesian product and + concatenates the respective outputs. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ') + sage: G.gens_monomial() + (x, y) + + TESTS:: + + sage: all(g.parent() == G for g in G.gens_monomial()) + True + """ + return sum(iter( + tuple(self.cartesian_injection(factor, g) for g in factor.gens_monomial()) + for factor in self.cartesian_factors()), + tuple()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ').variable_names() + ('x', 'y', 'z') + """ + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + return tuple(v for v, _ in groupby(vars)) + + + class Element(CartesianProductPoset.Element): + + from growth_group import _is_lt_one_ + is_lt_one = _is_lt_one_ + + + def _repr_(self): + r""" + A representation string for this cartesian product element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex').an_element()._repr_() + 'x^(1/2)*log(x)' + """ + s = '*'.join(repr(v) for v in self.value if not v.is_one()) + if s == '': + return '1' + return s + + + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given + ``exponent``. + + INPUT: + + - ``exponent`` -- a number. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ * z^ZZ') + sage: x, y, z = G.gens_monomial() + sage: (x^5 * y * z^5)^(1/5) # indirect doctest + x*y^(1/5)*z + + :: + + sage: G = GrowthGroup('x^QQ * log(x)^QQ'); x = G('x') + sage: (x^(21/5) * log(x)^7)^(1/42) # indirect doctest + x^(1/10)*log(x)^(1/6) + """ + return self.parent()._create_element_in_extension_( + tuple(x ** exponent for x in self.cartesian_factors())) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further and is not the identity (`1`). + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + sage: x, y = G.gens_monomial() + sage: x.factors() + (x,) + sage: f = (x * y).factors(); f + (x, y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group y^ZZ) + sage: f = (x * log(x)).factors(); f + (x, log(x)) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ) + + :: + + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ * y^QQ') + sage: x, y = G.gens_monomial() + sage: f = (x * log(x) * y).factors(); f + (x, log(x), y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ, Growth Group y^QQ) + + :: + + sage: G.one().factors() + () + """ + return sum(iter(f.factors() + for f in self.cartesian_factors() + if f != f.parent().one()), + tuple()) + + + from growth_group import _log_factor_, _log_ + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + """ + if self.is_one(): + return tuple() + + def try_create_growth(g): + try: + return self.parent()(g) + except (TypeError, ValueError): + return g + + try: + return sum(iter(tuple((try_create_growth(g), c) + for g, c in factor._log_factor_(base=base)) + for factor in self.cartesian_factors() + if factor != factor.parent().one()), + tuple()) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot build log(%s) in %s.' % + (self, self.parent())), e) + + + from growth_group import _rpow_ + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + .. NOTE:: + + The parent of the result can be different from the parent + of this element. + + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: lx = log(G('x')) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() + Growth Group x^ZZ + """ + factors = self.factors() + if len(factors) != 1: + raise ValueError # calling method has to deal with it... + from growth_group import MonomialGrowthGroup + factor = factors[0] + if not isinstance(factor.parent(), MonomialGrowthGroup): + raise ValueError # calling method has to deal with it... + return factor._rpow_element_(base) + + + def exp(self): + r""" + The exponential of this element. + + INPUT: + + Nothing. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ') + sage: x = G('x') + sage: exp(log(x)) + x + sage: exp(log(log(x))) + log(x) + + :: + + sage: exp(x) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct e^x in + Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ' and + 'Growth Group (e^x)^ZZ' + + TESTS:: + + sage: E = GrowthGroup("(e^y)^QQ * y^QQ * log(y)^QQ") + sage: y = E('y') + sage: log(exp(y)) + y + sage: exp(log(y)) + y + """ + return self.rpow('e') + + + def __invert__(self): + r""" + Return the multiplicative inverse of this cartesian product. + + OUTPUT: + + An growth element. + + .. NOTE:: + + The result may live in a larger parent than we started with. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('ZZ^x * x^ZZ') + sage: g = G('2^x * x^3') + sage: (~g).parent() + Growth Group QQ^x * x^ZZ + """ + return self.parent()._create_element_in_extension_( + tuple(~x for x in self.cartesian_factors())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this + cartesian product growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ * log(x)^QQ') + sage: G(x^3 * log(x)^5)._substitute_({'x': SR.var('z')}) + z^3*log(z)^5 + sage: _.parent() + Symbolic Ring + sage: G(x^3 * log(x)^5)._substitute_({'x': 2.2}) # rel tol 1e-6 + 3.24458458945 + sage: _.parent() + Real Field with 53 bits of precision + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ * log(x)^QQ. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ. + >> *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + from sage.symbolic.operators import mul_vararg + try: + return mul_vararg( + *tuple(x._substitute_(rules) + for x in self.cartesian_factors())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + CartesianProduct = CartesianProductGrowthGroups + + +class UnivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with the same variables. + + .. NOTE:: + + A univariate product of growth groups is ordered + lexicographically. This is motivated by the assumption + that univariate growth groups can be ordered in a chain + with respect to the growth they model (e.g. + ``x^ZZ * log(x)^ZZ``: polynomial growth dominates + logarithmic growth). + + .. SEEALSO:: + + :class:`MultivariateProduct`, + :class:`GenericProduct`. + """ + + def __init__(self, sets, category, **kwargs): + r""" + See :class:`UnivariateProduct` for details. + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * log(x)^ZZ')) # indirect doctest + + """ + super(UnivariateProduct, self).__init__( + sets, category, order='lex', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups + + +class MultivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with pairwise disjoint + (or equal) variable sets. + + .. NOTE:: + + A multivariate product of growth groups is ordered by + means of the product order, i.e. component-wise. This is + motivated by the assumption that different variables are + considered to be independent (e.g. ``x^ZZ * y^ZZ``). + + .. SEEALSO:: + + :class:`UnivariateProduct`, + :class:`GenericProduct`. + """ + def __init__(self, sets, category, **kwargs): + r""" + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * y^ZZ')) # indirect doctest + + """ + super(MultivariateProduct, self).__init__( + sets, category, order='product', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py new file mode 100644 index 00000000000..0b142d7698d --- /dev/null +++ b/src/sage/rings/asymptotic/misc.py @@ -0,0 +1,693 @@ +r""" +Asymptotic Expansions --- Miscellaneous + +AUTHORS: + +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Functions, Classes and Methods +============================== +""" + +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +def repr_short_to_parent(s): + r""" + Helper method for the growth group factory, which converts a short + representation string to a parent. + + INPUT: + + - ``s`` -- a string, short representation of a parent. + + OUTPUT: + + A parent. + + The possible short representations are shown in the examples below. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_short_to_parent + sage: repr_short_to_parent('ZZ') + Integer Ring + sage: repr_short_to_parent('QQ') + Rational Field + sage: repr_short_to_parent('SR') + Symbolic Ring + sage: repr_short_to_parent('NN') + Non negative integer semiring + + TESTS:: + + sage: repr_short_to_parent('abcdef') + Traceback (most recent call last): + ... + ValueError: Cannot create a parent out of 'abcdef'. + > *previous* NameError: name 'abcdef' is not defined + """ + from sage.misc.sage_eval import sage_eval + try: + P = sage_eval(s) + except Exception as e: + raise combine_exceptions( + ValueError("Cannot create a parent out of '%s'." % (s,)), e) + + from sage.misc.lazy_import import LazyImport + if type(P) is LazyImport: + P = P._get_object() + + from sage.structure.parent import is_Parent + if not is_Parent(P): + raise ValueError("'%s' does not describe a parent." % (s,)) + return P + + +def parent_to_repr_short(P): + r""" + Helper method which generates a short(er) representation string + out of a parent. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import parent_to_repr_short + sage: parent_to_repr_short(ZZ) + 'ZZ' + sage: parent_to_repr_short(QQ) + 'QQ' + sage: parent_to_repr_short(SR) + 'SR' + sage: parent_to_repr_short(ZZ['x']) + 'ZZ[x]' + sage: parent_to_repr_short(QQ['d, k']) + '(QQ[d, k])' + sage: parent_to_repr_short(QQ['e']) + 'QQ[e]' + sage: parent_to_repr_short(SR[['a, r']]) + '(SR[[a, r]])' + sage: parent_to_repr_short(Zmod(3)) + '(Ring of integers modulo 3)' + sage: parent_to_repr_short(Zmod(3)['g']) + '(Univariate Polynomial Ring in g over Ring of integers modulo 3)' + """ + def abbreviate(P): + if P is sage.rings.integer_ring.ZZ: + return 'ZZ' + elif P is sage.rings.rational_field.QQ: + return 'QQ' + elif P is sage.symbolic.ring.SR: + return 'SR' + raise ValueError('Cannot abbreviate %s.' % (P,)) + + poly = sage.rings.polynomial.polynomial_ring.is_PolynomialRing(P) or \ + sage.rings.polynomial.multi_polynomial_ring_generic.is_MPolynomialRing(P) + from sage.rings import multi_power_series_ring + power = sage.rings.power_series_ring.is_PowerSeriesRing(P) or \ + multi_power_series_ring.is_MPowerSeriesRing(P) + + if poly or power: + if poly: + op, cl = ('[', ']') + else: + op, cl = ('[[', ']]') + try: + s = abbreviate(P.base_ring()) + op + ', '.join(P.variable_names()) + cl + except ValueError: + s = str(P) + else: + try: + s = abbreviate(P) + except ValueError: + s = str(P) + + if ' ' in s: + s = '(' + s + ')' + return s + + +def split_str_by_op(string, op, strip_parentheses=True): + r""" + Split the given string into a tuple of substrings arising by + splitting by ``op`` and taking care of parentheses. + + INPUT: + + - ``string`` -- a string. + + - ``op`` -- a string. This is used by + :python:`str.split `. + Thus, if this is ``None``, then any whitespace string is a + separator and empty strings are removed from the result. + + - ``strip_parentheses`` -- (default: ``True``) a boolean. + + OUTPUT: + + A tuple of strings. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import split_str_by_op + sage: split_str_by_op('x^ZZ', '*') + ('x^ZZ',) + sage: split_str_by_op('log(x)^ZZ * y^QQ', '*') + ('log(x)^ZZ', 'y^QQ') + sage: split_str_by_op('log(x)**ZZ * y**QQ', '*') + ('log(x)**ZZ', 'y**QQ') + sage: split_str_by_op('a^b * * c^d', '*') + Traceback (most recent call last): + ... + ValueError: 'a^b * * c^d' is invalid since a '*' follows a '*'. + sage: split_str_by_op('a^b * (c*d^e)', '*') + ('a^b', 'c*d^e') + + :: + + sage: split_str_by_op('(a^b)^c', '^') + ('a^b', 'c') + sage: split_str_by_op('a^(b^c)', '^') + ('a', 'b^c') + + :: + + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=True) + ('a', 'b') + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=False) + ('(a)', '(b)') + sage: split_str_by_op(' ( t ) ', op='+', strip_parentheses=False) + ('( t )',) + + :: + + sage: split_str_by_op(' ( t ) ', op=None) + ('t',) + sage: split_str_by_op(' ( t )s', op=None) + ('(t)s',) + sage: split_str_by_op(' ( t ) s', op=None) + ('t', 's') + """ + factors = list() + balanced = True + if string and op is not None and string.startswith(op): + raise ValueError("'%s' is invalid since it starts with a '%s'." % + (string, op)) + for s in string.split(op): + if not s: + factors[-1] += op + balanced = False + continue + if not s.strip(): + raise ValueError("'%s' is invalid since a '%s' follows a '%s'." % + (string, op, op)) + if not balanced: + s = factors.pop() + (op if op else '') + s + balanced = s.count('(') == s.count(')') + factors.append(s) + + if not balanced: + raise ValueError("Parentheses in '%s' are not balanced." % (string,)) + + def strip(s): + s = s.strip() + if not s: + return s + if strip_parentheses and s[0] == '(' and s[-1] == ')': + s = s[1:-1] + return s.strip() + + return tuple(strip(f) for f in factors) + + +def repr_op(left, op, right=None): + r""" + Create a string ``left op right`` with + taking care of parentheses in its operands. + + INPUT: + + - ``left`` -- an element. + + - ``op`` -- a string. + + - ``right`` -- an alement. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_op + sage: repr_op('a^b', '^', 'c') + '(a^b)^c' + + TESTS:: + + sage: repr_op('a-b', '^', 'c') + '(a-b)^c' + sage: repr_op('a+b', '^', 'c') + '(a+b)^c' + """ + left = str(left) + right = str(right) if right is not None else '' + + def add_parentheses(s, op): + if op == '^': + signals = ('^', '/', '*', '-', '+', ' ') + else: + return s + if any(sig in s for sig in signals): + return '(%s)' % (s,) + else: + return s + + return add_parentheses(left, op) + op +\ + add_parentheses(right, op) + + +def combine_exceptions(e, *f): + r""" + Helper function which combines the messages of the given exceptions. + + INPUT: + + - ``e`` -- an exception. + + - ``*f`` -- exceptions. + + OUTPUT: + + An exception. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import combine_exceptions + sage: raise combine_exceptions(ValueError('Outer.'), TypeError('Inner.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: TypeError('Inner1.'), TypeError('Inner2.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner1. + > *and* TypeError: Inner2. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: combine_exceptions(TypeError('Middle.'), + ....: TypeError('Inner.'))) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Middle. + >> *previous* TypeError: Inner. + """ + import re + msg = ('\n *previous* ' + + '\n *and* '.join("%s: %s" % (ff.__class__.__name__, str(ff)) for ff in f)) + msg = re.sub(r'^([>]* \*previous\*)', r'>\1', msg, flags=re.MULTILINE) + msg = re.sub(r'^([>]* \*and\*)', r'>\1', msg, flags=re.MULTILINE) + msg = str(e.args if len(e.args) > 1 else e.args[0]) + msg + e.args = (msg,) + return e + + +def substitute_raise_exception(element, e): + r""" + Raise an error describing what went wrong with the substitution. + + INPUT: + + - ``element`` -- an element. + + - ``e`` -- an exception which is included in the raised error + message. + + OUTPUT: + + Raise an exception of the same type as ``e``. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import substitute_raise_exception + sage: substitute_raise_exception(x, Exception('blub')) + Traceback (most recent call last): + ... + Exception: Cannot substitute in x in Symbolic Ring. + > *previous* Exception: blub + """ + raise combine_exceptions( + type(e)('Cannot substitute in %s in %s.' % + (element, element.parent())), e) + + +def underlying_class(P): + r""" + Return the underlying class (class without the attached + categories) of the given instance. + + OUTPUT: + + A class. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import underlying_class + sage: type(QQ) + + sage: underlying_class(QQ) + + """ + cls = type(P) + if not hasattr(P, '_is_category_initialized') or not P._is_category_initialized(): + return cls + from sage.structure.misc import is_extension_type + if is_extension_type(cls): + return cls + + from sage.categories.sets_cat import Sets + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + return cls + + +def merge_overlapping(A, B, key=None): + r""" + Merge the two overlapping tuples/lists. + + INPUT: + + - ``A`` -- a list or tuple (type has to coincide with type of ``B``). + + - ``B`` -- a list or tuple (type has to coincide with type of ``A``). + + - ``key`` -- (default: ``None``) a function. If ``None``, then the + identity is used. This ``key``-function applied on an element + of the list/tuple is used for comparison. Thus elements with the + same key are considered as equal. + + OUTPUT: + + A pair of lists or tuples (depending on the type of ``A`` and ``B``). + + .. NOTE:: + + Suppose we can decompose the list `A=ac` and `B=cb` with + lists `a`, `b`, `c`, where `c` is nonempty. Then + :func:`merge_overlapping` returns the pair `(acb, acb)`. + + Suppose a ``key``-function is specified and `A=ac_A` and + `B=c_Bb`, where the list of keys of the elements of `c_A` + equals the list of keys of the elements of `c_B`. Then + :func:`merge_overlapping` returns the pair `(ac_Ab, ac_Bb)`. + + After unsuccessfully merging `A=ac` and `B=cb`, + a merge of `A=ca` and `B=bc` is tried. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import merge_overlapping + sage: def f(L, s): + ....: return list((ell, s) for ell in L) + sage: key = lambda k: k[0] + sage: merge_overlapping(f([0..3], 'a'), f([5..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..2], 'a'), f([4..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([4..7], 'a'), f([0..2], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..3], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'b')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([3..4], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'b'), (2, 'b'), (3, 'a'), (4, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'a')]) + sage: merge_overlapping(f([0..1], 'a'), f([0..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'b'), (3, 'b'), (4, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([0..3], 'a'), f([0..1], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'a'), (3, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..3], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([0..6], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'a'), (5, 'a'), (6, 'a')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b'), (5, 'a'), (6, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..2], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'a')]) + sage: merge_overlapping(f([1..2], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([1..3], 'b'), key) + ([(1, 'a'), (2, 'a'), (3, 'a')], + [(1, 'b'), (2, 'b'), (3, 'b')]) + """ + if key is None: + Akeys = A + Bkeys = B + else: + Akeys = tuple(key(a) for a in A) + Bkeys = tuple(key(b) for b in B) + + def find_overlapping_index(A, B): + if len(B) > len(A) - 2: + raise StopIteration + matches = iter(i for i in xrange(1, len(A) - len(B)) + if A[i:i+len(B)] == B) + return next(matches) + + def find_mergedoverlapping_index(A, B): + """ + Return in index i where to merge two overlapping tuples/lists ``A`` and ``B``. + + Then ``A + B[i:]`` or ``A[:-i] + B`` are the merged tuples/lists. + + Adapted from http://stackoverflow.com/a/30056066/1052778. + """ + matches = iter(i for i in xrange(min(len(A), len(B)), 0, -1) + if A[-i:] == B[:i]) + return next(matches, 0) + + i = find_mergedoverlapping_index(Akeys, Bkeys) + if i > 0: + return A + B[i:], A[:-i] + B + + i = find_mergedoverlapping_index(Bkeys, Akeys) + if i > 0: + return B[:-i] + A, B + A[i:] + + try: + i = find_overlapping_index(Akeys, Bkeys) + except StopIteration: + pass + else: + return A, A[:i] + B + A[i+len(B):] + + try: + i = find_overlapping_index(Bkeys, Akeys) + except StopIteration: + pass + else: + return B[:i] + A + B[i+len(A):], B + + raise ValueError('Input does not have an overlap.') + + +def log_string(element, base=None): + r""" + Return a representation of the log of the given element to the + given base. + + INPUT: + + - ``element`` -- an object. + + - ``base`` -- an object or ``None``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import log_string + sage: log_string(3) + 'log(3)' + sage: log_string(3, base=42) + 'log(3, base=42)' + """ + basestr = ', base=' + str(base) if base else '' + return 'log(%s%s)' % (element, basestr) + + +def transform_category(category, + subcategory_mapping, axiom_mapping, + initial_category=None): + r""" + Transform ``category`` to a new category according to the given + mappings. + + INPUT: + + - ``category`` -- a category. + + - ``subcategory_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are categories and + - ``mandatory`` is a boolean. + + - ``axiom_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are strings describing axioms and + - ``mandatory`` is a boolean. + + - ``initial_category`` -- (default: ``None``) a category. When + transforming the given category, this ``initial_category`` is + used as a starting point of the result. This means the resulting + category will be a subcategory of ``initial_category``. + If ``initial_category`` is ``None``, then the + :class:`category of objects ` + is used. + + OUTPUT: + + A category. + + .. NOTE:: + + Consider a subcategory mapping ``(from, to, mandatory)``. If + ``category`` is a subcategory of ``from``, then the + returned category will be a subcategory of ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + Consider an axiom mapping ``(from, to, mandatory)``. If + ``category`` is has axiom ``from``, then the + returned category will have axiom ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import transform_category + sage: from sage.categories.additive_semigroups import AdditiveSemigroups + sage: from sage.categories.additive_monoids import AdditiveMonoids + sage: from sage.categories.additive_groups import AdditiveGroups + sage: S = [ + ....: (Sets(), Sets(), True), + ....: (Posets(), Posets(), False), + ....: (AdditiveMagmas(), Magmas(), False)] + sage: A = [ + ....: ('AdditiveAssociative', 'Associative', False), + ....: ('AdditiveUnital', 'Unital', False), + ....: ('AdditiveInverse', 'Inverse', False), + ....: ('AdditiveCommutative', 'Commutative', False)] + sage: transform_category(Objects(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of objects is not + a subcategory of Category of sets. + sage: transform_category(Sets(), S, A) + Category of sets + sage: transform_category(Posets(), S, A) + Category of posets + sage: transform_category(AdditiveSemigroups(), S, A) + Category of semigroups + sage: transform_category(AdditiveMonoids(), S, A) + Category of monoids + sage: transform_category(AdditiveGroups(), S, A) + Category of groups + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A) + Category of commutative groups + + :: + + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A, + ....: initial_category=Posets()) + Join of Category of commutative groups + and Category of posets + + :: + + sage: transform_category(ZZ.category(), S, A) + Category of commutative groups + sage: transform_category(QQ.category(), S, A) + Category of commutative groups + sage: transform_category(SR.category(), S, A) + Category of commutative groups + sage: transform_category(Fields(), S, A) + Category of commutative groups + sage: transform_category(ZZ['t'].category(), S, A) + Category of commutative groups + + :: + + sage: A[-1] = ('Commutative', 'AdditiveCommutative', True) + sage: transform_category(Groups(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of groups does not have + axiom Commutative. + """ + if initial_category is None: + from sage.categories.objects import Objects + result = Objects() + else: + result = initial_category + + for A, B, mandatory in subcategory_mapping: + if category.is_subcategory(A): + result &= B + elif mandatory: + raise ValueError('%s is not a subcategory of %s.' % + (category, A)) + + axioms = category.axioms() + for A, B, mandatory in axiom_mapping: + if A in axioms: + result = result._with_axiom(B) + elif mandatory: + raise ValueError('%s does not have axiom %s.' % + (category, A)) + + return result diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py new file mode 100644 index 00000000000..0d18e1d900b --- /dev/null +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -0,0 +1,3720 @@ +r""" +(Asymptotic) Term Monoids + +This module implements asymptotic term monoids. The elements of these +monoids are used behind the scenes when performing calculations in an +:doc:`asymptotic ring `. + +The monoids build upon the (asymptotic) growth groups. While growth +elements only model the growth of a function as it tends towards +infinity (or tends towards another fixed point; see +:doc:`growth_group` for more details), an +asymptotic term additionally specifies its "type" and performs the +actual arithmetic operations (multiplication and partial +addition/absorption of terms). + +Besides an abstract base term :class:`GenericTerm`, this module +implements the following types of terms: + +- :class:`OTerm` -- `O`-terms at infinity, see + :wikipedia:`Big_O_notation`. +- :class:`TermWithCoefficient` -- abstract base class for + asymptotic terms with coefficients. +- :class:`ExactTerm` -- this class represents a growth element + multiplied with some non-zero coefficient from a coefficient ring. + +A characteristic property of asymptotic terms is that some terms are +able to "absorb" other terms (see +:meth:`~sage.rings.asymptotic.term_monoid.GenericTerm.absorb`). For +instance, `O(x^2)` is able to absorb `O(x)` (with result +`O(x^2)`), and `3\cdot x^5` is able to absorb `-2\cdot x^5` (with result +`x^5`). Essentially, absorption can be interpreted as the +addition of "compatible" terms (partial addition). + +.. WARNING:: + + As this code is experimental, a warning is thrown when a term + monoid is created for the first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + +.. _term_absorption: + +Absorption of Asymptotic Terms +============================== + +A characteristic property of asymptotic terms is that some terms are +able to "absorb" other terms. This is realized with the method +:meth:`~sage.rings.asymptotic.term_monoid.GenericTerm.absorb`. + +For instance, `O(x^2)` is able to absorb `O(x)` (with result +`O(x^2)`). This is because the functions bounded by linear growth +are bounded by quadratic growth as well. Another example would be +that `3x^5` is able to absorb `-2x^5` (with result `x^5`), which +simply corresponds to addition. + +Essentially, absorption can be interpreted +as the addition of "compatible" terms (partial addition). + +We want to show step by step which terms can be absorbed +by which other terms. We start by defining the necessary +term monoids and some terms:: + + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid, ExactTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(growth_group=G, coefficient_ring=QQ) + sage: ET = ExactTermMonoid(growth_group=G, coefficient_ring=QQ) + sage: ot1 = OT(x); ot2 = OT(x^2) + sage: et1 = ET(x^2, 2) + +- Because of the definition of `O`-terms (see + :wikipedia:`Big_O_notation`), :class:`OTerm` are able to absorb all + other asymptotic terms with weaker or equal growth. In our + implementation, this means that :class:`OTerm` is able to absorb + other :class:`OTerm`, as well as :class:`ExactTerm`, as long as the + growth of the other term is less than or equal to the growth of this + element:: + + sage: ot1, ot2 + (O(x), O(x^2)) + sage: ot1.can_absorb(ot2), ot2.can_absorb(ot1) + (False, True) + sage: et1 + 2*x^2 + sage: ot1.can_absorb(et1) + False + sage: ot2.can_absorb(et1) + True + + The result of this absorption always is the dominant + (absorbing) :class:`OTerm`:: + + sage: ot1.absorb(ot1) + O(x) + sage: ot2.absorb(ot1) + O(x^2) + sage: ot2.absorb(et1) + O(x^2) + + These examples correspond to `O(x) + O(x) = O(x)`, + `O(x^2) + O(x) = O(x^2)`, and `O(x^2) + 2x^2 = O(x^2)`. + +- :class:`ExactTerm` can only absorb another + :class:`ExactTerm` if the growth coincides with the + growth of this element:: + + sage: et1.can_absorb(ET(x^2, 5)) + True + sage: any(et1.can_absorb(t) for t in [ot1, ot2]) + False + + As mentioned above, absorption directly corresponds + to addition in this case:: + + sage: et1.absorb(ET(x^2, 5)) + 7*x^2 + + When adding two exact terms, they might cancel out. + For technical reasons, ``None`` is returned in this + case:: + + sage: ET(x^2, 5).can_absorb(ET(x^2, -5)) + True + sage: ET(x^2, 5).absorb(ET(x^2, -5)) is None + True + +- The abstract base terms :class:`GenericTerm` and + :class:`TermWithCoefficient` can neither absorb any + other term, nor be absorbed by any other term. + +If ``absorb`` is called on a term that cannot be absorbed, an +:python:`ArithmeticError` +is raised:: + + sage: ot1.absorb(ot2) + Traceback (most recent call last): + ... + ArithmeticError: O(x) cannot absorb O(x^2) + +This would only work the other way around:: + + sage: ot2.absorb(ot1) + O(x^2) + +Comparison +========== + +The comparison of asymptotic terms with `\leq` is implemented as follows: + +- When comparing ``t1 <= t2``, the coercion framework first tries to + find a common parent for both terms. If this fails, ``False`` is + returned. + +- In case the coerced terms do not have a coefficient in their + common parent (e.g. :class:`OTerm`), the growth of the two terms + is compared. + +- Otherwise, if the coerced terms have a coefficient (e.g. + :class:`ExactTerm`), we compare whether ``t1`` has a growth that is + strictly weaker than the growth of ``t2``. If so, we return + ``True``. If the terms have equal growth, then we return ``True`` + if and only if the coefficients coincide as well. + + In all other cases, we return ``False``. + +Long story short: we consider terms with different coefficients that +have equal growth to be incomparable. + + +Various +======= + +.. TODO:: + + - Implementation of more term types (e.g. `L` terms, + `\Omega` terms, `o` terms, `\Theta` terms). + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Classes and Methods +=================== +""" + +# ***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +import sage + + +def absorption(left, right): + r""" + Let one of the two passed terms absorb the other. + + Helper function used by + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. + + .. NOTE:: + + If neither of the terms can absorb the other, an + :python:`ArithmeticError` + is raised. + + See the :ref:`module description ` for a + detailed explanation of absorption. + + INPUT: + + - ``left`` -- an asymptotic term. + + - ``right`` -- an asymptotic term. + + OUTPUT: + + An asymptotic term or ``None``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, absorption) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) + O(x^3) + sage: absorption(T(x^3), T(x^2)) + O(x^3) + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) + Traceback (most recent call last): + ... + ArithmeticError: Absorption between x^2 and x^3 is not possible. + """ + try: + return left.absorb(right) + except ArithmeticError: + try: + return right.absorb(left) + except ArithmeticError: + raise ArithmeticError('Absorption between %s and %s is not possible.' % (left, right)) + + +def can_absorb(left, right): + r""" + Return whether one of the two input terms is able to absorb the + other. + + Helper function used by + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. + + INPUT: + + - ``left`` -- an asymptotic term. + + - ``right`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + See the :ref:`module description ` for a + detailed explanation of absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, can_absorb) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: can_absorb(T(x^2), T(x^3)) + True + sage: can_absorb(T(x^3), T(x^2)) + True + """ + return left.can_absorb(right) or right.can_absorb(left) + + +class GenericTerm(sage.structure.element.MonoidElement): + r""" + Base class for asymptotic terms. Mainly the structure and + several properties of asymptotic terms are handled here. + + INPUT: + + - ``parent`` -- the parent of the asymptotic term. + + - ``growth`` -- an asymptotic growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2); (t1, t2) + (Generic Term with growth x, Generic Term with growth x^2) + sage: t1 * t2 + Generic Term with growth x^3 + sage: t1.can_absorb(t2) + False + sage: t1.absorb(t2) + Traceback (most recent call last): + ... + ArithmeticError: Generic Term with growth x cannot absorb Generic Term with growth x^2 + sage: t1.can_absorb(t1) + False + """ + + def __init__(self, parent, growth): + r""" + See :class:`GenericTerm` for more information. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) + sage: T(x^2) + Generic Term with growth x^2 + + :: + + sage: from sage.rings.asymptotic.term_monoid import GenericTerm + sage: GenericTerm(parent=None, growth=x) + Traceback (most recent call last): + ... + ValueError: The parent must be provided + sage: GenericTerm(T, GrowthGroup('y^ZZ').gen()) + Traceback (most recent call last): + ... + ValueError: y is not in Growth Group x^ZZ + """ + if parent is None: + raise ValueError('The parent must be provided') + try: + self.growth = parent.growth_group(growth) + except (ValueError, TypeError): + raise ValueError("%s is not in %s" % (growth, parent.growth_group)) + + super(GenericTerm, self).__init__(parent=parent) + + + def _mul_(self, other): + r""" + Multiplication of this term by another. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A :class:`GenericTerm`. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element, as well as ``other`` + are from a common parent. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) + sage: t1 = T(x); t2 = T(x^2) + sage: t1, t2 + (Generic Term with growth x, Generic Term with growth x^2) + sage: t1 * t2 + Generic Term with growth x^3 + """ + return self.parent()(self.growth * other.growth) + + + def __div__(self, other): + r""" + Division of this term by another. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A :class:`GenericTerm`. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_div_`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + sage: t1 / t2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x^2 + not implemented (in this abstract method). + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._div_(other) + + from sage.structure.element import get_coercion_model + import operator + return get_coercion_model().bin_op(self, other, operator.div) + + + def _div_(self, other): + r""" + Division of this term by another. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A :class:`GenericTerm`. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element, as well as ``other`` + are from a common parent. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + sage: t1 / t2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x^2 + not implemented (in this abstract method). + """ + return self * ~other + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A :class:`GenericTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: ~T(x) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Inversion of Generic Term with growth x + not implemented (in this abstract method). + """ + raise NotImplementedError('Inversion of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def __pow__(self, exponent): + r""" + Calculate the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + Raise a :python:`NotImplementedError` + since it is an abstract base class. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t^3 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Taking powers of Generic Term with growth z + not implemented (in this abstract method). + """ + raise NotImplementedError('Taking powers of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def _calculate_pow_test_zero_(self, exponent): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent`` only if zero to this exponent is possible. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_test_zero_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_test_zero_(-2) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take Generic Term with growth z to exponent -2. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G, QQ)('z')._calculate_pow_test_zero_(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + # This assumes `0 = O(g)` for any `g` in the growth group, which + # is valid in the case of a variable going to `\infty`. + # Once non-standard asymptoptics are supported, this has to be + # rewritten. + # See also #19083, comment 64, 27. + + zero = self.parent().coefficient_ring.zero() + try: + zero ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ZeroDivisionError('Cannot take %s to exponent %s.' % + (self, exponent)), e) + return self._calculate_pow_(exponent) + + + def _calculate_pow_(self, exponent, new_coefficient=None): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + - ``new_coefficient`` -- if not ``None`` this is passed on to the + construction of the element (in particular, not taken to any power). + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_(3, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + sage: t._calculate_pow_(-2) + Generic Term with growth z^(-2) + sage: t._calculate_pow_(-2, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + """ + try: + g = self.growth ** exponent + except (ValueError, TypeError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + + return self.parent()._create_element_in_extension_(g, new_coefficient) + + + def can_absorb(self, other): + r""" + Check whether this asymptotic term is able to absorb + the asymptotic term ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + A :class:`GenericTerm` cannot absorb any other term. + + See the :ref:`module description ` for a + detailed explanation of absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GenericGrowthGroup(ZZ) + sage: T = GenericTermMonoid(G, QQ) + sage: g1 = G(raw_element=21); g2 = G(raw_element=42) + sage: t1 = T(g1); t2 = T(g2) + sage: t1.can_absorb(t2) # indirect doctest + False + sage: t2.can_absorb(t1) # indirect doctest + False + """ + return False + + + def absorb(self, other, check=True): + r""" + Absorb the asymptotic term ``other`` and return the resulting + asymptotic term. + + INPUT: + + - ``other`` -- an asymptotic term. + + - ``check`` -- a boolean. If ``check`` is ``True`` (default), + then ``can_absorb`` is called before absorption. + + OUTPUT: + + An asymptotic term or ``None`` if a cancellation occurs. If no + absorption can be performed, an :python:`ArithmeticError` + is raised. + + .. NOTE:: + + Setting ``check`` to ``False`` is meant to be used in + cases where the respective comparison is done externally + (in order to avoid duplicate checking). + + For a more detailed explanation of the *absorption* of + asymptotic terms see + the :ref:`module description `. + + EXAMPLES: + + We want to demonstrate in which cases an asymptotic term + is able to absorb another term, as well as explain the output + of this operation. We start by defining some parents and + elements:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_QQ = GrowthGroup('x^QQ'); x = G_QQ.gen() + sage: OT = TermMonoid('O', G_QQ, coefficient_ring=ZZ) + sage: ET = TermMonoid('exact', G_QQ, coefficient_ring=QQ) + sage: ot1 = OT(x); ot2 = OT(x^2) + sage: et1 = ET(x, 100); et2 = ET(x^2, 2) + sage: et3 = ET(x^2, 1); et4 = ET(x^2, -2) + + `O`-Terms are able to absorb other `O`-terms and exact terms + with weaker or equal growth. :: + + sage: ot1.absorb(ot1) + O(x) + sage: ot1.absorb(et1) + O(x) + sage: ot1.absorb(et1) is ot1 + True + + :class:`ExactTerm` is able to absorb another + :class:`ExactTerm` if the terms have the same growth. In this + case, *absorption* is nothing else than an addition of the + respective coefficients:: + + sage: et2.absorb(et3) + 3*x^2 + sage: et3.absorb(et2) + 3*x^2 + sage: et3.absorb(et4) + -x^2 + + Note that, for technical reasons, the coefficient `0` is not + allowed, and thus ``None`` is returned if two exact terms + cancel each other out:: + + sage: et2.absorb(et4) + sage: et4.absorb(et2) is None + True + + TESTS: + + When disabling the ``check`` flag, absorb might produce + wrong results:: + + sage: et1.absorb(ot2, check=False) + O(x) + """ + from sage.structure.element import have_same_parent + + if check: + if not self.can_absorb(other): + raise ArithmeticError('%s cannot absorb %s' % (self, other)) + + if have_same_parent(self, other): + return self._absorb_(other) + + from sage.structure.element import get_coercion_model + + return get_coercion_model().bin_op(self, other, + lambda left, right: + left._absorb_(right)) + + + def _absorb_(self, other): + r""" + Let this element absorb ``other``. + + INPUT: + + - ``other`` -- an asymptotic term from the same parent as + this element. + + OUTPUT: + + An asymptotic term or ``None``. + + .. NOTE:: + + This is not implemented for abstract base classes. For + concrete realizations see, for example, :meth:`OTerm._absorb_` + or :meth:`ExactTerm._absorb_`. + Override this in derived class. + + EXAMPLES: + + First, we define some asymptotic terms:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + + When it comes to absorption, note that the method + :meth:`can_absorb` (which is called before absorption takes + place) does not allow the absorption of generic terms. Thus, + an :python:`ArithmeticError` + is raised:: + + sage: t2.absorb(t1) + Traceback (most recent call last): + ... + ArithmeticError: Generic Term with growth x^2 cannot absorb Generic Term with growth x + + TESTS:: + + sage: t2._absorb_(t1) + Traceback (most recent call last): + ... + NotImplementedError: Not implemented in abstract base classes + """ + raise NotImplementedError('Not implemented in abstract base classes') + + + def log_term(self, base=None): + r""" + Determine the logarithm of this term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This abstract method raises a + :python:`NotImplementedError`. + See :class:`ExactTerm` and :class:`OTerm` for a concrete + implementation. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + raise NotImplementedError('This method is not implemented in this ' + 'abstract base class.') + + + def _log_growth_(self, base=None): + r""" + Helper function to calculate the logarithm of the growth of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2)._log_growth_() + (O(log(x)),) + sage: T(x^1234).log_term() # indirect doctest + (O(log(x)),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + return tuple(self.parent()._create_element_in_extension_(g, c) + for g, c in self.growth.log_factor(base=base)) + + + def __le__(self, other): + r""" + Return whether the growth of this term is less than + or equal to the growth of ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method **only** compares the growth of the input + terms! + + EXAMPLES: + + First, we define some asymptotic terms (and their parents):: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: GT = GenericTermMonoid(G, QQ) + sage: OT = TermMonoid('O', G, QQ) + sage: ET_ZZ = TermMonoid('exact', G, ZZ) + sage: ET_QQ = TermMonoid('exact', G, QQ) + sage: g1 = GT(x); g2 = GT(x^2); g1, g2 + (Generic Term with growth x, Generic Term with growth x^2) + sage: o1 = OT(x^-1); o2 = OT(x^3); o1, o2 + (O(x^(-1)), O(x^3)) + sage: t1 = ET_ZZ(x^2, 5); t2 = ET_QQ(x^3, 2/7); t1, t2 + (5*x^2, 2/7*x^3) + + In order for the comparison to work, the terms have come from + or coerce into the same parent. In particular, comparing + :class:`GenericTerm` to, for example, an :class:`OTerm` + always yields ``False``:: + + sage: g1 <= g2 + True + sage: o1, g1 + (O(x^(-1)), Generic Term with growth x) + sage: o1 <= g1 + False + + If the elements of the common parent do not possess + coefficients, then only the growth is compared:: + + sage: o1 <= o1 + True + sage: o1 <= o2 + True + sage: o1 <= t1 and t1 <= o2 + True + + For terms with coefficient (like exact terms), comparison + works similarly, with the sole exception that terms with + equal growth are considered incomparable. Thus, `\leq` + only holds if the coefficients are equal as well:: + + sage: t1 <= t2 + True + sage: ET_ZZ(x, -5) <= ET_ZZ(x, 42) + False + sage: ET_ZZ(x, 5) <= ET_ZZ(x, 5) + True + """ + from sage.structure.element import have_same_parent + + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def _le_(self, other): + r""" + Return whether this generic term grows at most (i.e. less than + or equal) like ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element, as well as ``other`` + are from the same parent. + + Also, this method **only** compares the growth of the + input terms! + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x^-2); t2 = T(x^5); t1, t2 + (Generic Term with growth x^(-2), Generic Term with growth x^5) + sage: t1._le_(t2) + True + sage: t2._le_(t1) + False + """ + return self.growth <= other.growth + + + def __eq__(self, other): + r""" + Return whether this asymptotic term is equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, + ....: ExactTermMonoid, OTermMonoid) + sage: GT = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: ET = ExactTermMonoid(GrowthGroup('x^ZZ'), ZZ) + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: g = GT.an_element(); e = ET.an_element(); o = OT.an_element() + sage: g, e, o + (Generic Term with growth x, x, O(x)) + sage: e == e^2 # indirect doctest + False + sage: e == ET(x,1) # indirect doctest + True + sage: o == OT(x^2) # indirect doctest + False + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._eq_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.eq) + except TypeError: + return False + + + def _eq_(self, other): + r""" + Return whether this asymptotic term is the same as ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method gets called by the coercion framework, so it + can be assumed that this asymptotic term is from the + same parent as ``other``. + + Only implemented in concrete realizations. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: t = T.an_element() + sage: t == t + True + + :: + + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: t = OT.an_element(); t + O(x) + sage: t == OT(x) # indirect doctest + True + sage: t == OT(x^2) # indirect doctest + False + """ + return self.growth == other.growth + + + def is_constant(self): + r""" + Return whether this term is an (exact) constant. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: t = T.an_element(); t + Generic Term with growth x*log(x) + sage: t.is_constant() + False + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T('x').is_constant() + False + sage: T(1).is_constant() + False + """ + return False + + + def is_little_o_of_one(self): + r""" + Return whether this generic term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, + ....: TermWithCoefficientMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Generic Term with growth x is o(1) + in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Term with coefficient 1/2 and growth x + is o(1) in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot check whether %s is o(1) in the ' + 'abstract base class %s.' % (self, self.parent())) + + + def rpow(self, base): + r""" + Return the power of ``base`` to this generic term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + NotImplementedError: Cannot take e to the exponent + Generic Term with growth x*log(x) in the abstract base class + Generic Term Monoid x^ZZ * log(x)^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot take %s to the exponent %s in the ' + 'abstract base class %s.' % (base, self, self.parent())) + + + def _repr_(self): + r""" + A representation string for this generic term. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: T(x)._repr_() + 'Generic Term with growth x' + sage: T(x^7)._repr_() + 'Generic Term with growth x^7' + """ + return 'Generic Term with growth ' + repr(self.growth) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + Nothing since a + :python:`TypeError` + is raised. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: t = GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).an_element() + sage: t._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in Generic Term with growth x in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + > *previous* TypeError: Cannot substitute in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericTermMonoid(sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): + r""" + Parent for generic asymptotic terms. + + INPUT: + + - ``growth_group`` -- a growth group (i.e. an instance of + :class:`~sage.rings.asymptotic.growth_group.GenericGrowthGroup`). + + - ``coefficient_ring`` -- a ring which contains the (maybe implicit) + coefficients of the elements. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of Monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + In this class the base + structure for asymptotic term monoids will be handled. These + monoids are the parents of asymptotic terms (for example, see + :class:`GenericTerm` or :class:`OTerm`). Basically, asymptotic + terms consist of a ``growth`` (which is an asymptotic growth + group element, for example + :class:`~sage.rings.asymptotic.growth_group.MonomialGrowthElement`); + additional structure and properties are added by the classes inherited + from :class:`GenericTermMonoid`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_x = GrowthGroup('x^ZZ'); x = G_x.gen() + sage: G_y = GrowthGroup('y^QQ'); y = G_y.gen() + sage: T_x_ZZ = GenericTermMonoid(G_x, QQ) + sage: T_y_QQ = GenericTermMonoid(G_y, QQ) + sage: T_x_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_y_QQ + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field + """ + + # enable the category framework for elements + Element = GenericTerm + + + @staticmethod + def __classcall__(cls, growth_group, coefficient_ring, category=None): + r""" + Normalize the input in order to ensure a unique + representation of the parent. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = GenericTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.misc import underlying_class + sage: underlying_class(T)(G, QQ) is T + True + + :: + + sage: GenericTermMonoid(None, ZZ) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No growth group specified. + sage: GenericTermMonoid(int, ZZ) # indirect doctest + Traceback (most recent call last): + ... + TypeError: is not a valid growth group. + sage: GenericTermMonoid(G, None) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No coefficient ring specified. + sage: GenericTermMonoid(G, int) # indirect doctest + Traceback (most recent call last): + ... + TypeError: is not a valid coefficient ring. + """ + if growth_group is None: + raise ValueError('No growth group specified.') + if not isinstance(growth_group, sage.structure.parent.Parent): + raise TypeError('%s is not a valid growth group.' % (growth_group,)) + + if coefficient_ring is None: + raise ValueError('No coefficient ring specified.') + if not isinstance(coefficient_ring, sage.structure.parent.Parent): + raise TypeError('%s is not a valid coefficient ring.' % (coefficient_ring,)) + + if category is None: + from sage.categories.monoids import Monoids + from sage.categories.posets import Posets + category = Monoids() & Posets() + + return super(GenericTermMonoid, cls).__classcall__( + cls, growth_group, coefficient_ring, category) + + + @sage.misc.superseded.experimental(trac_number=17601) + def __init__(self, growth_group, coefficient_ring, category): + r""" + See :class:`GenericTermMonoid` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid, TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ') + sage: T_x = GenericTermMonoid(G_x, QQ); T_x + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_x.growth_group + Growth Group x^ZZ + sage: G_y = GrowthGroup('y^QQ') + sage: T_y = GenericTermMonoid(G_y, QQ); T_y + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field + sage: T_x is T_y + False + + :: + + sage: GenericTermMonoid(None, None) + Traceback (most recent call last): + ... + ValueError: No growth group specified. + + :: + + sage: G = GrowthGroup('x^ZZ') + sage: T_ZZ = TermWithCoefficientMonoid(G, ZZ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: T_QQ = TermWithCoefficientMonoid(G, QQ); T_QQ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_QQ.category() + Join of Category of monoids and Category of posets + """ + self._growth_group_ = growth_group + self._coefficient_ring_ = coefficient_ring + super(GenericTermMonoid, self).__init__(category=category) + + + @property + def growth_group(self): + r""" + The growth group underlying this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ).growth_group + Growth Group x^ZZ + """ + return self._growth_group_ + + + @property + def coefficient_ring(self): + r""" + The coefficient ring of this term monoid, i.e. the ring where + the coefficients are from. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).coefficient_ring + Integer Ring + """ + return self._coefficient_ring_ + + + def _repr_(self): + r""" + A representation string for this generic term monoid. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, GrowthGroup) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GenericGrowthGroup(ZZ), QQ)._repr_() + 'Generic Term Monoid Generic(ZZ) with (implicit) coefficients in Rational Field' + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), QQ)._repr_() + 'Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field' + """ + return 'Generic Term Monoid %s with (implicit) coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this term monoid. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Another generic term monoid ``S`` coerces into this term + monoid if and only if both, the growth group of ``S`` coerces + into the growth group of this term monoid and the coefficient + ring of ``S`` coerces into the coefficient ring of this term + monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_QQ = GenericTermMonoid(G_QQ, QQ); T_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: T_QQ.has_coerce_map_from(T_ZZ) # indirect doctest + True + sage: T_QQ_ZZ = GenericTermMonoid(G_QQ, ZZ); T_QQ_ZZ + Generic Term Monoid x^QQ with (implicit) coefficients in Integer Ring + sage: T_QQ.has_coerce_map_from(T_QQ_ZZ) + True + sage: T_QQ_ZZ.has_coerce_map_from(T_QQ) + False + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, ZZ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_QQ.has_coerce_map_from(TC_ZZ) # indirect doctest + True + sage: TC_ZZ.has_coerce_map_from(TC_QQ) # indirect doctest + False + """ + if isinstance(S, self.__class__): + if self.growth_group.has_coerce_map_from(S.growth_group) and \ + self.coefficient_ring.has_coerce_map_from(S.coefficient_ring): + return True + + + def _element_constructor_(self, data, coefficient=None): + r""" + Convert the given object to this term monoid. + + INPUT: + + - ``data`` -- a growth element or an object representing the + element to be initialized. + + - ``coefficient`` -- (default: ``None``) + an element of the coefficient ring. + + OUTPUT: + + An element of this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_QQ = GenericTermMonoid(G_QQ, QQ) + sage: term1 = T_ZZ(G_ZZ.gen()) + sage: term2 = T_QQ(G_QQ.gen()^2) + + In order for two terms to be compared, a coercion into + a common parent has to be found:: + + sage: term1.parent() + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: term2.parent() + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: term1 <= term2 + True + + In this case, this works because ``T_ZZ``, the parent of + ``term1``, coerces into ``T_QQ``:: + + sage: T_QQ.coerce(term1) # coercion does not fail + Generic Term with growth x + + The conversion of growth elements also works for the creation + of terms:: + + sage: x = SR('x'); x.parent() + Symbolic Ring + sage: T_ZZ(x^42) + Generic Term with growth x^42 + sage: x = PolynomialRing(ZZ, 'x').gen(); x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: T_ZZ(x^10) + Generic Term with growth x^10 + sage: T_ZZ(10 * x^2) + Traceback (most recent call last): + ... + ValueError: 10*x^2 is not in Generic Term Monoid x^ZZ + with (implicit) coefficients in Rational Field. + > *previous* ValueError: Factor 10*x^2 of 10*x^2 is neither a + coefficient (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t1 = T(x^2, 5); t1 # indirect doctest + Term with coefficient 5 and growth x^2 + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: O_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: O_ZZ(x^11) + O(x^11) + + :: + + sage: T(G.gen()^10) + Term with coefficient 1 and growth x^10 + sage: T(G.gen()^10, coefficient=10) + Term with coefficient 10 and growth x^10 + sage: T(x^123) + Term with coefficient 1 and growth x^123 + + :: + + sage: T(x) + Term with coefficient 1 and growth x + + :: + + sage: G_log = GrowthGroup('log(x)^ZZ') + sage: T_log = TermWithCoefficientMonoid(G_log, ZZ) + sage: T_log(log(x)) + Term with coefficient 1 and growth log(x) + + """ + if isinstance(data, self.element_class) and data.parent() == self: + return data + elif isinstance(data, TermWithCoefficient): + return self._create_element_(data.growth, data.coefficient) + elif isinstance(data, GenericTerm): + return self._create_element_(data.growth, None) + elif isinstance(data, int) and data == 0: + raise ValueError('No input specified. Cannot continue ' + 'creating an element of %s.' % (self,)) + + from misc import combine_exceptions + if coefficient is not None: + try: + data = self.growth_group(data) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Growth %s is not in %s.' % (data, self)), e) + return self._create_element_(data, coefficient) + + try: + growth, coefficient = self._split_growth_and_coefficient_(data) + except ValueError as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + return self._create_element_(growth, coefficient) + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen()) # indirect doctest + Generic Term with growth x + """ + if coefficient is not None and coefficient != self.coefficient_ring.one(): + raise ValueError('Coefficient %s is not 1, but %s does not ' + 'support coefficients.' % (coefficient, self)) + return self.element_class(self, growth) + + + def _create_element_in_extension_(self, growth, coefficient): + r""" + Create an element in an extension of this term monoid which + is chosen according to the input. + + INPUT: + + - ``growth`` and ``coefficient`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: T._create_element_in_extension_(G.an_element(), 3) + 3*z + sage: T._create_element_in_extension_(G.an_element(), 3/2).parent() + Exact Term Monoid z^ZZ with coefficients in Rational Field + """ + if (growth.parent() is self.growth_group) and \ + (coefficient is None or coefficient.parent() is self.coefficient_ring): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(growth.parent(), + coefficient.parent() + if coefficient is not None + else self.coefficient_ring, + category=self.category()) + return parent(growth, coefficient) + + + def _split_growth_and_coefficient_(self, data): + r""" + Split given ``data`` into a growth element and a coefficient. + + INPUT: + + ``data`` -- an element. + + OUTPUT: + + A pair ``(growth, coefficient``). + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('2*x^3') + (x^3, 2) + + :: + + sage: T._split_growth_and_coefficient_('2.7 * x^3') + Traceback (most recent call last): + ... + ValueError: Factor 2.7 of 2.7 * x^3 is neither a coefficient + (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('3/4 * 2^x * log(x)') + (2^x*log(x), 3/4) + sage: T._split_growth_and_coefficient_('3 * x^2 * 4 * log(x) * x') + (x^3*log(x), 12) + sage: var('x') + x + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + + :: + + sage: T = TermMonoid('exact', G, SR) + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + sage: var('y') + y + sage: T._split_growth_and_coefficient_(2^x * y * 4) + (2^x, 4*y) + """ + factors = self._get_factors_(data) + + growth_group = self.growth_group + coefficient_ring = self.coefficient_ring + + coefficients = [] + growths = [] + for f in factors: + try: + growths.append(growth_group(f)) + continue + except (ValueError, TypeError): + pass + + try: + coefficients.append(coefficient_ring(f)) + continue + except (ValueError, TypeError): + pass + + raise ValueError('Factor %s of %s is neither a coefficient (in %s) ' + 'nor growth (in %s).' % + (f, data, coefficient_ring, growth_group)) + + from sage.misc.misc_c import prod + growth = prod(growths) if growths else growth_group.one() + coefficient = prod(coefficients) if coefficients else coefficient_ring.one() + return (growth, coefficient) + + + def _get_factors_(self, data): + r""" + Split given ``data`` into separate factors. + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + A tuple. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ._get_factors_(x^2 * log(x)) + (x^2, log(x)) + """ + if isinstance(data, str): + from misc import split_str_by_op + return split_str_by_op(data, '*') + + try: + P = data.parent() + except AttributeError: + return (data,) + + from sage.symbolic.ring import SymbolicRing + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return tuple(data.operands()) + + return (data,) + + + def _an_element_(self): + r""" + Return an element of this term monoid. + + INPUT: + + Nothing. + + OUTPUT: + + An element of this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ).an_element() # indirect doctest + O(x) + sage: GenericTermMonoid(G, QQ).an_element() # indirect doctest + Generic Term with growth x + """ + return self(self.growth_group.an_element()) + + + def some_elements(self): + r""" + Return some elements of this term monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: tuple(TermMonoid('O', G, QQ).some_elements()) + (O(1), O(x), O(x^(-1)), O(x^2), O(x^(-2)), O(x^3), ...) + """ + return iter(self(g) for g in self.growth_group.some_elements()) + + + def le(self, left, right): + r""" + Return whether the term ``left`` is at most (less than or equal + to) the term ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) + sage: t1 = T(x); t2 = T(x^2) + sage: T.le(t1, t2) + True + """ + return self(left) <= self(right) + + +class OTerm(GenericTerm): + r""" + Class for an asymptotic term representing an `O`-term with + specified growth. For the mathematical properties of `O`-terms + see :wikipedia:`Big_O_Notation`. + + `O`-terms can *absorb* terms of weaker or equal growth. + + INPUT: + + - ``parent`` -- the parent of the asymptotic term. + + - ``growth`` -- a growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(G, QQ) + sage: t1 = OT(x^-7); t2 = OT(x^5); t3 = OT(x^42) + sage: t1, t2, t3 + (O(x^(-7)), O(x^5), O(x^42)) + sage: t1.can_absorb(t2) + False + sage: t2.can_absorb(t1) + True + sage: t2.absorb(t1) + O(x^5) + sage: t1 <= t2 and t2 <= t3 + True + sage: t3 <= t1 + False + + The conversion of growth elements also works for the creation + of `O`-terms:: + + sage: x = SR('x'); x.parent() + Symbolic Ring + sage: OT(x^17) + O(x^17) + """ + + def _repr_(self): + r""" + A representation string for this `O`-term. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) + sage: t1 = OT(x); t2 = OT(x^2); t3 = OT(x^3) + sage: t1._repr_(), t2._repr_() + ('O(x)', 'O(x^2)') + sage: t3 + O(x^3) + """ + return 'O(%s)' % self.growth + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A :class:`ZeroDivisionError` since `O`-terms cannot be inverted. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('O', G, QQ) + sage: ~T(x) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(x). + """ + raise ZeroDivisionError('Cannot invert %s.' % (self,)) + + + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`OTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`OTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = TermMonoid('O', G, ZZ).an_element(); t + O(z) + sage: t^3 # indirect doctest + O(z^3) + sage: t^(1/2) # indirect doctest + O(z^(1/2)) + sage: t^(-1) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + return self._calculate_pow_test_zero_(exponent) + + + def can_absorb(self, other): + r""" + Check whether this `O`-term can absorb ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + An :class:`OTerm` can absorb any other asymptotic term + with weaker or equal growth. + + See the :ref:`module description ` for a + detailed explanation of absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: OT = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: t1 = OT(x^21); t2 = OT(x^42) + sage: t1.can_absorb(t2) + False + sage: t2.can_absorb(t1) + True + """ + return other <= self + + + def _absorb_(self, other): + r""" + Let this `O`-term absorb another `O`-term ``other``. + + INPUT: + + - ``other`` -- an asymptotic `O`-term. + + OUTPUT: + + An asymptotic `O`-term. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element and ``other`` + have the same parent. + + Also, observe that the result of a "dominant" `O`-term + absorbing another `O`-term always is the "dominant" + `O`-term again. + + See the :ref:`module description ` for a + detailed explanation on absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) + sage: ot1 = OT(x); ot2 = OT(x^2) + sage: ot1.absorb(ot1) + O(x) + sage: ot2.absorb(ot1) + O(x^2) + sage: ot1.absorb(ot2) + Traceback (most recent call last): + ... + ArithmeticError: O(x) cannot absorb O(x^2) + """ + return self + + + def log_term(self, base=None): + r""" + Determine the logarithm of this O-term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2).log_term() + (O(log(x)),) + sage: T(x^1234).log_term() + (O(log(x)),) + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), QQ) + sage: T('x * y').log_term() + (O(log(x)), O(log(y))) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`. + """ + return self._log_growth_(base=base) + + + def is_little_o_of_one(self): + r""" + Return whether this O-term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() + False + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() + True + """ + return self.growth.is_lt_one() + + + def rpow(self, base): + r""" + Return the power of ``base`` to this O-term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + .. NOTE:: + + For :class:`OTerm`, the powers can only be + constructed for exponents `O(1)` or if ``base`` is `1`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(1).rpow('e') + O(1) + sage: T(1).rpow(2) + O(1) + + :: + + sage: T.an_element().rpow(1) + 1 + sage: T('x^2').rpow(1) + 1 + + :: + + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(x*log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + sage: T('log(x)').rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + """ + if self.is_one() and base != 0: + return self + if base == 1: + P = self.parent() + return ExactTermMonoid(P.growth_group, P.coefficient_ring).one() + raise ValueError('Cannot take %s to the exponent %s in %s' % + (base, self, self.parent())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this O-Term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: t = T.an_element(); t + O(x) + sage: t._substitute_({'x': SR.var('z')}) + Order(z) + sage: t._substitute_({'x': SR.var('z'), 'O': function('Oh')}) + Oh(z) + sage: u = AsymptoticRing('x^ZZ', ZZ)('2*x'); u + 2*x + sage: t._substitute_({'x': u}) + O(x) + sage: T(1/x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in O(x^(-1)) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: t._substitute_({'x': 3}) + O(3) + sage: t._substitute_({'x': 'null'}) + Traceback (most recent call last): + ... + ArithmeticError: Cannot substitute in O(x) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ArithmeticError: O(null) not defined + """ + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + try: + return rules['O'](g) + except KeyError: + pass + + try: + P = g.parent() + except AttributeError: + pass + else: + from asymptotic_ring import AsymptoticRing + from sage.symbolic.ring import SymbolicRing + + if isinstance(P, AsymptoticRing): + return g.O() + + elif isinstance(P, SymbolicRing): + return g.Order() + + try: + return sage.rings.big_oh.O(g) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class OTermMonoid(GenericTermMonoid): + r""" + Parent for asymptotic big `O`-terms. + + INPUT: + + - ``growth_group`` -- a growth group. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G_x_ZZ = GrowthGroup('x^ZZ') + sage: G_y_QQ = GrowthGroup('y^QQ') + sage: OT_x_ZZ = OTermMonoid(G_x_ZZ, QQ); OT_x_ZZ + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: OT_y_QQ = OTermMonoid(G_y_QQ, QQ); OT_y_QQ + O-Term Monoid y^QQ with implicit coefficients in Rational Field + + `O`-term monoids can also be created by using the + :class:`term factory `:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G_x_ZZ, QQ) is OT_x_ZZ + True + sage: TermMonoid('O', GrowthGroup('x^QQ'), QQ) + O-Term Monoid x^QQ with implicit coefficients in Rational Field + """ + + # enable the category framework for elements + Element = OTerm + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring (will be + ignored since we create an O-Term). + + OUTPUT: + + An O-term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('O', G, QQ) + sage: T(G.gen()) # indirect doctest + O(x) + sage: T(G.gen(), SR.var('y')) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Cannot create O(x) since given coefficient y + is not valid in O-Term Monoid x^ZZ with implicit coefficients in + Rational Field. + > *previous* TypeError: unable to convert y to a rational + """ + if coefficient is not None: + try: + self.coefficient_ring(coefficient) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot create O(%s) since given coefficient %s ' + 'is not valid in %s.' % + (growth, coefficient, self)), e) + return self.element_class(self, growth) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this term monoid. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + ``True`` or ``None``. + + .. NOTE:: + + Another term monoid ``S`` coerces into this term monoid + if ``S`` is an instance of one of the following classes: + + - :class:`OTermMonoid` + + - :class:`ExactTermMonoid` + + Additionally, the growth group underlying ``S`` has to + coerce into the growth group of this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: OT_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: OT_QQ = TermMonoid('O', G_QQ, QQ) + sage: ET = TermMonoid('exact', G_ZZ, ZZ) + + Now, the :class:`OTermMonoid` whose growth group is over the + integer ring has to coerce into the :class:`OTermMonoid` with + the growth group over the rational field, and the + :class:`ExactTermMonoid` also has to coerce in each of the + given :class:`OTermMonoid`:: + + sage: OT_QQ.has_coerce_map_from(OT_ZZ) # indirect doctest + True + sage: OT_QQ.has_coerce_map_from(ET) # indirect doctest + True + sage: ET.has_coerce_map_from(OT_ZZ) # indirect doctest + False + """ + if isinstance(S, (ExactTermMonoid,)): + if self.growth_group.has_coerce_map_from(S.growth_group): + return True + else: + return super(OTermMonoid, self)._coerce_map_from_(S) + + + def _repr_(self): + r""" + A representation string for this `O`-term monoid. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('O', G, QQ)._repr_() + 'O-Term Monoid x^ZZ with implicit coefficients in Rational Field' + """ + return 'O-Term Monoid %s with implicit coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) + + +class TermWithCoefficient(GenericTerm): + r""" + Base class for asymptotic terms possessing a coefficient. For + example, :class:`ExactTerm` directly inherits from this class. + + INPUT: + + - ``parent`` -- the parent of the asymptotic term. + + - ``growth`` -- an asymptotic growth element of + the parent's growth group. + + - ``coefficient`` -- an element of the parent's coefficient ring. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) + sage: CT_ZZ(x^2, 5) + Term with coefficient 5 and growth x^2 + sage: CT_QQ(x^3, 3/8) + Term with coefficient 3/8 and growth x^3 + """ + + def __init__(self, parent, growth, coefficient): + r""" + See :class:`TermWithCoefficient` for more information. + + EXAMPLES: + + First, we define some monoids:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) + + The coefficients have to be from the given coefficient ring:: + + sage: t = CT_ZZ(x, 1/2) + Traceback (most recent call last): + ... + ValueError: 1/2 is not a coefficient in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + sage: t = CT_QQ(x, 1/2); t + Term with coefficient 1/2 and growth x + + For technical reasons, the coefficient 0 is not allowed:: + + sage: t = CT_ZZ(x^42, 0) + Traceback (most recent call last): + ... + ValueError: Zero coefficient 0 is not allowed in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + + The conversion of growth elements also works for the creation + of terms with coefficient:: + + sage: x = SR('x'); x.parent() + Symbolic Ring + sage: CT_ZZ(x^42, 42) + Term with coefficient 42 and growth x^42 + """ + try: + coefficient = parent.coefficient_ring(coefficient) + except (ValueError, TypeError): + raise ValueError('%s is not a coefficient in %s.' % + (coefficient, parent)) + if coefficient == 0: + raise ValueError('Zero coefficient %s is not allowed in %s.' % + (coefficient, parent)) + + self.coefficient = coefficient + super(TermWithCoefficient, self).__init__(parent=parent, growth=growth) + + + def _repr_(self): + r""" + A representation string for this term with coefficient. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: T(x^2, 5)._repr_() + 'Term with coefficient 5 and growth x^2' + """ + return 'Term with coefficient %s and growth %s' % \ + (self.coefficient, self.growth) + + + def _mul_(self, other): + r""" + Multiplication method for asymptotic terms with coefficients. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + An asymptotic term representing the product of this element + and ``other``. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element and ``other`` have + the same parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT = TermWithCoefficientMonoid(G, ZZ) + sage: ET = TermMonoid('exact', G, ZZ) + + This method handles the multiplication of abstract terms + with coefficient (i.e. :class:`TermWithCoefficient`) and + exact terms (i.e. :class:`ExactTerm`). First, an example + for abstract terms:: + + sage: t1 = CT(x^2, 2); t2 = CT(x^3, 3) + sage: t1 * t2 + Term with coefficient 6 and growth x^5 + + And now, an example for exact terms:: + + sage: t1 = ET(x^2, 2); t2 = ET(x^3, 3) + sage: t1 * t2 + 6*x^5 + """ + return self.parent()(self.growth * other.growth, + self.coefficient * other.coefficient) + + + def _calculate_pow_(self, exponent): + r""" + Helper function for :meth:`~ExactTerm.__pow__` which calculates + the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t = T('2*z'); t + Term with coefficient 2 and growth z + sage: t._calculate_pow_(3) + Term with coefficient 8 and growth z^3 + sage: t._calculate_pow_(-2) + Term with coefficient 1/4 and growth z^(-2) + + :: + + sage: T = TermWithCoefficientMonoid(G, CIF) + sage: T(G.an_element(), coefficient=CIF(RIF(-1,1), RIF(-1,1)))._calculate_pow_(I) + Traceback (most recent call last): + ... + ArithmeticError: Cannot take Term with coefficient 0.? + 0.?*I and + growth z to the exponent I in Generic Term Monoid z^ZZ with + (implicit) coefficients in Complex Interval Field with + 53 bits of precision since its coefficient 0.? + 0.?*I + cannot be taken to this exponent. + > *previous* ValueError: Can't take the argument of + interval strictly containing zero + """ + try: + c = self.coefficient ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot take %s to the exponent %s in %s since its ' + 'coefficient %s cannot be taken to this exponent.' % + (self, exponent, self.parent(), self.coefficient)), e) + return super(TermWithCoefficient, self)._calculate_pow_(exponent, new_coefficient=c) + + + def _log_coefficient_(self, base=None): + r""" + Helper function to calculate the logarithm of the coefficient of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(3*x^2)._log_coefficient_() + (log(3),) + sage: T(x^1234).log_term() # indirect doctest + (1234*log(x),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + if self.coefficient.is_one(): + return tuple() + from sage.functions.log import log + return (self.parent()._create_element_in_extension_( + self.parent().growth_group.one(), log(self.coefficient, base=base)),) + + + def _le_(self, other): + r""" + Return whether this asymptotic term with coefficient grows + at most (less than or equal) like ``other``. + + INPUT: + + - ``other`` -- an asymptotic term with coefficient. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element and ``other`` are + from the same parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, QQ) + sage: t1 = ET(x, 5); t2 = ET(x^2, 3); t3 = ET(x^2, 42) + sage: t1 <= t2 + True + sage: t2 <= t1 + False + sage: t2 <= t3 + False + sage: t3 <= t2 + False + sage: t2 <= t2 + True + + TESTS:: + + sage: ET(x, -2) <= ET(x, 1) + False + """ + if self.growth == other.growth: + return self.coefficient == other.coefficient + else: + return super(TermWithCoefficient, self)._le_(other) + + + def _eq_(self, other): + r""" + Return whether this :class:`TermWithCoefficient` is the same as + ``other``. + + INPUT: + + - ``other`` -- an :class:`TermWithCoefficient`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This method gets called by the coercion model, so it can + be assumed that this term and ``other`` come from the + same parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), ZZ) + sage: t = T.an_element(); t + Term with coefficient 1 and growth x + sage: t == T(x, 1) + True + sage: t == T(x, 2) + False + sage: t == T(x^2, 1) + False + """ + return super(TermWithCoefficient, self)._eq_(other) and \ + self.coefficient == other.coefficient + + +class TermWithCoefficientMonoid(GenericTermMonoid): + r""" + This class implements the base structure for parents of + asymptotic terms possessing a coefficient from some coefficient + ring. In particular, this is also the parent for + :class:`TermWithCoefficient`. + + INPUT: + + - ``growth_group`` -- a growth group. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + - ``coefficient_ring`` -- the ring which contains the + coefficients of the elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, QQ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_ZZ == TC_QQ or TC_ZZ is TC_QQ + False + sage: TC_QQ.coerce_map_from(TC_ZZ) + Conversion map: + From: Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + To: Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + """ + + # enable the category framework for elements + Element = TermWithCoefficient + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen(), 4/3) # indirect doctest + 4/3*x + """ + return self.element_class(self, growth, coefficient) + + + def _an_element_(self): + r""" + Return an element of this term with coefficient monoid. + + INPUT: + + Nothing. + + OUTPUT: + + An element of this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermWithCoefficientMonoid(G, ZZ).an_element() # indirect doctest + Term with coefficient 1 and growth x + sage: TermMonoid('exact', G, ZZ).an_element() # indirect doctest + x + sage: TermMonoid('exact', G, QQ).an_element() # indirect doctest + 1/2*x + """ + return self(self.growth_group.an_element(), + self.coefficient_ring.an_element()) + + + def some_elements(self): + r""" + Return some elements of this term with coefficient monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^QQ') + sage: T = TermMonoid('exact', G, ZZ) + sage: tuple(islice(T.some_elements(), 10)) + (z^(1/2), z^(-1/2), -z^(1/2), z^2, -z^(-1/2), 2*z^(1/2), + z^(-2), -z^2, 2*z^(-1/2), -2*z^(1/2)) + """ + from sage.misc.mrange import cantor_product + return iter(self(g, c) for g, c in cantor_product( + self.growth_group.some_elements(), + iter(c for c in self.coefficient_ring.some_elements() if c != 0))) + + +class ExactTerm(TermWithCoefficient): + r""" + Class for asymptotic exact terms. These terms primarily consist of + an asymptotic growth element as well as a coefficient specifying + the growth of the asymptotic term. + + INPUT: + + - ``parent`` -- the parent of the asymptotic term. + + - ``growth`` -- an asymptotic growth element from + ``parent.growth_group``. + + - ``coefficient`` -- an element from ``parent.coefficient_ring``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (ExactTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = ExactTermMonoid(G, QQ) + + Asymptotic exact terms may be multiplied (with the usual rules + applying):: + + sage: ET(x^2, 3) * ET(x, -1) + -3*x^3 + sage: ET(x^0, 4) * ET(x^5, 2) + 8*x^5 + + They may also be multiplied with `O`-terms:: + + sage: OT = TermMonoid('O', G, QQ) + sage: ET(x^2, 42) * OT(x) + O(x^3) + + Absorption for asymptotic exact terms relates to addition:: + + sage: ET(x^2, 5).can_absorb(ET(x^5, 12)) + False + sage: ET(x^2, 5).can_absorb(ET(x^2, 1)) + True + sage: ET(x^2, 5).absorb(ET(x^2, 1)) + 6*x^2 + + Note that, as for technical reasons, `0` is not allowed as a + coefficient for an asymptotic term with coefficient. Instead ``None`` + is returned if two asymptotic exact terms cancel out each other + during absorption:: + + sage: ET(x^2, 42).can_absorb(ET(x^2, -42)) + True + sage: ET(x^2, 42).absorb(ET(x^2, -42)) is None + True + + Exact terms can also be created by converting monomials with + coefficient from the symbolic ring, or a suitable polynomial + or power series ring:: + + sage: x = var('x'); x.parent() + Symbolic Ring + sage: ET(5*x^2) + 5*x^2 + """ + + def _repr_(self): + r""" + A representation string for this exact term. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, ZZ) + sage: et1 = ET(x^2, 2); et1 + 2*x^2 + + TESTS:: + + sage: ET(x^2, 1) + x^2 + sage: ET(x^2, -1) + -x^2 + sage: ET(x^0, 42) + 42 + """ + g = repr(self.growth) + c = repr(self.coefficient) + if g == '1': + return c + elif c == '1': + return '%s' % (g,) + elif c == '-1': + return '-%s' % (g,) + else: + return '%s*%s' % (c, g) + + + def __invert__(self): + r""" + Invert this term. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('exact', G, ZZ) + sage: ~T(x, 2) # indirect doctest + 1/2*x^(-1) + sage: (~T(x, 2)).parent() + Exact Term Monoid x^ZZ with coefficients in Rational Field + """ + try: + c = ~self.coefficient + except ZeroDivisionError: + raise ZeroDivisionError('Cannot invert %s since its coefficient %s ' + 'cannot be inverted.' % (self, self.coefficient)) + g = ~self.growth + return self.parent()._create_element_in_extension_(g, c) + + + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`ExactTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`ExactTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: t = T('2*z'); t + 2*z + sage: t^3 # indirect doctest + 8*z^3 + sage: t^(1/2) # indirect doctest + sqrt(2)*z^(1/2) + """ + return self._calculate_pow_(exponent) + + + def can_absorb(self, other): + r""" + Check whether this exact term can absorb ``other``. + + INPUT: + + - ``other`` -- an asymptotic term. + + OUTPUT: + + A boolean. + + .. NOTE:: + + For :class:`ExactTerm`, absorption corresponds to + addition. This means that an exact term can absorb + only other exact terms with the same growth. + + See the :ref:`module description ` for a + detailed explanation of absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: ET = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: t1 = ET(x^21, 1); t2 = ET(x^21, 2); t3 = ET(x^42, 1) + sage: t1.can_absorb(t2) + True + sage: t2.can_absorb(t1) + True + sage: t1.can_absorb(t3) or t3.can_absorb(t1) + False + """ + return isinstance(other, ExactTerm) and self.growth == other.growth + + + def _absorb_(self, other): + r""" + Let this exact term absorb another exact term ``other``. + + INPUT: + + - ``other`` -- an exact term. + + OUTPUT: + + An exact term or ``None``. + + .. NOTE:: + + In the context of exact terms, absorption translates + to addition. As the coefficient `0` is not allowed, + ``None`` is returned instead if the terms cancel out. + + See the :ref:`module description ` for a + detailed explanation on absorption. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, QQ) + + Asymptotic exact terms can absorb other asymptotic exact + terms with the same growth:: + + sage: et1 = ET(x^2, 2); et2 = ET(x^2, -2) + sage: et1.absorb(et1) + 4*x^2 + sage: et1.absorb(et2) is None + True + + If the growth differs, an ``ArithmeticException`` is raised:: + + sage: ET(x^5, 1).absorb(et1) + Traceback (most recent call last): + ... + ArithmeticError: x^5 cannot absorb 2*x^2 + """ + coeff_new = self.coefficient + other.coefficient + if coeff_new.is_zero(): + return None + else: + return self.parent()(self.growth, coeff_new) + + + def log_term(self, base=None): + r""" + Determine the logarithm of this exact term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), SR) + sage: T(3*x^2).log_term() + (log(3), 2*log(x)) + sage: T(x^1234).log_term() + (1234*log(x),) + sage: T(49*x^7).log_term(base=7) + (log(49)/log(7), 7/log(7)*log(x)) + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), SR) + sage: T('x * y').log_term() + (log(x), log(y)) + sage: T('4 * x * y').log_term(base=2) + (log(4)/log(2), 1/log(2)*log(x), 1/log(2)*log(y)) + + .. SEEALSO:: + + :meth:`OTerm.log_term`. + """ + return self._log_coefficient_(base=base) + self._log_growth_(base=base) + + + def is_constant(self): + r""" + Return whether this term is an (exact) constant. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T('x * log(x)').is_constant() + False + sage: T('3*x').is_constant() + False + sage: T(1/2).is_constant() + True + sage: T(42).is_constant() + True + """ + return self.growth.is_one() + + + def is_little_o_of_one(self): + r""" + Return whether this exact term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() + False + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() + True + """ + return self.growth.is_lt_one() + + + def rpow(self, base): + r""" + Return the power of ``base`` to this exact term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ'), QQ) + sage: T('x').rpow(2) + 2^x + sage: T('log(x)').rpow('e') + x + sage: T('42*log(x)').rpow('e') + x^42 + sage: T('3*x').rpow(2) + 8^x + + :: + + sage: T('3*x^2').rpow(2) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in + Growth Group QQ^x * x^ZZ * log(x)^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ * log(x)^ZZ' and 'Growth Group ZZ^(x^2)' + """ + P = self.parent() + + if self.is_constant(): + if not hasattr(base, 'parent'): + base = P.coefficient_ring(base) + return P._create_element_in_extension_( + P.growth_group.one(), base ** self.coefficient) + + elem = P._create_element_in_extension_( + self.growth.rpow(base), P.coefficient_ring.one()) + return elem ** self.coefficient + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exact term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: E = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: e = E.an_element(); e + x + sage: e._substitute_({'x': SR.var('z')}) + z + sage: E(2/x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 2*x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: (e*e)._substitute_({'x': 'something'}) + 'somethingsomething' + sage: E(1/x)._substitute_({'x': 'something'}) + '' + sage: E(1/x)._substitute_({'x': ZZ}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ValueError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + >> *previous* ValueError: rank (=-1) must be nonnegative + """ + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + c = self.coefficient + + try: + return c * g + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class ExactTermMonoid(TermWithCoefficientMonoid): + r""" + Parent for asymptotic exact terms, implemented in + :class:`ExactTerm`. + + INPUT: + + - ``growth_group`` -- a growth group. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + - ``coefficient_ring`` -- the ring which contains the coefficients of + the elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import ExactTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: ET_ZZ = ExactTermMonoid(G_ZZ, ZZ); ET_ZZ + Exact Term Monoid x^ZZ with coefficients in Integer Ring + sage: ET_QQ = ExactTermMonoid(G_QQ, QQ); ET_QQ + Exact Term Monoid x^QQ with coefficients in Rational Field + sage: ET_QQ.coerce_map_from(ET_ZZ) + Conversion map: + From: Exact Term Monoid x^ZZ with coefficients in Integer Ring + To: Exact Term Monoid x^QQ with coefficients in Rational Field + + Exact term monoids can also be created using the + :class:`term factory `:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', G_ZZ, ZZ) is ET_ZZ + True + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + Exact Term Monoid x^ZZ with coefficients in Rational Field + """ + + # enable the category framework for elements + Element = ExactTerm + + + def _repr_(self): + r""" + A representation string for this exact term monoid. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('exact', G, QQ)._repr_() + 'Exact Term Monoid x^ZZ with coefficients in Rational Field' + """ + return 'Exact Term Monoid %s with coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) + + +class TermMonoidFactory(sage.structure.factory.UniqueFactory): + r""" + Factory for asymptotic term monoids. It can generate the following + term monoids: + + - :class:`OTermMonoid`, + + - :class:`ExactTermMonoid`. + + .. NOTE:: + + An instance of this factory is available as ``TermMonoid``. + + INPUT: + + - ``term`` -- the kind of term that shall be created. Either a string + ``'exact'`` or ``'O'`` (capital letter ``O``), + or an existing instance of a term. + + - ``growth_group`` -- a growth group. + + - ``coefficient_ring`` -- a ring. + + - ``asymptotic_ring`` -- if specified, then ``growth_group`` and + ``coefficient_ring`` are taken from this asymptotic ring. + + OUTPUT: + + An asymptotic term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) + Exact Term Monoid x^ZZ with coefficients in Integer Ring + + :: + + sage: R = AsymptoticRing(growth_group=G, coefficient_ring=QQ) + sage: TermMonoid('exact', asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: TermMonoid('O', asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + + TESTS:: + + sage: TermMonoid(TermMonoid('O', G, ZZ), asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid(TermMonoid('exact', G, ZZ), asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: TermMonoid(GenericTermMonoid(G, ZZ), asymptotic_ring=R) + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + + :: + + sage: TestSuite(TermMonoid('exact', GrowthGroup('x^ZZ'), QQ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(TermMonoid('O', GrowthGroup('x^QQ'), ZZ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + """ + def create_key_and_extra_args(self, term, + growth_group=None, coefficient_ring=None, + asymptotic_ring=None, + **kwds): + r""" + Given the arguments and keyword, create a key that uniquely + determines this object. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid.create_key_and_extra_args('O', G, QQ) + ((, + Growth Group x^ZZ, Rational Field), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G, ZZ) + ((, + Growth Group x^ZZ, Integer Ring), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G) + Traceback (most recent call last): + ... + ValueError: A coefficient ring has to be specified + to create a term monoid of type 'exact' + + TESTS:: + + sage: TermMonoid.create_key_and_extra_args('icecream', G) + Traceback (most recent call last): + ... + ValueError: Term specification 'icecream' has to be either + 'exact' or 'O' or an instance of an existing term. + sage: TermMonoid.create_key_and_extra_args('O', ZZ) + Traceback (most recent call last): + ... + ValueError: Integer Ring has to be an asymptotic growth group + """ + if isinstance(term, GenericTermMonoid): + from misc import underlying_class + term_class = underlying_class(term) + elif term == 'O': + term_class = OTermMonoid + elif term == 'exact': + term_class = ExactTermMonoid + else: + raise ValueError("Term specification '%s' has to be either 'exact' or 'O' " + "or an instance of an existing term." % term) + + if asymptotic_ring is not None and \ + (growth_group is not None or coefficient_ring is not None): + raise ValueError("Input ambiguous: asymptotic ring %s as well as " + "growth group %s or coefficient ring %s are given." % + (asymptotic_ring, growth_group, coefficient_ring)) + + if asymptotic_ring is not None: + growth_group = asymptotic_ring.growth_group + coefficient_ring = asymptotic_ring.coefficient_ring + + from growth_group import GenericGrowthGroup + if not isinstance(growth_group, GenericGrowthGroup): + raise ValueError("%s has to be an asymptotic growth group" + % growth_group) + + if coefficient_ring is None: + raise ValueError("A coefficient ring has to be specified to " + "create a term monoid of type '%s'" % (term,)) + + return (term_class, growth_group, coefficient_ring), kwds + + + def create_object(self, version, key, **kwds): + r""" + Create a object from the given arguments. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) # indirect doctest + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) # indirect doctest + Exact Term Monoid x^ZZ with coefficients in Integer Ring + """ + term_class, growth_group, coefficient_ring = key + return term_class(growth_group, coefficient_ring, **kwds) + + +TermMonoid = TermMonoidFactory("TermMonoid") +r""" +A factory for asymptotic term monoids. +This is an instance of :class:`TermMonoidFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 7f0d49f541e..0e98a4a5bec 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -1,5 +1,12 @@ """ Big O for various types (power series, p-adics, etc.) + +.. SEEALSO:: + + - `asymptotic expansions <../../asymptotic_expansions_index.html>`_ + - `p-adic numbers <../../../padics/index.html>`_ + - `power series <../../../power_series/index.html>`_ + - `polynomials <../../../polynomial_rings/index.html>`_ """ import sage.rings.arith as arith @@ -12,19 +19,21 @@ from sage.rings.polynomial.polynomial_element import Polynomial import multi_power_series_ring_element -def O(*x): + +def O(*x, **kwds): """ Big O constructor for various types. EXAMPLES: - This is useful for writing power series elements. :: + This is useful for writing power series elements:: sage: R. = ZZ[['t']] sage: (1+t)^10 + O(t^5) 1 + 10*t + 45*t^2 + 120*t^3 + 210*t^4 + O(t^5) - A power series ring is created implicitly if a polynomial element is passed in. :: + A power series ring is created implicitly if a polynomial + element is passed:: sage: R. = QQ['x'] sage: O(x^100) @@ -35,26 +44,29 @@ def O(*x): sage: 1 + u + v^2 + O(u, v)^5 1 + u + v^2 + O(u, v)^5 - This is also useful to create p-adic numbers. :: + This is also useful to create `p`-adic numbers:: sage: O(7^6) O(7^6) sage: 1/3 + O(7^6) 5 + 4*7 + 4*7^2 + 4*7^3 + 4*7^4 + 4*7^5 + O(7^6) - It behaves well with respect to adding negative powers of p:: + It behaves well with respect to adding negative powers of `p`:: sage: a = O(11^-32); a O(11^-32) sage: a.parent() 11-adic Field with capped relative precision 20 - There are problems if you add a rational with very negative valuation to a big_oh. :: + There are problems if you add a rational with very negative + valuation to an `O`-Term:: sage: 11^-12 + O(11^15) 11^-12 + O(11^8) - The reason that this fails is that the O function doesn't know the right precision cap to use. If you cast explicitly or use other means of element creation, you can get around this issue. :: + The reason that this fails is that the constructor doesn't know + the right precision cap to use. If you cast explicitly or use + other means of element creation, you can get around this issue:: sage: K = Qp(11, 30) sage: K(11^-12) + O(11^15) @@ -66,26 +78,64 @@ def O(*x): sage: K(11^-12, 15) 11^-12 + O(11^15) + We can also work with :doc:`asymptotic expansions + `:: + + sage: A. = AsymptoticRing(growth_group='QQ^n * n^QQ * log(n)^QQ', coefficient_ring=QQ); A + doctest:...: FutureWarning: + This class/method/function is marked as experimental. ... + Asymptotic Ring over Rational Field + sage: O(n) + O(n) + + TESTS:: + + sage: var('x, y') + (x, y) + sage: O(x) + Traceback (most recent call last): + ... + ArithmeticError: O(x) not defined + sage: O(y) + Traceback (most recent call last): + ... + ArithmeticError: O(y) not defined + sage: O(x, y) + Traceback (most recent call last): + ... + ArithmeticError: O(x, y) not defined + sage: O(4, 2) + Traceback (most recent call last): + ... + ArithmeticError: O(4, 2) not defined """ if len(x) > 1: if isinstance(x[0], multi_power_series_ring_element.MPowerSeries): - return multi_power_series_ring_element.MO(x) + return multi_power_series_ring_element.MO(x, **kwds) + else: + raise ArithmeticError("O(%s) not defined" % + (', '.join(str(e) for e in x),)) + x = x[0] if isinstance(x, power_series_ring_element.PowerSeries): - return x.parent()(0, x.degree()) + return x.parent()(0, x.degree(), **kwds) elif isinstance(x, Polynomial): if x.parent().ngens() != 1: - raise NotImplementedError("completion only currently defined for univariate polynomials") + raise NotImplementedError("completion only currently defined " + "for univariate polynomials") if not x.is_monomial(): - raise NotImplementedError("completion only currently defined for the maximal ideal (x)") - return x.parent().completion(x.parent().gen())(0, x.degree()) + raise NotImplementedError("completion only currently defined " + "for the maximal ideal (x)") + return x.parent().completion(x.parent().gen())(0, x.degree(), **kwds) elif isinstance(x, laurent_series_ring_element.LaurentSeries): - return laurent_series_ring_element.LaurentSeries(x.parent(), 0).add_bigoh(x.valuation()) + return laurent_series_ring_element.LaurentSeries(x.parent(), 0).\ + add_bigoh(x.valuation(), **kwds) - elif isinstance(x, (int,long,integer.Integer,rational.Rational)): # p-adic number + elif isinstance(x, (int, long, integer.Integer, rational.Rational)): + # p-adic number if x <= 0: raise ArithmeticError("x must be a prime power >= 2") F = arith.factor(x) @@ -93,11 +143,14 @@ def O(*x): raise ArithmeticError("x must be prime power") p, r = F[0] if r >= 0: - return padics_factory.Zp(p, prec = max(r, 20), type = 'capped-rel')(0, absprec = r) + return padics_factory.Zp(p, prec=max(r, 20), + type='capped-rel')(0, absprec=r, **kwds) else: - return padics_factory.Qp(p, prec = max(r, 20), type = 'capped-rel')(0, absprec = r) + return padics_factory.Qp(p, prec=max(r, 20), + type='capped-rel')(0, absprec=r, **kwds) elif isinstance(x, padic_generic_element.pAdicGenericElement): - return x.parent()(0, absprec = x.valuation()) - raise ArithmeticError("O(x) not defined") - + return x.parent()(0, absprec=x.valuation(), **kwds) + elif hasattr(x, 'O'): + return x.O(**kwds) + raise ArithmeticError("O(%s) not defined" % (x,)) diff --git a/src/sage/rings/cfinite_sequence.py b/src/sage/rings/cfinite_sequence.py index a33e1c75d01..972392426db 100644 --- a/src/sage/rings/cfinite_sequence.py +++ b/src/sage/rings/cfinite_sequence.py @@ -429,6 +429,18 @@ def _repr_(self): else: return 'C-finite sequence, generated by ' + str(self.ogf()) + def __hash__(self): + r""" + Hash value for C finite sequence. + + EXAMPLES:: + + sage: C. = CFiniteSequences(QQ) + sage: hash(C((2-x)/(1-x-x^2))) # random + 42 + """ + return hash(self.parent()) ^ hash(self._ogf) + def _add_(self, other): """ Addition of C-finite sequences. diff --git a/src/sage/rings/commutative_algebra_element.py b/src/sage/rings/commutative_algebra_element.py index 3d5252a521a..c469bdd45a0 100644 --- a/src/sage/rings/commutative_algebra_element.py +++ b/src/sage/rings/commutative_algebra_element.py @@ -17,4 +17,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.commutative_algebra_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import CommutativeAlgebraElement, is_CommutativeAlgebraElement diff --git a/src/sage/rings/commutative_ring_element.py b/src/sage/rings/commutative_ring_element.py index 1089a932a88..756fd32d92b 100644 --- a/src/sage/rings/commutative_ring_element.py +++ b/src/sage/rings/commutative_ring_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.commutative_ring_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import CommutativeRingElement def is_CommutativeRingElement(x): @@ -25,6 +28,9 @@ def is_CommutativeRingElement(x): EXAMPLES:: + sage: import sage.rings.commutative_ring_element + doctest:...: DeprecationWarning: the module sage.rings.commutative_ring_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.commutative_ring_element.is_CommutativeRingElement(ZZ(2)) True """ diff --git a/src/sage/rings/complex_ball_acb.pxd b/src/sage/rings/complex_arb.pxd similarity index 76% rename from src/sage/rings/complex_ball_acb.pxd rename to src/sage/rings/complex_arb.pxd index 094ce48b392..0f9882f36ef 100644 --- a/src/sage/rings/complex_ball_acb.pxd +++ b/src/sage/rings/complex_arb.pxd @@ -1,7 +1,7 @@ from sage.libs.arb.acb cimport acb_t from sage.rings.complex_interval cimport ComplexIntervalFieldElement from sage.rings.real_arb cimport RealBall -from sage.structure.element cimport Element +from sage.structure.element cimport RingElement cdef void ComplexIntervalFieldElement_to_acb( acb_t target, @@ -11,9 +11,9 @@ cdef int acb_to_ComplexIntervalFieldElement( ComplexIntervalFieldElement target, const acb_t source) except -1 -cdef class ComplexBall(Element): +cdef class ComplexBall(RingElement): cdef acb_t value cdef ComplexBall _new(self) - cpdef ComplexIntervalFieldElement _interval(self) + cpdef ComplexIntervalFieldElement _complex_mpfi_(self, parent) cpdef RealBall real(self) cpdef RealBall imag(self) diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx new file mode 100644 index 00000000000..43b130dc06b --- /dev/null +++ b/src/sage/rings/complex_arb.pyx @@ -0,0 +1,1845 @@ +# -*- coding: utf-8 +r""" +Arbitrary precision complex balls using Arb + +This is a binding to the `Arb library `_; it +may be useful to refer to its documentation for more details. + +Parts of the documentation for this module are copied or adapted from +Arb's own documentation, licenced under the GNU General Public License +version 2, or later. + +.. SEEALSO:: + + - :mod:`Real balls using Arb ` + - :mod:`Complex interval field (using MPFI) ` + - :mod:`Complex intervals (using MPFI) ` + +Data Structure +============== + +A :class:`ComplexBall` represents a complex number with error bounds. It wraps +an Arb object of type ``acb_t``, which consists of a pair of real number balls +representing the real and imaginary part with separate error bounds. (See the +documentation of :mod:`sage.rings.real_arb` for more information.) + +A :class:`ComplexBall` thus represents a rectangle `[m_1-r_1, m_1+r_1] + +[m_2-r_2, m_2+r_2] i` in the complex plane. This is used in Arb instead of a +disk or square representation (consisting of a complex floating-point midpoint +with a single radius), since it allows implementing many operations more +conveniently by splitting into ball operations on the real and imaginary parts. +It also allows tracking when complex numbers have an exact (for example exactly +zero) real part and an inexact imaginary part, or vice versa. + +Comparison +========== + +.. WARNING:: + + In accordance with the semantics of Arb, identical :class:`ComplexBall` + objects are understood to give permission for algebraic simplification. + This assumption is made to improve performance. For example, setting ``z = + x*x`` sets `z` to a ball enclosing the set `\{t^2 : t \in x\}` and not the + (generally larger) set `\{tu : t \in x, u \in x\}`. + +Two elements are equal if and only if they are the same object +or if both are exact and equal:: + + sage: a = CBF(1, 2) + sage: b = CBF(1, 2) + sage: a is b + False + sage: a == b + True + sage: a = CBF(1/3, 1/5) + sage: b = CBF(1/3, 1/5) + sage: a.is_exact() + False + sage: b.is_exact() + False + sage: a is b + False + sage: a == b + False + +A ball is non-zero in the sense of usual comparison if and only if it does not +contain zero:: + + sage: a = CBF(RIF(-0.5, 0.5)) + sage: a != 0 + False + sage: b = CBF(1/3, 1/5) + sage: b != 0 + True + +However, ``bool(b)`` returns ``False`` for a ball ``b`` only if ``b`` is exactly +zero:: + + sage: bool(a) + True + sage: bool(b) + True + sage: bool(CBF.zero()) + False + +Coercion +======== + +Automatic coercions work as expected:: + + sage: bpol = 1/3*CBF(i) + AA(sqrt(2)) + (polygen(RealBallField(20), 'x') + QQbar(i)) + sage: bpol + x + [1.41421 +/- 5.09e-6] + [1.33333 +/- 3.97e-6]*I + sage: bpol.parent() + Univariate Polynomial Ring in x over Complex ball field with 20 bits precision + sage: bpol/3 + ([0.333333 +/- 4.93e-7])*x + [0.47140 +/- 5.39e-6] + [0.44444 +/- 4.98e-6]*I + +TESTS:: + + sage: polygen(CBF, x)^3 + x^3 + +Classes and Methods +=================== +""" +#***************************************************************************** +# Copyright (C) 2014 Clemens Heuberger +# +# 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/ +#***************************************************************************** +include 'sage/ext/interrupt.pxi' +include "sage/ext/stdsage.pxi" + +import sage.categories.fields + +cimport sage.rings.integer +cimport sage.rings.rational + +from cpython.float cimport PyFloat_AS_DOUBLE +from cpython.int cimport PyInt_AS_LONG +from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE + +from sage.libs.mpfr cimport MPFR_RNDU +from sage.libs.arb.arb cimport * +from sage.libs.arb.acb cimport * +from sage.libs.arb.acb_hypgeom cimport * +from sage.libs.arb.arf cimport arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, arf_set_mag, arf_set +from sage.libs.arb.mag cimport mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero +from sage.libs.flint.fmpz cimport fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear +from sage.libs.flint.fmpq cimport fmpq_t, fmpq_init, fmpq_set_mpq, fmpq_clear +from sage.rings.complex_field import ComplexField +from sage.rings.complex_interval_field import ComplexIntervalField +from sage.rings.real_arb cimport mpfi_to_arb, arb_to_mpfi +from sage.rings.real_arb import RealBallField +from sage.rings.real_mpfr cimport RealField_class, RealField, RealNumber +from sage.rings.ring import Field +from sage.structure.element cimport Element, ModuleElement +from sage.structure.parent cimport Parent +from sage.structure.unique_representation import UniqueRepresentation + +cdef void ComplexIntervalFieldElement_to_acb( + acb_t target, + ComplexIntervalFieldElement source): + """ + Convert a :class:`ComplexIntervalFieldElement` to an ``acb``. + + INPUT: + + - ``target`` -- an ``acb_t`` + + - ``source`` -- a :class:`ComplexIntervalFieldElement` + + OUTPUT: + + None. + """ + cdef long precision + precision = source.parent().precision() + mpfi_to_arb(acb_realref(target), source.__re, precision) + mpfi_to_arb(acb_imagref(target), source.__im, precision) + +cdef int acb_to_ComplexIntervalFieldElement( + ComplexIntervalFieldElement target, + const acb_t source) except -1: + """ + Convert an ``acb`` to a :class:`ComplexIntervalFieldElement`. + + INPUT: + + - ``target`` -- a :class:`ComplexIntervalFieldElement` + + - ``source`` -- an ``acb_t`` + + OUTPUT: + + A :class:`ComplexIntervalFieldElement`. + """ + cdef long precision = target._prec + + arb_to_mpfi(target.__re, acb_realref(source), precision) + arb_to_mpfi(target.__im, acb_imagref(source), precision) + return 0 + +class ComplexBallField(UniqueRepresentation, Field): + r""" + An approximation of the field of complex numbers using pairs of mid-rad + intervals. + + INPUT: + + - ``precision`` -- an integer `\ge 2`. + + EXAMPLES:: + + sage: CBF = ComplexBallField() # indirect doctest + sage: CBF(1) + 1.000000000000000 + + TESTS:: + + sage: ComplexBallField(0) + Traceback (most recent call last): + ... + ValueError: Precision must be at least 2. + sage: ComplexBallField(1) + Traceback (most recent call last): + ... + ValueError: Precision must be at least 2. + """ + Element = ComplexBall + + @staticmethod + def __classcall__(cls, long precision=53, category=None): + r""" + Normalize the arguments for caching. + + TESTS:: + + sage: ComplexBallField(53) is ComplexBallField() + True + """ + return super(ComplexBallField, cls).__classcall__(cls, precision, category) + + def __init__(self, precision, category): + r""" + Initialize the complex ball field. + + INPUT: + + - ``precision`` -- an integer `\ge 2`. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: CBF(1) + 1.000000000000000 + + TESTS:: + + sage: CBF.base() + Real ball field with 53 bits precision + sage: CBF.base_ring() + Real ball field with 53 bits precision + + There are direct coercions from ZZ and QQ (for which arb provides + construction functions):: + + sage: CBF.coerce_map_from(ZZ) + Conversion map: + From: Integer Ring + To: Complex ball field with 53 bits precision + sage: CBF.coerce_map_from(QQ) + Conversion map: + From: Rational Field + To: Complex ball field with 53 bits precision + + Various other coercions are available through real ball fields or CLF:: + + sage: CBF.coerce_map_from(RLF) + Composite map: + From: Real Lazy Field + To: Complex ball field with 53 bits precision + Defn: Conversion map: + From: Real Lazy Field + To: Real ball field with 53 bits precision + then + Conversion map: + From: Real ball field with 53 bits precision + To: Complex ball field with 53 bits precision + sage: CBF.has_coerce_map_from(AA) + True + sage: CBF.has_coerce_map_from(QuadraticField(-1)) + True + sage: CBF.has_coerce_map_from(QQbar) + True + sage: CBF.has_coerce_map_from(CLF) + True + """ + if precision < 2: + raise ValueError("Precision must be at least 2.") + real_field = RealBallField(precision) + super(ComplexBallField, self).__init__( + base_ring=real_field, + category=category or sage.categories.fields.Fields().Infinite()) + self._prec = precision + from sage.rings.integer_ring import ZZ + from sage.rings.rational_field import QQ + from sage.rings.real_lazy import CLF + self._populate_coercion_lists_([ZZ, QQ, real_field, CLF]) + + def _real_field(self): + """ + TESTS:: + + sage: CBF._real_field() + Real ball field with 53 bits precision + """ + return self._base + + def _repr_(self): + r""" + String representation of ``self``. + + EXAMPLES:: + + sage: ComplexBallField() + Complex ball field with 53 bits precision + sage: ComplexBallField(106) + Complex ball field with 106 bits precision + """ + return "Complex ball field with {} bits precision".format(self._prec) + + def construction(self): + """ + Return the construction of a complex ball field as the algebraic + closure of the real ball field with the same precision. + + EXAMPLES:: + + sage: functor, base = CBF.construction() + sage: functor, base + (AlgebraicClosureFunctor, Real ball field with 53 bits precision) + sage: functor(base) is CBF + True + """ + from sage.categories.pushout import AlgebraicClosureFunctor + return (AlgebraicClosureFunctor(), self._base) + + def complex_field(self): + """ + Return the complex ball field with the same precision, i.e. ``self`` + + EXAMPLES:: + + sage: CBF.complex_field() is CBF + True + """ + return ComplexBallField(self._prec) + + def ngens(self): + r""" + Return 1 as the only generator is the imaginary unit. + + EXAMPLE:: + + sage: CBF.ngens() + 1 + """ + return 1 + + def gen(self, i): + r""" + For i = 0, return the imaginary unit in this complex ball field. + + EXAMPLE:: + + sage: CBF.0 + 1.000000000000000*I + sage: CBF.gen(1) + Traceback (most recent call last): + ... + ValueError: only one generator + """ + if i == 0: + return self(0, 1) + else: + raise ValueError("only one generator") + + def gens(self): + r""" + Return the tuple of generators of this complex ball field, i.e. + ``(i,)``. + + EXAMPLE:: + + sage: CBF.gens() + (1.000000000000000*I,) + sage: CBF.gens_dict() + {'1.000000000000000*I': 1.000000000000000*I} + """ + return (self(0, 1),) + + def _coerce_map_from_(self, other): + r""" + Parents that canonically coerce into complex ball fields include: + + - anything that coerces into the corresponding real ball field; + + - real and complex ball fields with a larger precision; + + - various exact or lazy parents representing subsets of the complex + numbers, such as ``QQbar``, ``CLF``, and number fields equipped + with complex embeddings. + + TESTS:: + + sage: CBF.coerce_map_from(CBF) + Identity endomorphism of Complex ball field with 53 bits precision + sage: CBF.coerce_map_from(ComplexBallField(100)) + Conversion map: + From: Complex ball field with 100 bits precision + To: Complex ball field with 53 bits precision + sage: CBF.has_coerce_map_from(ComplexBallField(42)) + False + sage: CBF.has_coerce_map_from(RealBallField(54)) + True + sage: CBF.has_coerce_map_from(RealBallField(52)) + False + + Check that there are no coercions from interval or floating-point parents:: + + sage: CBF.has_coerce_map_from(RIF) + False + sage: CBF.has_coerce_map_from(CIF) + False + sage: CBF.has_coerce_map_from(RR) + False + sage: CBF.has_coerce_map_from(CC) + False + """ + if isinstance(other, (RealBallField, ComplexBallField)): + return (other._prec >= self._prec) + + def _element_constructor_(self, x=None, y=None): + r""" + Convert (x, y) to an element of this complex ball field, perhaps + non-canonically. + + INPUT: + + - ``x``, ``y`` (optional) -- either a complex number, interval or ball, + or two real ones (see examples below for more information on accepted + number types). + + EXAMPLES:: + + sage: CBF() + 0 + sage: CBF(1) # indirect doctest + 1.000000000000000 + sage: CBF(1, 1) + 1.000000000000000 + 1.000000000000000*I + sage: CBF(pi, sqrt(2)) + [3.141592653589793 +/- 5.61e-16] + [1.414213562373095 +/- 4.10e-16]*I + sage: CBF(I) + 1.000000000000000*I + sage: CBF(pi+I/3) + [3.141592653589793 +/- 5.61e-16] + [0.3333333333333333 +/- 7.04e-17]*I + sage: CBF(QQbar(i/7)) + [0.1428571428571428 +/- 9.09e-17]*I + sage: CBF(AA(sqrt(2))) + [1.414213562373095 +/- 4.10e-16] + sage: CBF(CIF(0, 1)) + 1.000000000000000*I + sage: CBF(RBF(1/3)) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(RBF(1/3), RBF(1/6)) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + sage: CBF(1/3) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(1/3, 1/6) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + sage: ComplexBallField(106)(1/3, 1/6) + [0.33333333333333333333333333333333 +/- 6.94e-33] + [0.16666666666666666666666666666666 +/- 7.70e-33]*I + sage: CBF(infinity, NaN) + [+/- inf] + nan*I + sage: CBF(x) + Traceback (most recent call last): + ... + TypeError: unable to convert x to a ComplexBall + + .. SEEALSO:: + + :meth:`sage.rings.real_arb.RealBallField._element_constructor_` + + TESTS:: + + sage: CBF(1+I, 2) + Traceback (most recent call last): + ... + TypeError: unable to convert I + 1 to a RealBall + """ + try: + return self.element_class(self, x, y) + except TypeError: + pass + + if y is None: + try: + x = self._base(x) + return self.element_class(self, x) + except (TypeError, ValueError): + pass + try: + y = self._base(x.imag()) + x = self._base(x.real()) + return self.element_class(self, x, y) + except (AttributeError, TypeError): + pass + try: + x = ComplexIntervalField(self._prec)(x) + return self.element_class(self, x) + except TypeError: + pass + raise TypeError("unable to convert {} to a ComplexBall".format(x)) + else: + x = self._base(x) + y = self._base(y) + return self.element_class(self, x, y) + + def _an_element_(self): + r""" + Construct an element. + + EXAMPLES:: + + sage: CBF.an_element() # indirect doctest + [0.3333333333333333 +/- 1.49e-17] - [0.1666666666666667 +/- 4.26e-17]*I + """ + return self(1.0/3, -1.0/6) + + def precision(self): + """ + Return the bit precision used for operations on elements of this field. + + EXAMPLES:: + + sage: ComplexBallField().precision() + 53 + """ + return self._prec + + def is_exact(self): + """ + Complex ball fields are not exact. + + EXAMPLES:: + + sage: ComplexBallField().is_exact() + False + """ + return False + + def is_finite(self): + """ + Complex ball fields are infinite. + + They already specify it via their category, but we currently need to + re-implement this method due to the legacy implementation in + :class:`sage.rings.ring.Ring`. + + EXAMPLES:: + + sage: ComplexBallField().is_finite() + False + """ + return False + + def characteristic(self): + """ + Complex ball fields have characteristic zero. + + EXAMPLES:: + + sage: ComplexBallField().characteristic() + 0 + """ + return 0 + + def some_elements(self): + """ + Complex ball fields contain elements with exact, inexact, infinite, or + undefined real and imaginary parts. + + EXAMPLES:: + + sage: CBF.some_elements() + [1.000000000000000, + -0.5000000000000000*I, + 1.000000000000000 + [0.3333333333333333 +/- 1.49e-17]*I, + [-0.3333333333333333 +/- 1.49e-17] + 0.2500000000000000*I, + [-2.175556475109056e+181961467118333366510562 +/- 1.29e+181961467118333366510545], + [+/- inf], + [0.3333333333333333 +/- 1.49e-17] + [+/- inf]*I, + [+/- inf] + [+/- inf]*I, + nan, + nan + nan*I, + [+/- inf] + nan*I] + """ + return [self(1), self(0, -1./2), self(1, 1./3), self(-1./3, 1./4), + -self(1, 1)**(sage.rings.integer.Integer(2)**80), + self('inf'), self(1./3, 'inf'), self('inf', 'inf'), + self('nan'), self('nan', 'nan'), self('inf', 'nan')] + +cdef inline bint _do_sig(long prec): + """ + Whether signal handlers should be installed for calls to arb. + """ + return (prec > 1000) + +cdef inline long prec(ComplexBall ball): + return ball._parent._prec + +cdef inline Parent real_ball_field(ComplexBall ball): + return ball._parent._base + +cdef class ComplexBall(RingElement): + """ + Hold one ``acb_t`` of the `Arb library + `_ + + EXAMPLES:: + + sage: a = ComplexBallField()(1, 1) + sage: a + 1.000000000000000 + 1.000000000000000*I + """ + def __cinit__(self): + """ + Allocate memory for the encapsulated value. + + EXAMPLES:: + + sage: ComplexBallField(2)(0) # indirect doctest + 0 + """ + acb_init(self.value) + + def __dealloc__(self): + """ + Deallocate memory of the encapsulated value. + + EXAMPLES:: + + sage: a = ComplexBallField(2)(0) # indirect doctest + sage: del a + """ + acb_clear(self.value) + + def __init__(self, parent, x=None, y=None): + """ + Initialize the :class:`ComplexBall`. + + INPUT: + + - ``parent`` -- a :class:`ComplexBallField`. + + - ``x``, ``y`` (optional) -- either a complex number, interval or ball, + or two real ones. + + .. SEEALSO:: :meth:`ComplexBallField._element_constructor_` + + TESTS:: + + sage: from sage.rings.complex_arb import ComplexBall + sage: CBF53, CBF100 = ComplexBallField(53), ComplexBallField(100) + sage: ComplexBall(CBF100) + 0 + sage: ComplexBall(CBF100, ComplexBall(CBF53, ComplexBall(CBF100, 1/3))) + [0.333333333333333333333333333333 +/- 4.65e-31] + sage: ComplexBall(CBF100, RBF(pi)) + [3.141592653589793 +/- 5.61e-16] + sage: ComplexBall(CBF100, -3r) + -3.000000000000000000000000000000 + sage: ComplexBall(CBF100, 10^100) + 1.000000000000000000000000000000e+100 + sage: ComplexBall(CBF100, CIF(1, 2)) + 1.000000000000000000000000000000 + 2.000000000000000000000000000000*I + sage: ComplexBall(CBF100, RBF(1/3), RBF(1)) + [0.3333333333333333 +/- 7.04e-17] + 1.000000000000000000000000000000*I + sage: ComplexBall(CBF100, 1, 2) + Traceback (most recent call last): + ... + TypeError: unsupported initializer + """ + cdef fmpz_t tmpz + cdef fmpq_t tmpq + + RingElement.__init__(self, parent) + + if x is None: + return + elif y is None: + if isinstance(x, ComplexBall): + acb_set(self.value, ( x).value) + elif isinstance(x, RealBall): + acb_set_arb(self.value, ( x).value) + elif isinstance(x, int): + acb_set_si(self.value, PyInt_AS_LONG(x)) + elif isinstance(x, sage.rings.integer.Integer): + if _do_sig(prec(self)): sig_on() + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( x).value) + acb_set_fmpz(self.value, tmpz) + fmpz_clear(tmpz) + if _do_sig(prec(self)): sig_off() + elif isinstance(x, sage.rings.rational.Rational): + if _do_sig(prec(self)): sig_on() + fmpq_init(tmpq) + fmpq_set_mpq(tmpq, ( x).value) + acb_set_fmpq(self.value, tmpq, prec(self)) + fmpq_clear(tmpq) + if _do_sig(prec(self)): sig_off() + elif isinstance(x, ComplexIntervalFieldElement): + ComplexIntervalFieldElement_to_acb(self.value, + x) + else: + raise TypeError("unsupported initializer") + elif isinstance(x, RealBall) and isinstance(y, RealBall): + arb_set(acb_realref(self.value), ( x).value) + arb_set(acb_imagref(self.value), ( y).value) + else: + raise TypeError("unsupported initializer") + + cdef ComplexBall _new(self): + """ + Return a new complex ball element with the same parent as ``self``. + """ + cdef ComplexBall x + x = ComplexBall.__new__(ComplexBall) + x._parent = self._parent + return x + + def __hash__(self): + """ + TESTS:: + + sage: hash(CBF(1/3)) == hash(RBF(1/3)) + True + sage: hash(CBF(1/3 + 2*i)) != hash(CBF(1/3 + i)) + True + """ + if self.is_real(): + return hash(self.real()) + else: + return (hash(self.real()) // 3) ^ hash(self.imag()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: CBF(1/3) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(0, 1/3) + [0.3333333333333333 +/- 7.04e-17]*I + sage: CBF(1/3, 1/6) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + + TESTS:: + + sage: CBF(1-I/2) + 1.000000000000000 - 0.5000000000000000*I + """ + cdef arb_t real = acb_realref(self.value) + cdef arb_t imag = acb_imagref(self.value) + if arb_is_zero(imag): + return self.real()._repr_() + elif arb_is_zero(real): + return "{}*I".format(self.imag()._repr_()) + elif arb_is_exact(imag) and arb_is_negative(imag): + return "{} - {}*I".format(self.real()._repr_(), + (-self.imag())._repr_()) + else: + return "{} + {}*I".format(self.real()._repr_(), + self.imag()._repr_()) + + def _is_atomic(self): + r""" + Declare that complex balls print atomically in some cases. + + TESTS:: + + sage: CBF(-1/3)._is_atomic() + True + + This method should in principle ensure that ``CBF['x']([1, -1/3])`` + is printed as:: + + sage: CBF['x']([1, -1/3]) # todo - not tested + [-0.3333333333333333 +/- 7.04e-17]*x + 1.000000000000000 + + However, this facility is not really used in Sage at this point, and we + still get:: + + sage: CBF['x']([1, -1/3]) + ([-0.3333333333333333 +/- 7.04e-17])*x + 1.000000000000000 + """ + return self.is_real() or self.real().is_zero() + + # Conversions + + cpdef ComplexIntervalFieldElement _complex_mpfi_(self, parent): + """ + Return :class:`ComplexIntervalFieldElement` of the same value. + + EXAMPLES:: + + sage: CIF(CBF(1/3, 1/3)) # indirect doctest + 0.3333333333333333? + 0.3333333333333333?*I + """ + cdef ComplexIntervalFieldElement res = parent.zero() + res = res._new() # FIXME after modernizing CIF + acb_to_ComplexIntervalFieldElement(res, self.value) + return res + + def _integer_(self, _): + """ + Check that this ball contains a single integer and return that integer. + + EXAMPLES:: + + sage: ZZ(CBF(-42, RBF(.1, rad=.2))) # indirect doctest + -42 + sage: ZZ(CBF(i)) + Traceback (most recent call last): + ... + ValueError: 1.000000000000000*I does not contain a unique integer + """ + cdef sage.rings.integer.Integer res + cdef fmpz_t tmp + fmpz_init(tmp) + try: + if acb_get_unique_fmpz(tmp, self.value): + res = sage.rings.integer.Integer.__new__(sage.rings.integer.Integer) + fmpz_get_mpz(res.value, tmp) + else: + raise ValueError("{} does not contain a unique integer".format(self)) + finally: + fmpz_clear(tmp) + return res + + def _rational_(self): + """ + Check that this ball contains a single rational number and return that + number. + + EXAMPLES:: + + sage: QQ(CBF(12345/2^5)) + 12345/32 + sage: QQ(CBF(i)) + Traceback (most recent call last): + ... + ValueError: 1.000000000000000*I does not contain a unique rational number + """ + if acb_is_real(self.value) and acb_is_exact(self.value): + return self.real().mid().exact_rational() + else: + raise ValueError("{} does not contain a unique rational number".format(self)) + + def _complex_mpfr_field_(self, parent): + r""" + Convert this complex ball to a complex number. + + INPUT: + + - ``parent`` - :class:`~sage.rings.complex_field.ComplexField_class`, + target parent. + + EXAMPLES:: + + sage: CC(CBF(1/3, 1/3)) + 0.333333333333333 + 0.333333333333333*I + sage: ComplexField(100)(CBF(1/3, 1/3)) + 0.33333333333333331482961625625 + 0.33333333333333331482961625625*I + """ + real_field = parent._base + return parent(real_field(self.real()), real_field(self.imag())) + + def _real_mpfi_(self, parent): + r""" + Try to convert this complex ball to a real interval. + + Fail if the imaginary part is not exactly zero. + + INPUT: + + - ``parent`` - :class:`~sage.rings.real_mpfi.RealIntervalField_class`, + target parent. + + EXAMPLES:: + + sage: RIF(CBF(RBF(1/3, rad=1e-5))) + 0.3334? + sage: RIF(CBF(RBF(1/3, rad=1e-5), 1e-10)) + Traceback (most recent call last): + ... + ValueError: nonzero imaginary part + """ + if acb_is_real(self.value): + return parent(self.real()) + else: + raise ValueError("nonzero imaginary part") + + def _mpfr_(self, parent): + r""" + Try to convert this complex ball to a real number. + + Fail if the imaginary part is not exactly zero. + + INPUT: + + - ``parent`` - :class:`~sage.rings.real_mpfr.RealField_class`, + target parent. + + EXAMPLES:: + + sage: RR(CBF(1/3)) + 0.333333333333333 + sage: RR(CBF(1, 1/3) - CBF(0, 1/3)) + Traceback (most recent call last): + ... + ValueError: nonzero imaginary part + """ + if acb_is_real(self.value): + return parent(self.real()) + else: + raise ValueError("nonzero imaginary part") + + # Real and imaginary part, midpoint, radius + + cpdef RealBall real(self): + """ + Return the real part of this ball. + + OUTPUT: + + A :class:`~sage.rings.real_arb.RealBall`. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: a = CBF(1/3, 1/5) + sage: a.real() + [0.3333333333333333 +/- 7.04e-17] + """ + cdef RealBall r = RealBall(real_ball_field(self)) + arb_set(r.value, acb_realref(self.value)) + return r + + cpdef RealBall imag(self): + """ + Return the imaginary part of this ball. + + OUTPUT: + + A :class:`~sage.rings.real_arb.RealBall`. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: a = CBF(1/3, 1/5) + sage: a.imag() + [0.2000000000000000 +/- 4.45e-17] + """ + cdef RealBall r = RealBall(real_ball_field(self)) + arb_set(r.value, acb_imagref(self.value)) + return r + + def __abs__(self): + """ + Return the absolute value of this complex ball. + + EXAMPLES:: + + sage: CBF(1 + i).abs() # indirect doctest + [1.414213562373095 +/- 2.99e-16] + sage: abs(CBF(i)) + 1.000000000000000 + + sage: CBF(1 + i).abs().parent() + Real ball field with 53 bits precision + """ + cdef RealBall r = RealBall(real_ball_field(self)) + acb_abs(r.value, self.value, prec(self)) + return r + + def below_abs(self, test_zero=False): + """ + Return a lower bound for the absolute value of this complex ball. + + INPUT: + + - ``test_zero`` (boolean, default ``False``) -- if ``True``, + make sure that the returned lower bound is positive, raising + an error if the ball contains zero. + + OUTPUT: + + A ball with zero radius + + EXAMPLES:: + + sage: b = ComplexBallField(8)(1+i).below_abs() + sage: b + [1.4 +/- 0.0141] + sage: b.is_exact() + True + sage: QQ(b)*128 + 181 + sage: (CBF(1/3) - 1/3).below_abs() + 0 + sage: (CBF(1/3) - 1/3).below_abs(test_zero=True) + Traceback (most recent call last): + ... + ValueError: ball contains zero + + .. SEEALSO:: :meth:`above_abs` + """ + cdef RealBall res = RealBall(real_ball_field(self)) + acb_get_abs_lbound_arf(arb_midref(res.value), self.value, prec(self)) + if test_zero and arb_contains_zero(res.value): + assert acb_contains_zero(self.value) + raise ValueError("ball contains zero") + return res + + def above_abs(self): + """ + Return an upper bound for the absolute value of this complex ball. + + OUTPUT: + + A ball with zero radius + + EXAMPLES:: + + sage: b = ComplexBallField(8)(1+i).above_abs() + sage: b + [1.4 +/- 0.0219] + sage: b.is_exact() + True + sage: QQ(b)*128 + 182 + + .. SEEALSO:: :meth:`below_abs` + """ + cdef RealBall res = RealBall(real_ball_field(self)) + acb_get_abs_ubound_arf(arb_midref(res.value), self.value, prec(self)) + return res + + def arg(self): + """ + Return the argument of this complex ball. + + EXAMPLES:: + + sage: CBF(1 + i).arg() + [0.785398163397448 +/- 3.91e-16] + sage: CBF(-1).arg() + [3.141592653589793 +/- 5.61e-16] + sage: CBF(-1).arg().parent() + Real ball field with 53 bits precision + """ + cdef RealBall r = RealBall(real_ball_field(self)) + acb_arg(r.value, self.value, prec(self)) + return r + + def mid(self): + """ + Return the midpoint of this ball. + + OUTPUT: + + :class:`~sage.rings.complex_number.ComplexNumber`, floating-point + complex number formed by the centers of the real and imaginary parts of + this ball. + + EXAMPLES:: + + sage: CBF(1/3, 1).mid() + 0.333333333333333 + 1.00000000000000*I + sage: CBF(1/3, 1).mid().parent() + Complex Field with 53 bits of precision + sage: CBF('inf', 'nan').mid() + +infinity - NaN*I + sage: CBF('nan', 'inf').mid() + NaN + +infinity*I + sage: CBF('nan').mid() + NaN + sage: CBF('inf').mid() + +infinity + sage: CBF(0, 'inf').mid() + +infinity*I + + .. SEEALSO:: :meth:`squash` + """ + re, im = self.real().mid(), self.imag().mid() + field = sage.rings.complex_field.ComplexField( + max(prec(self), re.prec(), im.prec())) + return field(re, im) + + def squash(self): + """ + Return an exact ball with the same midpoint as this ball. + + OUTPUT: + + A :class:`ComplexBall`. + + EXAMPLES:: + + sage: mid = CBF(1/3, 1/10).squash() + sage: mid + [0.3333333333333333 +/- 1.49e-17] + [0.09999999999999999 +/- 1.68e-18]*I + sage: mid.parent() + Complex ball field with 53 bits precision + sage: mid.is_exact() + True + + .. SEEALSO:: :meth:`mid` + """ + cdef ComplexBall res = self._new() + arf_set(arb_midref(acb_realref(res.value)), arb_midref(acb_realref(self.value))) + arf_set(arb_midref(acb_imagref(res.value)), arb_midref(acb_imagref(self.value))) + mag_zero(arb_radref(acb_realref(res.value))) + mag_zero(arb_radref(acb_imagref(res.value))) + return res + + def rad(self): + """ + Return an upper bound for the error radius of this ball. + + OUTPUT: + + A :class:`~sage.rings.real_mpfr.RealNumber` of the same precision as + the radii of real balls. + + .. WARNING:: + + Unlike a :class:`~sage.rings.real_arb.RealBall`, + a :class:`ComplexBall` is *not* defined + by its midpoint and radius. (Instances of :class:`ComplexBall` are + actually rectangles, not balls.) + + EXAMPLES:: + + sage: CBF(1 + i).rad() + 0.00000000 + sage: CBF(i/3).rad() + 1.1102230e-16 + sage: CBF(i/3).rad().parent() + Real Field with 30 bits of precision + + TESTS:: + + sage: (CBF(0, 1/3) << (2^64)).rad() + Traceback (most recent call last): + ... + RuntimeError: unable to convert the radius to MPFR (exponent out of range?) + """ + # Should we return a real number with rounding towards +∞ (or away from + # zero if/when implemented)? + cdef RealField_class rad_field = RealField(MAG_BITS) + cdef RealNumber rad = RealNumber(rad_field, None) + cdef arf_t tmp + arf_init(tmp) + acb_get_rad_ubound_arf(tmp, self.value, MAG_BITS) + sig_str("unable to convert the radius to MPFR (exponent out of range?)") + if arf_get_mpfr(rad.value, tmp, MPFR_RNDU): + sig_error() + sig_off() + arf_clear(tmp) + return rad + + # Should we implement rad_as_ball? If we do, should it return an enclosure + # of the radius (which radius?), or an upper bound? + + # Precision and accuracy + + def round(self): + """ + Return a copy of this ball rounded to the precision of the parent. + + EXAMPLES: + + It is possible to create balls whose midpoint is more precise that + their parent's nominal precision (see :mod:`~sage.rings.real_arb` for + more information):: + + sage: b = CBF(exp(I*pi/3).n(100)) + sage: b.mid() + 0.50000000000000000000000000000 + 0.86602540378443864676372317075*I + + The ``round()`` method rounds such a ball to its parent's precision:: + + sage: b.round().mid() + 0.500000000000000 + 0.866025403784439*I + + .. SEEALSO:: :meth:`trim` + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_set_round(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def accuracy(self): + """ + Return the effective relative accuracy of this ball measured in bits. + + This is computed as if calling + :meth:`~sage.rings.real_arb.RealBall.accuracy()` + on the real ball whose midpoint is the larger out of the real and + imaginary midpoints of this complex ball, and whose radius is the + larger out of the real and imaginary radii of this complex ball. + + EXAMPLES:: + + sage: CBF(exp(I*pi/3)).accuracy() + 51 + sage: CBF(I/2).accuracy() == CBF.base().maximal_accuracy() + True + sage: CBF('nan', 'inf').accuracy() == -CBF.base().maximal_accuracy() + True + + .. SEEALSO:: + + :meth:`~sage.rings.real_arb.RealBallField.maximal_accuracy` + """ + return acb_rel_accuracy_bits(self.value) + + def trim(self): + """ + Return a trimmed copy of this ball. + + Return a copy of this ball with both the real and imaginary parts + trimmed (see :meth:`~sage.rings.real_arb.RealBall.trim()`). + + EXAMPLES:: + + sage: b = CBF(1/3, RBF(1/3, rad=.01)) + sage: b.mid() + 0.333333333333333 + 0.333333333333333*I + sage: b.trim().mid() + 0.333333333333333 + 0.333333015441895*I + + .. SEEALSO:: :meth:`round` + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_trim(res.value, self.value) + if _do_sig(prec(self)): sig_off() + return res + + def add_error(self, ampl): + """ + Increase the radii of the real and imaginary parts by (an upper bound + on) ``ampl``. + + If ``ampl`` is negative, the radii remain unchanged. + + INPUT: + + - ``ampl`` - A **real** ball (or an object that can be coerced to a + real ball). + + OUTPUT: + + A new complex ball. + + EXAMPLES:: + + sage: CBF(1+i).add_error(10^-16) + [1.000000000000000 +/- 1.01e-16] + [1.000000000000000 +/- 1.01e-16]*I + """ + return ComplexBall(self._parent, self.real().add_error(ampl), self.imag().add_error(ampl)) + + # Comparisons and predicates + + def is_zero(self): + """ + Return ``True`` iff the midpoint and radius of this ball are both zero. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: CBF(0).is_zero() + True + sage: CBF(RIF(-0.5, 0.5)).is_zero() + False + + .. SEEALSO:: :meth:`is_nonzero` + """ + return acb_is_zero(self.value) + + def is_nonzero(self): + """ + Return ``True`` iff zero is not contained in the interval represented + by this ball. + + .. NOTE:: + + This method is not the negation of :meth:`is_zero`: it only + returns ``True`` if zero is known not to be contained in the ball. + + Use ``bool(b)`` (or, equivalently, ``not b.is_zero()``) to check if + a ball ``b`` **may** represent a nonzero number (for instance, to + determine the “degree” of a polynomial with ball coefficients). + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: CBF(pi, 1/3).is_nonzero() + True + sage: CBF(RIF(-0.5, 0.5), 1/3).is_nonzero() + True + sage: CBF(1/3, RIF(-0.5, 0.5)).is_nonzero() + True + sage: CBF(RIF(-0.5, 0.5), RIF(-0.5, 0.5)).is_nonzero() + False + + .. SEEALSO:: :meth:`is_zero` + """ + return (arb_is_nonzero(acb_realref(self.value)) + or arb_is_nonzero(acb_imagref(self.value))) + + def __nonzero__(self): + """ + Return ``True`` iff this complex ball is not the zero ball, i.e. if the + midpoint and radius of its real and imaginary parts are not all zero. + + This is the preferred way, for instance, to determine the “degree” of a + polynomial with ball coefficients. + + .. WARNING:: + + A “nonzero” ball in the sense of this method may represent the + value zero. Use :meth:`is_nonzero` to check that a complex number + represented by a ``ComplexBall`` object is known to be nonzero. + + EXAMPLES:: + + sage: bool(CBF(0)) # indirect doctest + False + sage: bool(CBF(i)) + True + sage: bool(CBF(RIF(-0.5, 0.5))) + True + """ + return not acb_is_zero(self.value) + + def is_exact(self): + """ + Return ``True`` iff the radius of this ball is zero. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: CBF(1).is_exact() + True + sage: CBF(1/3, 1/3).is_exact() + False + """ + return acb_is_exact(self.value) + + def is_real(self): + """ + Return ``True`` iff the imaginary part of this ball is exactly zero. + + EXAMPLES:: + + sage: CBF(1/3, 0).is_real() + True + sage: (CBF(i/3) - CBF(1, 1/3)).is_real() + False + sage: CBF('inf').is_real() + True + """ + return acb_is_real(self.value) + + cpdef _richcmp_(left, Element right, int op): + """ + Compare ``left`` and ``right``. + + For more information, see :mod:`sage.rings.complex_arb`. + + EXAMPLES:: + + sage: CBF = ComplexBallField() + sage: a = CBF(1) + sage: b = CBF(1) + sage: a is b + False + sage: a == b + True + sage: a = CBF(1/3) + sage: a.is_exact() + False + sage: b = CBF(1/3) + sage: b.is_exact() + False + sage: a == b + False + sage: a = CBF(1, 2) + sage: b = CBF(1, 2) + sage: a is b + False + sage: a == b + True + + TESTS: + + Balls whose intersection consists of one point:: + + sage: a = CBF(RIF(1, 2), RIF(1, 2)) + sage: b = CBF(RIF(2, 4), RIF(2, 4)) + sage: a < b + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a > b + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a <= b + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a >= b + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a == b + False + sage: a != b + False + + Balls with non-trivial intersection:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) + sage: a = CBF(RIF(2, 5), RIF(2, 5)) + sage: a == b + False + sage: a != b + False + + One ball contained in another:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) + sage: b = CBF(RIF(2, 3), RIF(2, 3)) + sage: a == b + False + sage: a != b + False + + Disjoint balls:: + + sage: a = CBF(1/3, 1/3) + sage: b = CBF(1/5, 1/5) + sage: a == b + False + sage: a != b + True + + Exact elements:: + + sage: a = CBF(2, 2) + sage: b = CBF(2, 2) + sage: a.is_exact() + True + sage: b.is_exact() + True + sage: a == b + True + sage: a != b + False + """ + cdef ComplexBall lt, rt + cdef acb_t difference + + lt = left + rt = right + + if op == Py_EQ: + return lt is rt or ( + acb_is_exact(lt.value) and acb_is_exact(rt.value) + and acb_equal(lt.value, rt.value)) + + if op == Py_NE: + return not acb_overlaps(lt.value, rt.value) + + elif op == Py_GT or op == Py_GE or op == Py_LT or op == Py_LE: + raise TypeError("No order is defined for ComplexBalls.") + + def identical(self, ComplexBall other): + """ + Return whether ``self`` and ``other`` represent the same ball. + + INPUT: + + - ``other`` -- a :class:`ComplexBall`. + + OUTPUT: + + Return True iff ``self`` and ``other`` are equal as sets, i.e. if their + real and imaginary parts each have the same midpoint and radius. + + Note that this is not the same thing as testing whether both ``self`` + and ``other`` certainly represent the complex real number, unless + either ``self`` or ``other`` is exact (and neither contains NaN). To + test whether both operands might represent the same mathematical + quantity, use :meth:`overlaps` or ``in``, depending on the + circumstance. + + EXAMPLES:: + + sage: CBF(1, 1/3).identical(1 + CBF(0, 1)/3) + True + sage: CBF(1, 1).identical(1 + CBF(0, 1/3)*3) + False + """ + return acb_equal(self.value, other.value) + + def overlaps(self, ComplexBall other): + """ + Return True iff ``self`` and ``other`` have some point in common. + + INPUT: + + - ``other`` -- a :class:`ComplexBall`. + + EXAMPLES:: + + sage: CBF(1, 1).overlaps(1 + CBF(0, 1/3)*3) + True + sage: CBF(1, 1).overlaps(CBF(1, 'nan')) + True + sage: CBF(1, 1).overlaps(CBF(0, 'nan')) + False + """ + return acb_overlaps(self.value, other.value) + + def contains_exact(self, other): + """ + Return ``True`` *iff* ``other`` is contained in ``self``. + + Use ``other in self`` for a test that works for a wider range of inputs + but may return false negatives. + + INPUT: + + - ``other`` -- :class:`ComplexBall`, + :class:`~sage.rings.integer.Integer`, + or :class:`~sage.rings.rational.Rational` + + EXAMPLES:: + + sage: CBF(RealBallField(100)(1/3), 0).contains_exact(1/3) + True + sage: CBF(1).contains_exact(1) + True + sage: CBF(1).contains_exact(CBF(1)) + True + + sage: CBF(sqrt(2)).contains_exact(sqrt(2)) + Traceback (most recent call last): + ... + TypeError: unsupported type: + """ + cdef fmpz_t tmpz + cdef fmpq_t tmpq + if _do_sig(prec(self)): sig_on() + try: + if isinstance(other, ComplexBall): + res = acb_contains(self.value, ( other).value) + elif isinstance(other, sage.rings.integer.Integer): + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( other).value) + res = acb_contains_fmpz(self.value, tmpz) + fmpz_clear(tmpz) + elif isinstance(other, sage.rings.rational.Rational): + fmpq_init(tmpq) + fmpq_set_mpq(tmpq, ( other).value) + res = acb_contains_fmpq(self.value, tmpq) + fmpq_clear(tmpq) + else: + raise TypeError("unsupported type: " + str(type(other))) + finally: + if _do_sig(prec(self)): sig_off() + return res + + def __contains__(self, other): + """ + Return True if ``other`` can be verified to be contained in ``self``. + + Depending on the type of ``other``, the test may use interval + arithmetic with a precision determined by the parent of ``self`` and + may return false negatives. + + EXAMPLES:: + + sage: 1/3*i in CBF(0, 1/3) + True + + A false negative:: + + sage: RLF(1/3) in CBF(RealBallField(100)(1/3), 0) + False + + .. SEEALSO:: :meth:`contains_exact` + """ + if not isinstance(other, ( + ComplexBall, + sage.rings.integer.Integer, + sage.rings.rational.Rational)): + other = self._parent(other) + return self.contains_exact(other) + + def contains_zero(self): + """ + Return ``True`` iff this ball contains zero. + + EXAMPLES:: + + sage: CBF(0).contains_zero() + True + sage: CBF(RIF(-1,1)).contains_zero() + True + sage: CBF(i).contains_zero() + False + """ + return acb_contains_zero(self.value) + + # Arithmetic + + def __neg__(self): + """ + Return the opposite of this ball. + + EXAMPLES:: + + sage: -CBF(1/3 + I) + [-0.3333333333333333 +/- 7.04e-17] - 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + acb_neg(res.value, self.value) + return res + + def conjugate(self): + """ + Return the complex conjugate of this ball. + + EXAMPLES:: + + sage: CBF(-2 + I/3).conjugate() + -2.000000000000000 + [-0.3333333333333333 +/- 7.04e-17]*I + """ + cdef ComplexBall res = self._new() + acb_conj(res.value, self.value) + return res + + cpdef ModuleElement _add_(self, ModuleElement other): + """ + Return the sum of two balls, rounded to the ambient field's precision. + + The resulting ball is guaranteed to contain the sums of any two points + of the respective input balls. + + EXAMPLES:: + + sage: CBF(1) + CBF(I) + 1.000000000000000 + 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_add(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + cpdef ModuleElement _sub_(self, ModuleElement other): + """ + Return the difference of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the differences of any two + points of the respective input balls. + + EXAMPLES:: + + sage: CBF(1) - CBF(I) + 1.000000000000000 - 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_sub(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def __invert__(self): + """ + Return the inverse of this ball. + + The result is guaranteed to contain the inverse of any point of the + input ball. + + EXAMPLES:: + + sage: ~CBF(i/3) + [-3.00000000000000 +/- 9.44e-16]*I + sage: ~CBF(0) + [+/- inf] + sage: ~CBF(RIF(10,11)) + [0.1 +/- 9.53e-3] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_inv(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + cpdef RingElement _mul_(self, RingElement other): + """ + Return the product of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the products of any two + points of the respective input balls. + + EXAMPLES:: + + sage: CBF(-2, 1)*CBF(1, 1/3) + [-2.333333333333333 +/- 5.37e-16] + [0.333333333333333 +/- 4.82e-16]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_mul(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def __lshift__(val, shift): + r""" + If ``val`` is a ``ComplexBall`` and ``shift`` is an integer, return the + ball obtained by shifting the center and radius of ``val`` to the left + by ``shift`` bits. + + INPUT: + + - ``shift`` -- integer, may be negative. + + EXAMPLES:: + + sage: CBF(i/3) << 2 + [1.333333333333333 +/- 4.82e-16]*I + sage: CBF(i) << -2 + 0.2500000000000000*I + + TESTS:: + + sage: CBF(i) << (2^65) + [3.636549880934858e+11106046577046714264 +/- 1.91e+11106046577046714248]*I + sage: 'a' << CBF(1/3) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for <<: 'str' and + 'ComplexBall' + sage: CBF(1) << 1/2 + Traceback (most recent call last): + ... + TypeError: shift should be an integer + """ + cdef fmpz_t tmpz + # the ComplexBall might be shift, not val + if not isinstance(val, ComplexBall): + raise TypeError("unsupported operand type(s) for <<: '{}' and '{}'" + .format(type(val).__name__, type(shift).__name__)) + cdef ComplexBall self = val + cdef ComplexBall res = self._new() + if isinstance(shift, int): + acb_mul_2exp_si(res.value, self.value, PyInt_AS_LONG(shift)) + elif isinstance(shift, sage.rings.integer.Integer): + sig_on() + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( shift).value) + acb_mul_2exp_fmpz(res.value, self.value, tmpz) + fmpz_clear(tmpz) + sig_off() + else: + raise TypeError("shift should be an integer") + return res + + def __rshift__(val, shift): + r""" + If ``val`` is a ``ComplexBall`` and ``shift`` is an integer, return the + ball obtained by shifting the center and radius of ``val`` to the right + by ``shift`` bits. + + INPUT: + + - ``shift`` -- integer, may be negative. + + EXAMPLES:: + + sage: CBF(1+I) >> 2 + 0.2500000000000000 + 0.2500000000000000*I + + TESTS:: + + sage: 'a' >> CBF(1/3) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for >>: 'str' and + 'ComplexBall' + """ + # the ComplexBall might be shift, not val + if isinstance(val, ComplexBall): + return val << (-shift) + else: + raise TypeError("unsupported operand type(s) for >>: '{}' and '{}'" + .format(type(val).__name__, type(shift).__name__)) + + cpdef RingElement _div_(self, RingElement other): + """ + Return the quotient of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the quotients of any two + points of the respective input balls. + + EXAMPLES:: + + sage: CBF(-2, 1)/CBF(1, 1/3) + [-1.50000000000000 +/- 1.27e-15] + [1.500000000000000 +/- 8.94e-16]*I + sage: CBF(2+I)/CBF(0) + [+/- inf] + [+/- inf]*I + sage: CBF(1)/CBF(0) + [+/- inf] + sage: CBF(1)/CBF(RBF(0, 1.)) + nan + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_div(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + +CBF = ComplexBallField() diff --git a/src/sage/rings/complex_ball_acb.pyx b/src/sage/rings/complex_ball_acb.pyx deleted file mode 100644 index 6823960c101..00000000000 --- a/src/sage/rings/complex_ball_acb.pyx +++ /dev/null @@ -1,712 +0,0 @@ -r""" -Arbitrary Precision Complex Intervals using Arb - -AUTHORS: - -- Clemens Heuberger (2014-10-25): Initial version. - -This is a rudimentary binding to the optional `Arb library -`_; it may be useful to refer to its -documentation for more details. - -You may have to run ``sage -i arb`` to use the arb library. - -.. WARNING:: - - Identical :class:`ComplexBall` objects are understood to give - permission for algebraic simplification. This assumption is made - to improve performance. For example, setting ``z = x*x`` sets `z` - to a ball enclosing the set `\{t^2 : t \in x\}` and not the - (generally larger) set `\{tu : t \in x, u \in x\}`. - -Comparison -========== - -Two elements are equal if and only if they are the same object -or if both are exact and equal:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - doctest:...: FutureWarning: This class/method/function is marked as experimental. - It, its functionality or its interface might change without a formal deprecation. - See http://trac.sagemath.org/17218 for details. - sage: a = CBF(1, 2) # optional - arb - sage: b = CBF(1, 2) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - sage: a = CBF(1/3, 1/5) # optional - arb - sage: b = CBF(1/3, 1/5) # optional - arb - sage: a.is_exact() # optional - arb - False - sage: b.is_exact() # optional - arb - False - sage: a is b # optional - arb - False - sage: a == b # optional - arb - False - -A ball is non-zero if and only if it does not contain zero. :: - - sage: a = CBF(RIF(-0.5, 0.5)) # optional - arb - sage: bool(a) # optional - arb - False - sage: a != 0 # optional - arb - False - sage: b = CBF(1/3, 1/5) # optional - arb - sage: bool(b) # optional - arb - True - sage: b != 0 # optional - arb - True - -Classes and Methods -=================== -""" -#***************************************************************************** -# Copyright (C) 2014 Clemens Heuberger -# -# 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/ -#***************************************************************************** -include 'sage/ext/interrupt.pxi' -include "sage/ext/python.pxi" -include "sage/ext/stdsage.pxi" - -import sage.categories.fields -from sage.libs.arb.arb cimport * -from sage.libs.arb.acb cimport * -from sage.misc.superseded import experimental -from sage.rings.complex_interval_field import ComplexIntervalField -from sage.rings.real_arb cimport mpfi_to_arb, arb_to_mpfi -from sage.rings.real_arb import RealBallField -from sage.structure.parent cimport Parent -from sage.structure.unique_representation import UniqueRepresentation - -cdef inline bint acb_is_nonzero(const acb_t z): - return arb_is_nonzero(&z.real) or arb_is_nonzero(&z.imag) - -cdef void ComplexIntervalFieldElement_to_acb( - acb_t target, - ComplexIntervalFieldElement source): - """ - Convert a :class:`ComplexIntervalFieldElement` to an ``acb``. - - INPUT: - - - ``target`` -- an ``acb_t`` - - - ``source`` -- a :class:`ComplexIntervalFieldElement` - - OUTPUT: - - None. - """ - cdef long precision - precision = source.parent().precision() - mpfi_to_arb(&target.real, source.__re, precision) - mpfi_to_arb(&target.imag, source.__im, precision) - -cdef int acb_to_ComplexIntervalFieldElement( - ComplexIntervalFieldElement target, - const acb_t source) except -1: - """ - Convert an ``acb`` to a :class:`ComplexIntervalFieldElement`. - - INPUT: - - - ``target`` -- a :class:`ComplexIntervalFieldElement` - - - ``source`` -- an ``acb_t`` - - OUTPUT: - - A :class:`ComplexIntervalFieldElement`. - """ - cdef long precision = target._prec - - arb_to_mpfi(target.__re, &source.real, precision) - arb_to_mpfi(target.__im, &source.imag, precision) - return 0 - -class ComplexBallField(UniqueRepresentation, Parent): - r""" - An approximation of the field of complex numbers using mid-rad - intervals, also known as balls. - - INPUT: - - - ``precision`` -- an integer `\ge 2`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb; indirect doctest - sage: CBF(1) # optional - arb - 1.000000000000000 - - TESTS:: - - sage: ComplexBallField(0) # optional - arb - Traceback (most recent call last): - ... - ValueError: Precision must be at least 2. - sage: ComplexBallField(1) # optional - arb - Traceback (most recent call last): - ... - ValueError: Precision must be at least 2. - """ - Element = ComplexBall - - @staticmethod - def __classcall__(cls, long precision=53): - r""" - Normalize the arguments for caching. - - TESTS:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField(53) is ComplexBallField() # optional - arb - True - """ - return super(ComplexBallField, cls).__classcall__(cls, precision) - - @experimental(17218) - def __init__(self, precision): - r""" - Initialize the complex ball field. - - INPUT: - - - ``precision`` -- an integer `\ge 2`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1) # optional - arb - 1.000000000000000 - """ - if precision < 2: - raise ValueError("Precision must be at least 2.") - super(ComplexBallField, self).__init__(category=[sage.categories.fields.Fields()]) - self._prec = precision - self.RealBallField = RealBallField(precision) - - def _repr_(self): - r""" - String representation of ``self``. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField() # optional - arb - Complex ball field with 53 bits precision - sage: ComplexBallField(106) # optional - arb - Complex ball field with 106 bits precision - """ - return "Complex ball field with {} bits precision".format(self._prec) - - def _coerce_map_from_(self, S): - r""" - Currently, there is no coercion. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField()._coerce_map_from_(CIF) # optional - arb - False - sage: ComplexBallField()._coerce_map_from_(SR) # optional - arb - False - """ - return False - - def _element_constructor_(self, *args, **kwds): - r""" - Construct a :class:`ComplexBall`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional: arb - sage: CBF = ComplexBallField() # optional: arb - sage: CBF(1) # optional: arb; indirect doctest - 1.000000000000000 - """ - return self.element_class(self, *args, **kwds) - - def _an_element_(self): - r""" - Construct an element. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional: arb - sage: CBF = ComplexBallField() # optional: arb - sage: CBF._an_element_() # optional: arb; indirect doctest - [0.3333333333333333 +/- 1.49e-17] + [0.1666666666666667 +/- 4.26e-17]*I - """ - return self(1.0/3, 1.0/6) - - def precision(self): - """ - Return the bit precision used for operations on elements of this field. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().precision() # optional - arb - 53 - """ - return self._prec - - def is_exact(self): - """ - Complex ball fields are not exact. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().is_exact() # optional - arb - False - """ - return False - - def is_finite(self): - """ - Complex ball fields are infinite. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().is_finite() # optional - arb - False - """ - return False - - def characteristic(self): - """ - Complex ball fields have characteristic zero. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().characteristic() # optional - arb - 0 - """ - return 0 - - -cdef inline bint _do_sig(long prec): - """ - Whether signal handlers should be installed for calls to arb. - - TESTS:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - """ - return (prec > 1000) - -cdef inline long prec(ComplexBall ball): - return ball._parent._prec - -cdef inline Parent real_ball_field(ComplexBall ball): - return ball._parent.RealBallField - -cdef class ComplexBall(Element): - """ - Hold one ``acb_t`` of the `Arb library - `_ - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: a = ComplexBallField()(1, 1) # optional - arb - sage: a # optional - arb - 1.000000000000000 + 1.000000000000000*I - sage: a._interval() # optional - arb - 1 + 1*I - """ - def __cinit__(self): - """ - Allocate memory for the encapsulated value. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField(2)(0) # optional - arb; indirect doctest - 0 - """ - acb_init(self.value) - - def __dealloc__(self): - """ - Deallocate memory of the encapsulated value. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: a = ComplexBallField(2)(0) # optional - arb; indirect doctest - sage: del a # optional - arb - """ - acb_clear(self.value) - - def __init__(self, parent, x=None, y=None): - """ - Initialize the :class:`ComplexBall` using `x` and `y`. - - INPUT: - - - ``parent`` -- a :class:`ComplexBallField`. - - - ``x`` -- (default: ``None``) ``None`` or a - :class:`~sage.rings.complex_interval.ComplexIntervalFieldElement` or - a :class:`sage.rings.real_arb.RealBall`. - - - ``y`` -- (default: ``None``) ``None`` or a - :class:`sage.rings.real_arb.RealBall`. - - OUTPUT: - - None. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(CIF(0, 1)) # optional - arb - 1.000000000000000*I - sage: CBF(1) # optional - arb - 1.000000000000000 - sage: CBF(1, 1) # optional - arb - 1.000000000000000 + 1.000000000000000*I - sage: CBF(x) # optional - arb - Traceback (most recent call last): - ... - TypeError: unable to convert to a ComplexIntervalFieldElement - sage: RBF = RealBallField() # optional - arb - sage: CBF(RBF(1/3)) # optional - arb - [0.3333333333333333 +/- 7.04e-17] - sage: CBF(RBF(1/3), RBF(1/6)) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I - sage: CBF(1/3) # optional - arb - [0.333333333333333 +/- 3.99e-16] - sage: CBF(1/3, 1/6) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I - sage: ComplexBallField(106)(1/3, 1/6) # optional - arb - [0.33333333333333333333333333333333 +/- 6.94e-33] + [0.16666666666666666666666666666666 +/- 7.70e-33]*I - """ - Element.__init__(self, parent) - - if x is None: - return - - if y is None: - # we assume x to be a complex number - if isinstance(x, RealBall): - arb_set(&self.value.real, ( x).value) - arb_set_ui(&self.value.imag, 0) - else: - if not isinstance(x, ComplexIntervalFieldElement): - try: - x = ComplexIntervalField(prec(self))(x) - except TypeError: - raise TypeError("unable to convert to a " - "ComplexIntervalFieldElement") - ComplexIntervalFieldElement_to_acb(self.value, - x) - else: - if not isinstance(x, RealBall): - try: - x = real_ball_field(self)(x) - except TypeError: - raise TypeError("unable to convert to a " - "RealBall") - if not isinstance(y, RealBall): - try: - y = real_ball_field(self)(y) - except TypeError: - raise TypeError("unable to convert to a " - "RealBall") - arb_set(&self.value.real, ( x).value) - arb_set(&self.value.imag, ( y).value) - - cdef ComplexBall _new(self): - """ - Return a new complex ball element with the same parent as ``self``. - """ - cdef ComplexBall x - x = ComplexBall.__new__(ComplexBall) - x._parent = self._parent - return x - - cpdef RealBall real(self): - """ - Return the real part of this ball. - - OUTPUT: - - A :class:`RealBall`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1/3, 1/5) # optional - arb - sage: a.real() # optional - arb - [0.3333333333333333 +/- 7.04e-17] - """ - cdef RealBall r - r = real_ball_field(self)(0) - arb_set(r.value, &self.value.real) - return r - - cpdef RealBall imag(self): - """ - Return the imaginary part of this ball. - - OUTPUT: - - A :class:`RealBall`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1/3, 1/5) # optional - arb - sage: a.imag() # optional - arb - [0.2000000000000000 +/- 4.45e-17] - """ - cdef RealBall r - r = real_ball_field(self)(0) - arb_set(r.value, &self.value.imag) - return r - - def _repr_(self): - """ - Return a string representation of ``self``. - - OUTPUT: - - A string. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1/3) # optional - arb - [0.333333333333333 +/- 3.99e-16] - sage: CBF(0, 1/3) # optional - arb - [0.3333333333333333 +/- 7.04e-17]*I - sage: ComplexBallField()(1/3, 1/6) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I - """ - if arb_is_zero(&self.value.imag): - return self.real()._repr_() - elif arb_is_zero(&self.value.real): - return "{}*I".format(self.imag()._repr_()) - else: - return "{} + {}*I".format(self.real()._repr_(), - self.imag()._repr_()) - - cpdef ComplexIntervalFieldElement _interval(self): - """ - Return :class:`ComplexIntervalFieldElement` of the same value. - - OUTPUT: - - A :class:`ComplexIntervalFieldElement`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(CIF(2, 2)) # optional - arb - sage: a._interval() # optional - arb - 2 + 2*I - """ - cdef ComplexIntervalFieldElement target = ComplexIntervalField(prec(self))(0) - acb_to_ComplexIntervalFieldElement(target, self.value) - return target - - # Comparisons and predicates - - def is_zero(self): - """ - Return ``True`` iff the midpoint and radius of this ball are both zero. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(0).is_zero() # optional - arb - True - sage: CBF(RIF(-0.5, 0.5)).is_zero() # optional - arb - False - """ - return acb_is_zero(self.value) - - def __nonzero__(self): - """ - Return ``True`` iff zero is not contained in the interval represented - by this ball. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: bool(CBF(pi, 1/3)) # optional - arb - True - sage: bool(CBF(RIF(-0.5, 0.5), 1/3)) # optional - arb - True - sage: bool(CBF(1/3, RIF(-0.5, 0.5))) #optional - arb - True - sage: bool(CBF(RIF(-0.5, 0.5), RIF(-0.5, 0.5))) #optional - arb - False - """ - return acb_is_nonzero(self.value) - - def is_exact(self): - """ - Return ``True`` iff the radius of this ball is zero. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1).is_exact() # optional - arb - True - sage: CBF(1/3, 1/3).is_exact() # optional - arb - False - """ - return acb_is_exact(self.value) - - def __richcmp__(left, right, int op): - """ - Compare ``left`` and ``right``. - - For more information, see :mod:`sage.rings.complex_ball_acb`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1) # optional - arb - sage: b = CBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - sage: a = CBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb - False - sage: b = CBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb - False - sage: a == b # optional - arb - False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Compare ``left`` and ``right``. - - For more information, see :mod:`sage.rings.complex_ball_acb`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1, 2) # optional - arb - sage: b = CBF(1, 2) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - - TESTS: - - Balls whose intersection consists of one point:: - - sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb - sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a > b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a <= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a >= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = CBF(1/3, 1/3) # optional - arb - sage: b = CBF(1/5, 1/5) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = CBF(2, 2) # optional - arb - sage: b = CBF(2, 2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False - """ - cdef ComplexBall lt, rt - cdef acb_t difference - - lt = left - rt = right - - if op == Py_EQ: - return lt is rt or ( - acb_is_exact(lt.value) and acb_is_exact(rt.value) - and acb_equal(lt.value, rt.value)) - - if op == Py_NE: - return not acb_overlaps(lt.value, rt.value) - - elif op == Py_GT or op == Py_GE or op == Py_LT or op == Py_LE: - raise TypeError("No order is defined for ComplexBalls.") diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 2dbd9b83612..31c12bd710a 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -151,7 +151,7 @@ cdef class ComplexDoubleField_class(sage.rings.ring.Field): (-1.0, -1.0 + 1.2246...e-16*I, False) """ from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, ('I',), normalize=False, category = Fields()) + ParentWithGens.__init__(self, self, ('I',), normalize=False, category=Fields().Metric().Complete()) self._populate_coercion_lists_() def __reduce__(self): @@ -795,9 +795,13 @@ cdef class ComplexDoubleElement(FieldElement): """ return hash(complex(self)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparison between ``left`` and ``right``. + We order the complex numbers in dictionary order by real parts then + imaginary parts. + + This order, of course, does not respect the field structure, though + it agrees with the usual order on the real numbers. EXAMPLES:: @@ -807,18 +811,8 @@ cdef class ComplexDoubleElement(FieldElement): -1 sage: cmp(CDF(1 + i), CDF(-1 - i)) 1 - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - We order the complex numbers in dictionary order by real parts then - imaginary parts. - This order, of course, does not respect the field structure, though - it agrees with the usual order on the real numbers. - - EXAMPLES:: + :: sage: CDF(2,3) < CDF(3,1) True diff --git a/src/sage/rings/complex_field.py b/src/sage/rings/complex_field.py index 5ac959ccce6..991635b8e6a 100644 --- a/src/sage/rings/complex_field.py +++ b/src/sage/rings/complex_field.py @@ -198,12 +198,12 @@ def __init__(self, prec=53): sage: C = ComplexField(200) sage: C.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: TestSuite(C).run() """ self._prec = int(prec) from sage.categories.fields import Fields - ParentWithGens.__init__(self, self._real_field(), ('I',), False, category = Fields()) + ParentWithGens.__init__(self, self._real_field(), ('I',), False, category=Fields().Metric().Complete()) # self._populate_coercion_lists_() self._populate_coercion_lists_(coerce_list=[complex_number.RRtoCC(self._real_field(), self)]) @@ -744,3 +744,4 @@ def _factor_univariate_polynomial(self, f): from sage.structure.factorization import Factorization return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) + diff --git a/src/sage/rings/complex_interval.pyx b/src/sage/rings/complex_interval.pyx index 9610ee36528..936ed82c762 100644 --- a/src/sage/rings/complex_interval.pyx +++ b/src/sage/rings/complex_interval.pyx @@ -29,30 +29,31 @@ heavily modified: :meth:`ComplexNumber.multiplicative_order()` methods. """ -################################################################################# +#***************************************************************************** # Copyright (C) 2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -import math -import operator include "sage/ext/interrupt.pxi" +from sage.libs.gmp.mpz cimport mpz_sgn, mpz_cmpabs_ui +from sage.libs.flint.fmpz cimport * from sage.structure.element cimport FieldElement, RingElement, Element, ModuleElement from complex_number cimport ComplexNumber import complex_interval_field from complex_field import ComplexField -import sage.misc.misc -import integer +from sage.rings.integer cimport Integer import infinity -import real_mpfi -import real_mpfr +cimport real_mpfi cimport real_mpfr +from sage.libs.pari.gen cimport gen as pari_gen cdef double LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 # a small overestimate of log(10,2) @@ -118,7 +119,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): real, imag = (real).real(), (real).imag() elif isinstance(real, ComplexIntervalFieldElement): real, imag = (real).real(), (real).imag() - elif isinstance(real, sage.libs.pari.all.pari_gen): + elif isinstance(real, pari_gen): real, imag = real.real(), real.imag() elif isinstance(real, list) or isinstance(real, tuple): re, imag = real @@ -473,6 +474,54 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): mpfi_union(x.__im, self.__im, other_intv.__im) return x + def magnitude(self): + """ + The largest absolute value of the elements of the interval, rounded + away from zero. + + OUTPUT: a real number with rounding mode ``RNDU`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).magnitude() + 1.41421356237310 + sage: CIF(RIF(1,2), RIF(3,4)).magnitude() + 4.47213595499958 + sage: parent(CIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__upper_field._new() + cdef real_mpfr.RealNumber y = RIF.__upper_field._new() + mpfi_mag(x.value, self.__re) + mpfi_mag(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDA) + return x + + def mignitude(self): + """ + The smallest absolute value of the elements of the interval, rounded + towards zero. + + OUTPUT: a real number with rounding mode ``RNDD`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).mignitude() + 0.000000000000000 + sage: CIF(RIF(1,2), RIF(3,4)).mignitude() + 3.16227766016837 + sage: parent(CIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__lower_field._new() + cdef real_mpfr.RealNumber y = RIF.__lower_field._new() + mpfi_mig(x.value, self.__re) + mpfi_mig(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDZ) + return x + def center(self): """ Returns the closest floating-point approximation to the center @@ -730,10 +779,109 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): '[0.99109735947126309 .. 1.1179269966896264] + [1.4042388462787560 .. 1.4984624123369835]*I' sage: CIF(-7, -1) ^ CIF(0.3) 1.117926996689626? - 1.408500714575360?*I - """ - if isinstance(right, (int, long, integer.Integer)): - return sage.rings.ring_element.RingElement.__pow__(self, right) - return (self.log() * self.parent()(right)).exp() + + Note that ``x^2`` is not the same as ``x*x``:: + + sage: a = CIF(RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [0.00000000000000000 .. 1.0000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(0, RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. -0.00000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(RIF(-1,1), RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + sage: print (a*a).str(style="brackets") + [-2.0000000000000000 .. 2.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + + We can take very high powers:: + + sage: RIF = RealIntervalField(27) + sage: CIF = ComplexIntervalField(27) + sage: s = RealField(27, rnd="RNDZ")(1/2)^(1/3) + sage: a = CIF(RIF(-s/2,s/2), RIF(-s, s)) + sage: r = a^(10^10000) + sage: print r.str(style="brackets") + [-2.107553304e1028 .. 2.107553304e1028] + [-2.107553304e1028 .. 2.107553304e1028]*I + + TESTS:: + + sage: CIF = ComplexIntervalField(7) + sage: [CIF(2) ^ RDF(i) for i in range(-5,6)] + [0.03125?, 0.06250?, 0.1250?, 0.2500?, 0.5000?, 1, 2, 4, 8, 16, 32] + sage: pow(CIF(1), CIF(1), CIF(1)) + Traceback (most recent call last): + ... + TypeError: pow() 3rd argument not allowed unless all arguments are integers + """ + if modulus is not None: + raise TypeError("pow() 3rd argument not allowed unless all arguments are integers") + + cdef ComplexIntervalFieldElement z, z2, t = None + z = self + + # Convert right to an integer + if not isinstance(right, Integer): + try: + right = Integer(right) + except TypeError: + # Exponent is really not an integer + return (z.log() * z._parent(right)).exp() + + cdef int s = mpz_sgn((right).value) + if s == 0: + return z._parent.one() + elif s < 0: + z = ~z + if not mpz_cmpabs_ui((right).value, 1): + return z + + # Convert exponent to fmpz_t + cdef fmpz_t e + fmpz_init(e) + fmpz_set_mpz(e, (right).value) + fmpz_abs(e, e) + + # Now we know that e >= 2. + # Use binary powering with special formula for squares. + + # Handle first bit more efficiently: + if fmpz_tstbit(e, 0): + res = z + else: + res = z._parent.one() + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + + # Allocate a temporary ComplexIntervalFieldElement + z2 = z._new() + + while True: + # Compute z2 = z^2 using the formula + # (a + bi)^2 = (a^2 - b^2) + 2abi + mpfi_sqr(z2.__re, z.__re) # a^2 + mpfi_sqr(z2.__im, z.__im) # b^2 + mpfi_sub(z2.__re, z2.__re, z2.__im) # a^2 - b^2 + mpfi_mul(z2.__im, z.__re, z.__im) # ab + mpfi_mul_2ui(z2.__im, z2.__im, 1) # 2ab + z = z2 + if fmpz_tstbit(e, 0): + res *= z + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + if fmpz_is_zero(e): + break + + # Swap temporary elements z2 and t (allocate t first if needed) + if t is not None: + z2 = t + else: + z2 = z2._new() + t = z + fmpz_clear(e) + return res def _magma_init_(self, magma): r""" @@ -924,6 +1072,23 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): return x + def _complex_mpfr_field_(self, field): + """ + Convert to a complex field. + + EXAMPLES:: + + sage: re = RIF("1.2") + sage: im = RIF(2, 3) + sage: a = ComplexIntervalField(30)(re, im) + sage: CC(a) + 1.20000000018626 + 2.50000000000000*I + """ + cdef ComplexNumber x = field(0) + mpfi_mid(x.__re, self.__re) + mpfi_mid(x.__im, self.__im) + return x + def __int__(self): """ Convert ``self`` to an ``int``. @@ -997,7 +1162,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): """ return self.real().__nonzero__() or self.imag().__nonzero__() - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): r""" As with the real interval fields this never returns false positives. Thus, `a == b` is ``True`` iff both `a` and `b` represent the same @@ -1034,9 +1199,6 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): sage: CDF(1) >= CDF(1) >= CDF.gen() >= CDF.gen() >= 0 >= -CDF.gen() >= CDF(-1) True """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): cdef ComplexIntervalFieldElement lt, rt lt = left rt = right @@ -1069,7 +1231,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): elif op == 5: #>= return real_diff > 0 or (real_diff == 0 and imag_diff >= 0) - def __cmp__(left, right): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ Intervals are compared lexicographically on the 4-tuple: ``(x.real().lower(), x.real().upper(), @@ -1090,27 +1252,18 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): 0 sage: cmp(b, a) 1 - """ - return (left)._cmp(right) - - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: - """ - Intervals are compared lexicographically on the 4-tuple: - ``(x.real().lower(), x.real().upper(), - x.imag().lower(), x.imag().upper())`` TESTS:: sage: tests = [] sage: for rl in (0, 1): - ... for ru in (rl, rl + 1): - ... for il in (0, 1): - ... for iu in (il, il + 1): - ... tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) + ....: for ru in (rl, rl + 1): + ....: for il in (0, 1): + ....: for iu in (il, il + 1): + ....: tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) sage: for (i1, t1) in tests: - ... for (i2, t2) in tests: - ... assert(cmp(i1, i2) == cmp(t1, t2)) + ....: for (i2, t2) in tests: + ....: assert(cmp(i1, i2) == cmp(t1, t2)) """ cdef int a, b a = mpfi_nan_p(left.__re) diff --git a/src/sage/rings/complex_interval_field.py b/src/sage/rings/complex_interval_field.py index 115d35f1847..e147a410be5 100644 --- a/src/sage/rings/complex_interval_field.py +++ b/src/sage/rings/complex_interval_field.py @@ -195,6 +195,7 @@ def __init__(self, prec=53): Initialize ``self``. EXAMPLES:: + sage: ComplexIntervalField() Complex Interval Field with 53 bits of precision sage: ComplexIntervalField(200) @@ -376,10 +377,15 @@ def __call__(self, x, im=None): 2 + 3*I sage: CIF(pi, e) 3.141592653589794? + 2.718281828459046?*I + sage: ComplexIntervalField(100)(CIF(RIF(2,3))) + 3.? """ if im is None: - if isinstance(x, complex_interval.ComplexIntervalFieldElement) and x.parent() is self: - return x + if isinstance(x, complex_interval.ComplexIntervalFieldElement): + if x.parent() is self: + return x + else: + return complex_interval.ComplexIntervalFieldElement(self, x) elif isinstance(x, complex_double.ComplexDoubleElement): return complex_interval.ComplexIntervalFieldElement(self, x.real(), x.imag()) elif isinstance(x, str): diff --git a/src/sage/rings/complex_number.pyx b/src/sage/rings/complex_number.pyx index b1e6e524fc6..8e4de8876a7 100644 --- a/src/sage/rings/complex_number.pyx +++ b/src/sage/rings/complex_number.pyx @@ -818,7 +818,7 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): 1.4553 + 0.34356*I """ if isinstance(right, (int, long, integer.Integer)): - return sage.rings.ring_element.RingElement.__pow__(self, right) + return RingElement.__pow__(self, right) try: return (self.log()*right).exp() @@ -1123,11 +1123,10 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): """ return complex(mpfr_get_d(self.__re, rnd), mpfr_get_d(self.__im, rnd)) - # return complex(float(self.__re), float(self.__im)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparision between ``left`` and ``right``. + Compare ``left`` and ``right``. EXAMPLES:: @@ -1136,9 +1135,6 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): sage: cmp(CC(2, 1), CC(2, 1)) 0 """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int a, b a = mpfr_nan_p(left.__re) b = mpfr_nan_p((right).__re) diff --git a/src/sage/rings/contfrac.py b/src/sage/rings/contfrac.py index 19cefcc6cb1..67d1a27f8f4 100644 --- a/src/sage/rings/contfrac.py +++ b/src/sage/rings/contfrac.py @@ -1,5 +1,5 @@ r""" -Continued fraction field +Finite simple continued fractions Sage implements the field ``ContinuedFractionField`` (or ``CFF`` for short) of finite simple continued fractions. This is really diff --git a/src/sage/rings/continued_fraction.py b/src/sage/rings/continued_fraction.py index 0acc38d99ca..fd86a4f6ebb 100644 --- a/src/sage/rings/continued_fraction.py +++ b/src/sage/rings/continued_fraction.py @@ -533,12 +533,8 @@ def __cmp__(self, other): def _mpfr_(self, R): r""" - Return a numerical approximation of ``self`` in the real mpfr ring ``R``. - - The output result is accurate: when the rounding mode of - ``R`` is 'RNDN' then the result is the nearest binary number of ``R`` to - ``self``. The other rounding mode are 'RNDD' (toward +infinity), 'RNDU' - (toward -infinity) and 'RNDZ' (toward zero). + Return a correctly-rounded numerical approximation of ``self`` + in the real mpfr ring ``R``. EXAMPLES:: @@ -589,43 +585,38 @@ def _mpfr_(self, R): sage: cf.n(digits=11) 8.9371541378 - TESTS:: + TESTS: - We check that the rounding works as expected, at least in the rational - case:: + Check that the rounding works as expected (at least in the + rational case):: - sage: for _ in xrange(100): - ....: a = QQ.random_element(num_bound=1<<64) + sage: fields = [] + sage: for prec in [17, 24, 53, 128, 256]: + ....: for rnd in ['RNDN', 'RNDD', 'RNDU', 'RNDZ']: + ....: fields.append(RealField(prec=prec, rnd=rnd)) + sage: for n in range(3000): # long time + ....: a = QQ.random_element(num_bound=2^(n%100)) ....: cf = continued_fraction(a) - ....: for prec in 17,24,53,128,256: - ....: for rnd in 'RNDN','RNDD','RNDU','RNDZ': - ....: R = RealField(prec=prec, rnd=rnd) - ....: assert R(cf) == R(a) + ....: for R in fields: + ....: assert R(cf) == R(a) """ # 1. integer case if self.quotient(1) is Infinity: return R(self.quotient(0)) - # 2. negative numbers - # TODO: it is possible to deal with negative values. The only problem is - # that we need to find the good value for N (which involves - # self.quotient(k) for k=0,1,2) + rnd = R.rounding_mode() + + # 2. negative numbers: reduce to the positive case if self.quotient(0) < 0: - rnd = R.rounding_mode() - if rnd == 'RNDN' or rnd == 'RNDZ': - return -R(-self) - elif rnd == 'RNDD': - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m+1) >> (-e)) - return -(R(m+1) << e) - else: - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m-1) >> (-e)) - return -(R(m-1) << e) + sgn = -1 + self = -self + # Adjust rounding for change in sign + if rnd == 'RNDD': + rnd = 'RNDA' + elif rnd == 'RNDU': + rnd = 'RNDZ' + else: + sgn = 1 # 3. positive non integer if self.quotient(0) == 0: # 0 <= self < 1 @@ -655,8 +646,8 @@ def _mpfr_(self, R): assert m_odd.nbits() == R.prec() or m_even.nbits() == R.prec() - if m_even == m_odd: # no need to worry (we have a decimal number) - return R(m_even) >> N + if m_even == m_odd: # no need to worry (we have an exact number) + return R(sgn * m_even) >> N # check ordering # m_even/2^N <= p_even/q_even <= self <= p_odd/q_odd <= m_odd/2^N @@ -665,30 +656,24 @@ def _mpfr_(self, R): assert p_even / q_even <= p_odd / q_odd assert p_odd / q_odd <= m_odd / (ZZ_1 << N) - rnd = R.rounding_mode() if rnd == 'RNDN': # round to the nearest # in order to find the nearest approximation we possibly need to # augment our precision on convergents. while True: assert not(p_odd << (N+1) <= (2*m_odd-1) * q_odd) or not(p_even << (N+1) >= (2*m_even+1) * q_even) if p_odd << (N+1) <= (2*m_odd-1) * q_odd: - return R(m_even) >> N + return R(sgn * m_even) >> N if p_even << (N+1) >= (2*m_even+1) * q_even: - return R(m_odd) >> N + return R(sgn * m_odd) >> N k += 1 p_even = self.numerator(2*k) p_odd = self.numerator(2*k+1) q_even = self.denominator(2*k) q_odd = self.denominator(2*k+1) - elif rnd == 'RNDU': # round up (toward +infinity) - return R(m_odd) >> N - elif rnd == 'RNDD': # round down (toward -infinity) - return R(m_even) >> N - elif rnd == 'RNDZ': # round toward zero - if m_even.sign() == 1: - return R(m_even) >> N - else: - return R(m_odd) >> N + elif rnd == 'RNDU' or rnd == 'RNDA': # round up + return R(sgn * m_odd) >> N + elif rnd == 'RNDD' or rnd == 'RNDZ': # round down + return R(sgn * m_even) >> N else: raise ValueError("%s unknown rounding mode" % rnd) @@ -1471,7 +1456,7 @@ def _rational_(self): return self.numerator(n-1) / self.denominator(n-1) def _latex_(self): - """ + r""" EXAMPLES:: sage: a = continued_fraction(-17/389) @@ -1522,7 +1507,7 @@ def __invert__(self): 1 """ if not self: - raise ZeroDivisionError("Rational division by 0") + raise ZeroDivisionError("rational division by zero") if self._x1: if self._x1[0] < 0: return -(-self).__invert__() diff --git a/src/sage/rings/dedekind_domain_element.py b/src/sage/rings/dedekind_domain_element.py index 657dca10167..cfe34aba5fc 100644 --- a/src/sage/rings/dedekind_domain_element.py +++ b/src/sage/rings/dedekind_domain_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.dedekind_domain_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import DedekindDomainElement def is_DedekindDomainElement(x): @@ -25,6 +28,9 @@ def is_DedekindDomainElement(x): EXAMPLES:: + sage: import sage.rings.dedekind_domain_element + doctest:...: DeprecationWarning: the module sage.rings.dedekind_domain_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.dedekind_domain_element.is_DedekindDomainElement(DedekindDomainElement(QQ)) True """ diff --git a/src/sage/rings/euclidean_domain_element.py b/src/sage/rings/euclidean_domain_element.py index efb876d679c..c428b0ec514 100644 --- a/src/sage/rings/euclidean_domain_element.py +++ b/src/sage/rings/euclidean_domain_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.euclidean_domain_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import EuclideanDomainElement def is_EuclideanDomainElement(x): @@ -25,6 +28,9 @@ def is_EuclideanDomainElement(x): EXAMPLES:: + sage: import sage.rings.euclidean_domain_element + doctest:...: DeprecationWarning: the module sage.rings.euclidean_domain_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.euclidean_domain_element.is_EuclideanDomainElement(EuclideanDomainElement(ZZ)) True """ diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index 0fa7ddb0f08..4b65b5abb98 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -1,5 +1,5 @@ """ -Basic arithmetic with c-integers. +Basic arithmetic with C integers """ #***************************************************************************** diff --git a/src/sage/rings/field_element.py b/src/sage/rings/field_element.py index 1314d3fb283..1d0921a75ab 100644 --- a/src/sage/rings/field_element.py +++ b/src/sage/rings/field_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.field_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import FieldElement def is_FieldElement(x): @@ -25,6 +28,9 @@ def is_FieldElement(x): EXAMPLES:: + sage: import sage.rings.field_element + doctest:...: DeprecationWarning: the module sage.rings.field_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.field_element.is_FieldElement(QQ(2)) True """ diff --git a/src/sage/rings/finite_rings/conway_polynomials.py b/src/sage/rings/finite_rings/conway_polynomials.py index 32c84e232e5..e9059d29524 100644 --- a/src/sage/rings/finite_rings/conway_polynomials.py +++ b/src/sage/rings/finite_rings/conway_polynomials.py @@ -9,6 +9,7 @@ - Peter Bruin """ +from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject from sage.rings.finite_rings.constructor import FiniteField import sage.databases.conway @@ -90,7 +91,7 @@ def exists_conway_polynomial(p, n): """ return sage.databases.conway.ConwayPolynomials().has_polynomial(p,n) -class PseudoConwayLattice(SageObject): +class PseudoConwayLattice(WithEqualityById, SageObject): r""" A pseudo-Conway lattice over a given finite prime field. @@ -128,8 +129,25 @@ class PseudoConwayLattice(SageObject): sage: PCL = PseudoConwayLattice(2, use_database=False) sage: PCL.polynomial(3) x^3 + x + 1 - """ + TESTS:: + + sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice + sage: PCL = PseudoConwayLattice(3) + sage: hash(PCL) # random + 8738829832350 + + sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice + sage: PseudoConwayLattice(3) == PseudoConwayLattice(3) + False + sage: PseudoConwayLattice(3) != PseudoConwayLattice(3) + True + sage: P = PseudoConwayLattice(5) + sage: P == P + True + sage: P != P + False + """ def __init__(self, p, use_database=True): """ TESTS:: @@ -157,33 +175,6 @@ def __init__(self, p, use_database=True): else: self.nodes = {} - def __cmp__(self, other): - """ - TEST:: - - sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice - sage: PCL3 = PseudoConwayLattice(3) - sage: PCL5 = PseudoConwayLattice(5) - sage: PCL3 == PCL3 - True - sage: PCL3 == PCL5 - False - sage: PCL3 = PseudoConwayLattice(3, use_database=False) - sage: PCL5 = PseudoConwayLattice(5, use_database=False) - sage: PCL5 == PCL5 - True - sage: PCL3 == PCL5 - False - - """ - if self is other: - return 0 - c = cmp(type(self), type(other)) - if c != 0: - return c - return cmp((self.p, self.nodes), - (other.p, other.nodes)) - def polynomial(self, n): r""" Return the pseudo-Conway polynomial of degree `n` in this diff --git a/src/sage/rings/finite_rings/element_base.pyx b/src/sage/rings/finite_rings/element_base.pyx index cb6dbb2966e..37810256d0c 100644 --- a/src/sage/rings/finite_rings/element_base.pyx +++ b/src/sage/rings/finite_rings/element_base.pyx @@ -342,7 +342,7 @@ cdef class FinitePolyExtElement(FiniteRingElement): TESTS: - The following tests against a bug fixed in trac:`11530`:: + The following tests against a bug fixed in :trac:`11530`:: sage: F. = GF(3^4) sage: F.modulus() diff --git a/src/sage/rings/finite_rings/element_ext_pari.py b/src/sage/rings/finite_rings/element_ext_pari.py index 633af6b86a0..41d294ffe99 100644 --- a/src/sage/rings/finite_rings/element_ext_pari.py +++ b/src/sage/rings/finite_rings/element_ext_pari.py @@ -35,7 +35,6 @@ from sage.rings.integer import Integer from sage.libs.pari.all import pari, pari_gen from sage.rings.finite_rings.element_base import FinitePolyExtElement -import sage.rings.field_element as field_element import sage.rings.finite_rings.integer_mod as integer_mod from element_base import is_FiniteFieldElement from sage.modules.free_module_element import FreeModuleElement @@ -221,7 +220,7 @@ def __init__(self, parent, value, value_from_pari=False): sage: K(0)._pari_().type() 't_POLMOD' """ - field_element.FieldElement.__init__(self, parent) + element.FieldElement.__init__(self, parent) self.__class__ = dynamic_FiniteField_ext_pariElement # If value_from_pari is True, directly set self.__value to value. diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index c94ee5a675f..403b99c8fd0 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -1297,19 +1297,6 @@ cdef class FiniteField_givaroElement(FinitePolyExtElement): return make_FiniteField_givaroElement(cache, cache.objectptr.one) return make_FiniteField_givaroElement(cache, r) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: k. = GF(9); k - Finite Field in a of size 3^2 - sage: a == k('a') # indirect doctest - True - sage: a == a + 1 - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements is correct or equality diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index ba7b6991436..7c890f3ac2f 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -803,10 +803,16 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): from sage.groups.generic import power return power(self,exp) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements. + .. NOTE:: + + Finite fields are unordered. However, we adopt the convention that + an element ``e`` is bigger than element ``f`` if its polynomial + representation is bigger. + EXAMPLES:: sage: k. = GF(2^20) @@ -819,13 +825,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, we adopt the convention that - an element ``e`` is bigger than element ``f`` if its polynomial - representation is bigger. - - EXAMPLES:: + :: sage: K. = GF(2^100) sage: a < a^2 @@ -843,12 +843,6 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: a == a True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Comparison of finite field elements. - """ (left._parent._cache).F.restore() c = GF2E_equal((left).x, (right).x) if c == 1: diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pyx b/src/sage/rings/finite_rings/element_pari_ffelt.pyx index 2ab45e245c5..2401a21b95a 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pyx +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pyx @@ -388,31 +388,13 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): """ Comparison of finite field elements. - TESTS:: - - sage: k. = FiniteField(3^3, impl='pari_ffelt') - sage: a == 1 - False - sage: a^0 == 1 - True - sage: a == a - True - sage: a < a^2 - True - sage: a > a^2 - False - """ - cdef int r - pari_catch_sig_on() - r = cmp_universal(self.val, (other).val) - pari_catch_sig_off() - return r + .. NOTE:: - def __richcmp__(FiniteFieldElement_pari_ffelt left, object right, int op): - """ - Rich comparison of finite field elements. + Finite fields are unordered. However, for the purpose of + this function, we adopt the lexicographic ordering on the + representing polynomials. - EXAMPLE:: + EXAMPLES:: sage: k. = GF(2^20, impl='pari_ffelt') sage: e = k.random_element() @@ -424,13 +406,7 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, for the purpose of - this function, we adopt the lexicographic ordering on the - representing polynomials. - - EXAMPLE:: + :: sage: K. = GF(2^100, impl='pari_ffelt') sage: a < a^2 @@ -447,8 +423,26 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): False sage: a == a True + + TESTS:: + + sage: k. = FiniteField(3^3, impl='pari_ffelt') + sage: a == 1 + False + sage: a^0 == 1 + True + sage: a == a + True + sage: a < a^2 + True + sage: a > a^2 + False """ - return (left)._richcmp(right, op) + cdef int r + pari_catch_sig_on() + r = cmp_universal(self.val, (other).val) + pari_catch_sig_off() + return r cpdef ModuleElement _add_(FiniteFieldElement_pari_ffelt self, ModuleElement right): """ diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 0b9d5aab123..f771390b114 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1159,6 +1159,18 @@ cdef class FiniteField(Field): sage: k.extension(x^5 + x^2 + x - 1) Univariate Quotient Polynomial Ring in x over Finite Field in z4 of size 3^4 with modulus x^5 + x^2 + x + 2 + + TESTS: + + We check that trac #18915 is fixed:: + + sage: F = GF(2) + sage: F.extension(int(3), 'a') + Finite Field in a of size 2^3 + + sage: F = GF(2 ** 4, 'a') + sage: F.extension(int(3), 'aa') + Finite Field in aa of size 2^12 """ from constructor import GF from sage.rings.polynomial.polynomial_element import is_Polynomial @@ -1166,7 +1178,7 @@ cdef class FiniteField(Field): if name is None and names is not None: name = names if self.degree() == 1: - if isinstance(modulus, Integer): + if isinstance(modulus, (int, Integer)): E = GF(self.characteristic()**modulus, name=name, **kwds) elif isinstance(modulus, (list, tuple)): E = GF(self.characteristic()**(len(modulus) - 1), name=name, modulus=modulus, **kwds) @@ -1175,7 +1187,7 @@ cdef class FiniteField(Field): E = GF(self.characteristic()**(modulus.degree()), name=name, modulus=modulus, **kwds) else: E = Field.extension(self, modulus, name=name, embedding=embedding) - elif isinstance(modulus, Integer): + elif isinstance(modulus, (int, Integer)): E = GF(self.order()**modulus, name=name, **kwds) else: E = Field.extension(self, modulus, name=name, embedding=embedding) @@ -1421,6 +1433,129 @@ cdef class FiniteField(Field): from sage.rings.finite_rings.hom_finite_field import FrobeniusEndomorphism_finite_field return FrobeniusEndomorphism_finite_field(self, n) + def dual_basis(self, basis=None, check=True): + r""" + Return the dual basis of ``basis``, or the dual basis of the power + basis if no basis is supplied. + + If `e = \{e_0, e_1, ..., e_{n-1}\}` is a basis of + `\GF{p^n}` as a vector space over `\GF{p}`, then the dual basis of `e`, + `d = \{d_0, d_1, ..., d_{n-1}\}`, is the unique basis such that + `\mathrm{Tr}(e_i d_j) = \delta_{i,j}, 0 \leq i,j \leq n-1`, where + `\mathrm{Tr}` is the trace function. + + INPUT: + + - ``basis`` -- (default: ``None``): a basis of the finite field + ``self``, `\GF{p^n}`, as a vector space over the base field + `\GF{p}`. Uses the power basis `\{x^i : 0 \leq i \leq n-1\}` as + input if no basis is supplied, where `x` is the generator of + ``self``. + + - ``check`` -- (default: ``True``): verifies that ``basis`` is + a valid basis of ``self``. + + ALGORITHM: + + The algorithm used to calculate the dual basis comes from pages + 110--111 of [FFCSE1987]_. + + Let `e = \{e_0, e_1, ..., e_{n-1}\}` be a basis of `\GF{p^n}` as a + vector space over `\GF{p}` and `d = \{d_0, d_1, ..., d_{n-1}\}` be the + dual basis of `e`. Since `e` is a basis, we can rewrite any + `d_c, 0 \leq c \leq n-1`, as + `d_c = \beta_0 e_0 + \beta_1 e_1 + ... + \beta_{n-1} e_{n-1}`, for some + `\beta_0, \beta_1, ..., \beta_{n-1} \in \GF{p}`. Using properties of + the trace function, we can rewrite the `n` equations of the form + `\mathrm{Tr}(e_i d_c) = \delta_{i,c}` and express the result as the + matrix vector product: + `A [\beta_0, \beta_1, ..., \beta_{n-1}] = i_c`, where the `i,j`-th + element of `A` is `\mathrm{Tr(e_i e_j)}` and `i_c` is the `i`-th + column of the `n \times n` identity matrix. Since `A` is an invertible + matrix, `[\beta_0, \beta_1, ..., \beta_{n-1}] = A^{-1} i_c`, from + which we can easily calculate `d_c`. + + EXAMPLES:: + + sage: F. = GF(2^4) + sage: F.dual_basis(basis=None, check=False) + [a^3 + 1, a^2, a, 1] + + We can test that the dual basis returned satisfies the defining + property of a dual basis: + `\mathrm{Tr}(e_i d_j) = \delta_{i,j}, 0 \leq i,j \leq n-1` :: + + sage: F. = GF(7^4) + sage: e = [4*a^3, 2*a^3 + a^2 + 3*a + 5, + ....: 3*a^3 + 5*a^2 + 4*a + 2, 2*a^3 + 2*a^2 + 2] + sage: d = F.dual_basis(e, check=True); d + [3*a^3 + 4*a^2 + 6*a + 2, a^3 + 6*a + 5, + 3*a^3 + 6*a^2 + 2*a + 5, 5*a^2 + 4*a + 3] + sage: vals = [[(x * y).trace() for x in e] for y in d] + sage: matrix(vals) == matrix.identity(4) + True + + We can test that if `d` is the dual basis of `e`, then `e` is the dual + basis of `d`:: + + sage: F. = GF(7^8) + sage: e = [a^0, a^1, a^2, a^3, a^4, a^5, a^6, a^7] + sage: d = F.dual_basis(e, check=False); d + [6*a^6 + 4*a^5 + 4*a^4 + a^3 + 6*a^2 + 3, + 6*a^7 + 4*a^6 + 4*a^5 + 2*a^4 + a^2, + 4*a^6 + 5*a^5 + 5*a^4 + 4*a^3 + 5*a^2 + a + 6, + 5*a^7 + a^6 + a^4 + 4*a^3 + 4*a^2 + 1, + 2*a^7 + 5*a^6 + a^5 + a^3 + 5*a^2 + 2*a + 4, + a^7 + 2*a^6 + 5*a^5 + a^4 + 5*a^2 + 4*a + 4, + a^7 + a^6 + 2*a^5 + 5*a^4 + a^3 + 4*a^2 + 4*a + 6, + 5*a^7 + a^6 + a^5 + 2*a^4 + 5*a^3 + 6*a] + sage: F.dual_basis(d) + [1, a, a^2, a^3, a^4, a^5, a^6, a^7] + + We cannot calculate the dual basis if ``basis`` is not a valid basis. + :: + + sage: F. = GF(2^3) + sage: F.dual_basis([a], check=True) + Traceback (most recent call last): + ... + ValueError: basis length should be 3, not 1 + + sage: F.dual_basis([a^0, a, a^0 + a], check=True) + Traceback (most recent call last): + ... + ValueError: value of 'basis' keyword is not a basis + + REFERENCES: + + .. [FFCSE1987] Robert J. McEliece. Finite Fields for Computer + Scientists and Engineers. Kluwer Academic Publishers, 1987. + + AUTHOR: + + - Thomas Gagne (2015-06-16) + """ + from sage.matrix.constructor import matrix + + if basis == None: + basis = [self.gen()**i for i in range(self.degree())] + check = False + + if check: + if len(basis) != self.degree(): + msg = 'basis length should be {0}, not {1}' + raise ValueError(msg.format(self.degree(), len(basis))) + V = self.vector_space() + vec_reps = [V(b) for b in basis] + if matrix(vec_reps).is_singular(): + raise ValueError('value of \'basis\' keyword is not a basis') + + entries = [(basis[i] * basis[j]).trace() for i in range(self.degree()) + for j in range(self.degree())] + B = matrix(self.base_ring(), self.degree(), entries).inverse() + db = [sum(map(lambda x: x[0] * x[1], zip(col, basis))) + for col in B.columns()] + return db def unpickle_FiniteField_ext(_type, order, variable_name, modulus, kwargs): r""" diff --git a/src/sage/rings/finite_rings/finite_field_ext_pari.py b/src/sage/rings/finite_rings/finite_field_ext_pari.py index df4ebdfbb3a..b2c60d49ead 100644 --- a/src/sage/rings/finite_rings/finite_field_ext_pari.py +++ b/src/sage/rings/finite_rings/finite_field_ext_pari.py @@ -1,5 +1,5 @@ """ -Finite Extension Fields implemented via PARI POLMODs (deprecated). +Finite Extension Fields implemented via PARI POLMODs (deprecated) AUTHORS: diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 04a45c10142..1ca9fd576c1 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -341,16 +341,10 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): """ return self._section_class(self) - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) - cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ A class implementing Frobenius endomorphisms on finite fields. @@ -670,11 +664,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return self.power() == 0 - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index 37c2eb84b8a..4b041bba56b 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -88,7 +88,6 @@ import sage.rings.rational as rational from sage.libs.pari.all import pari, PariError import sage.rings.integer_ring as integer_ring -import sage.rings.commutative_ring_element as commutative_ring_element import sage.interfaces.all import sage.rings.integer @@ -1839,10 +1838,6 @@ cdef class IntegerMod_gmp(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_gmp self): """ Returns ``True`` if this is `1`, otherwise @@ -2253,10 +2248,6 @@ cdef class IntegerMod_int(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int self): """ Returns ``True`` if this is `1`, otherwise @@ -3081,10 +3072,6 @@ cdef class IntegerMod_int64(IntegerMod_abstract): elif self.ivalue < (right).ivalue: return -1 else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int64 self): """ Returns ``True`` if this is `1`, otherwise diff --git a/src/sage/rings/finite_rings/residue_field.pyx b/src/sage/rings/finite_rings/residue_field.pyx index 45be7eeddb5..c6041c3e843 100644 --- a/src/sage/rings/finite_rings/residue_field.pyx +++ b/src/sage/rings/finite_rings/residue_field.pyx @@ -1,5 +1,5 @@ """ -Finite residue fields. +Finite residue fields We can take the residue field of maximal ideals in the ring of integers of number fields. We can also take the residue field of irreducible diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index 4898d3c5760..ee3c10cf786 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -341,18 +341,6 @@ cdef class FpTElement(RingElement): else: return "\\frac{%s}{%s}" % (self.numer()._latex_(), self.denom()._latex_()) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K = Frac(GF(5)['t']); t = K.gen() - sage: t == 1 - False - sage: t + 1 < t^2 - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ Compares this with another element. The ordering is arbitrary, @@ -387,6 +375,14 @@ cdef class FpTElement(RingElement): True sage: b < 1/a False + + :: + + sage: K = Frac(GF(5)['t']); t = K.gen() + sage: t == 1 + False + sage: t + 1 < t^2 + True """ # They are normalized. cdef int j = sage_cmp_nmod_poly_t(self._numer, (other)._numer) diff --git a/src/sage/rings/fraction_field_element.pyx b/src/sage/rings/fraction_field_element.pyx index f8a944466a7..86d12adfb26 100644 --- a/src/sage/rings/fraction_field_element.pyx +++ b/src/sage/rings/fraction_field_element.pyx @@ -841,18 +841,6 @@ cdef class FractionFieldElement(FieldElement): """ return float(self.__numerator) / float(self.__denominator) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K. = Frac(ZZ['x,y']) - sage: x > y - True - sage: 1 > y - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -864,6 +852,14 @@ cdef class FractionFieldElement(FieldElement): True sage: t == t/5 False + + :: + + sage: K. = Frac(ZZ['x,y']) + sage: x > y + True + sage: 1 > y + False """ return cmp(self.__numerator * \ (other).__denominator, @@ -973,6 +969,36 @@ cdef class FractionFieldElement(FieldElement): return (make_element, (self._parent, self.__numerator, self.__denominator)) + def _evaluate_polynomial(self, pol): + """ + Evaluate a univariate polynomial on this fraction. + + EXAMPLES:: + + sage: R. = QQ[] + sage: pol = x^3 + 1 + sage: pol(1/x) + (x^3 + 1)/x^3 + + TESTS:: + + sage: R. = ZZ[] + sage: (~(y+z))._evaluate_polynomial(pol) + (y^3 + 3*y^2*z + 3*y*z^2 + z^3 + 1)/(y^3 + 3*y^2*z + 3*y*z^2 + z^3) + sage: rat = (y+z)/y + sage: rat._evaluate_polynomial(pol) + Traceback (most recent call last): + ... + NotImplementedError + sage: pol(rat) + (2*y^3 + 3*y^2*z + 3*y*z^2 + z^3)/y^3 + """ + inverse = ~self + if inverse.denominator().is_one(): + num = inverse.numerator() + return pol.reverse()(num)/num**pol.degree() + else: + raise NotImplementedError class FractionFieldElement_1poly_field(FractionFieldElement): """ diff --git a/src/sage/rings/function_field/function_field_element.pyx b/src/sage/rings/function_field/function_field_element.pyx index c701e7f8e39..2ecaabd469d 100644 --- a/src/sage/rings/function_field/function_field_element.pyx +++ b/src/sage/rings/function_field/function_field_element.pyx @@ -361,6 +361,17 @@ cdef class FunctionFieldElement_polymod(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x*y + 4*x^3) + sage: len({hash(y^i+x^j) for i in [-2..2] for j in [-2..2]}) == 25 + True + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -563,6 +574,19 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS: + + It would be nice if the following would produce a list of + 15 distinct hashes:: + + sage: K. = FunctionField(QQ) + sage: len({hash(t^i+t^j) for i in [-2..2] for j in [i..2]}) + 10 + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: diff --git a/src/sage/rings/ideal.py b/src/sage/rings/ideal.py index cde642dc404..36932590f97 100644 --- a/src/sage/rings/ideal.py +++ b/src/sage/rings/ideal.py @@ -1,5 +1,5 @@ r""" -Ideals of commutative rings. +Ideals of commutative rings Sage provides functionality for computing with ideals. One can create an ideal in any commutative or non-commutative ring `R` by giving a @@ -512,7 +512,7 @@ def apply_morphism(self, phi): return phi(self) def _latex_(self): - """ + r""" Return a latex representation of ``self``. EXAMPLES:: @@ -1229,6 +1229,22 @@ def __contains__(self, x): return x.is_zero() return self.gen().divides(x) + def __hash__(self): + r""" + Very stupid constant hash function! + + TESTS:: + + sage: P. = PolynomialRing(ZZ) + sage: I = P.ideal(x^2) + sage: J = [x, y^2 + x*y]*P + sage: hash(I) + 0 + sage: hash(J) + 0 + """ + return 0 + def __cmp__(self, other): """ Compare the two ideals. @@ -1316,10 +1332,10 @@ def __add__(self, other): EXAMPLES:: - sage: I = 8*ZZ - sage: I2 = 3*ZZ - sage: I + I2 - Principal ideal (1) of Integer Ring + sage: I = 8*ZZ + sage: I2 = 3*ZZ + sage: I + I2 + Principal ideal (1) of Integer Ring """ if not isinstance(other, Ideal_generic): other = self.ring().ideal(other) diff --git a/src/sage/rings/infinity.py b/src/sage/rings/infinity.py index 1c18c220837..64b5d78c147 100644 --- a/src/sage/rings/infinity.py +++ b/src/sage/rings/infinity.py @@ -1,5 +1,5 @@ r""" -Infinity Rings +Signed and Unsigned Infinities The unsigned infinity "ring" is the set of two elements @@ -207,12 +207,9 @@ [(+Infinity)] """ -from sage.rings.ring_element import RingElement from sage.rings.ring import Ring from sage.structure.element import RingElement, InfinityElement, PlusInfinityElement, MinusInfinityElement from sage.structure.parent_gens import ParentWithGens -#import sage.rings.real_double -#import sage.rings.real_mpfr import sage.rings.integer import sage.rings.rational diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 348cfa923a9..35a6db4810b 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -3049,7 +3049,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): TESTS: - Check that trac:`9345` is fixed:: + Check that :trac:`9345` is fixed:: sage: 0.rational_reconstruction(0) Traceback (most recent call last): @@ -6266,16 +6266,23 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: 13.binomial(2r) 78 - Check that it can be interrupted:: + Check that it can be interrupted (:trac:`17852`):: sage: alarm(0.5); (2^100).binomial(2^22, algorithm='mpir') Traceback (most recent call last): ... AlarmInterrupt - sage: alarm(0.5); (2^100).binomial(2^22, algorithm='pari') - Traceback (most recent call last): - ... - AlarmInterrupt + + For PARI, we try 10 interrupts with increasing intervals to + check for reliable interrupting, see :trac:`18919`:: + + sage: from sage.ext.interrupt import AlarmInterrupt + sage: for i in [1..10]: # long time (5s) + ....: try: + ....: alarm(i/11) + ....: (2^100).binomial(2^22, algorithm='pari') + ....: except AlarmInterrupt: + ....: pass """ cdef Integer x cdef Integer mm diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index a1f458eb44d..76adb61c7d6 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -45,7 +45,6 @@ other types will also coerce to the integers, when it makes sense. include "sage/ext/cdefs.pxi" include "sage/ext/stdsage.pxi" include "sage/ext/interrupt.pxi" # ctrl-c interrupt block support -include "sage/ext/random.pxi" from cpython.int cimport * from cpython.list cimport * @@ -64,6 +63,7 @@ from sage.structure.parent_gens import ParentWithGens from sage.structure.parent cimport Parent from sage.structure.sequence import Sequence from sage.misc.misc_c import prod +from sage.misc.randstate cimport randstate, current_randstate, SAGE_RAND_MAX cimport integer cimport rational @@ -122,7 +122,8 @@ cdef class IntegerRing_class(PrincipalIdealDomain): False sage: Z.category() Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Z(2^(2^5) + 1) 4294967297 @@ -303,7 +304,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): True """ ParentWithGens.__init__(self, self, ('x',), normalize=False, - category=(EuclideanDomains(), InfiniteEnumeratedSets())) + category=(EuclideanDomains(), InfiniteEnumeratedSets().Metric())) self._populate_coercion_lists_(element_constructor=integer.Integer, init_no_parent=True, convert_method_name='_integer_') @@ -415,11 +416,11 @@ cdef class IntegerRing_class(PrincipalIdealDomain): sage: A._div(12,0) Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero """ cdef rational.Rational x = rational.Rational.__new__(rational.Rational) if mpz_sgn(right.value) == 0: - raise ZeroDivisionError('Rational division by zero') + raise ZeroDivisionError('rational division by zero') mpz_set(mpq_numref(x.value), left.value) mpz_set(mpq_denref(x.value), right.value) mpq_canonicalize(x.value) @@ -1245,9 +1246,9 @@ cdef class IntegerRing_class(PrincipalIdealDomain): elif n == 2: return sage.rings.integer.Integer(-1) elif n < 1: - raise ValueError, "n must be positive in zeta()" + raise ValueError("n must be positive in zeta()") else: - raise ValueError, "no nth root of unity in integer ring" + raise ValueError("no nth root of unity in integer ring") def parameter(self): r""" diff --git a/src/sage/rings/integral_domain_element.py b/src/sage/rings/integral_domain_element.py index 1e06e871ab3..a321a7f1c7e 100644 --- a/src/sage/rings/integral_domain_element.py +++ b/src/sage/rings/integral_domain_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.integral_domain_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import IntegralDomainElement def is_IntegralDomainElement(x): @@ -25,6 +28,9 @@ def is_IntegralDomainElement(x): EXAMPLES:: + sage: import sage.rings.integral_domain_element + doctest:...: DeprecationWarning: the module sage.rings.integral_domain_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.integral_domain_element.is_IntegralDomainElement(ZZ(2)) True """ diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 757717e6c9b..7965773001c 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2258,7 +2258,7 @@ def first(self): The first algebraic form used in the definition. EXAMPLES:: - + sage: R. = QQ[] sage: q0 = invariant_theory.quadratic_form(x^2 + y^2) sage: q1 = invariant_theory.quadratic_form(x*y) @@ -2283,7 +2283,7 @@ def second(self): The second form used in the definition. EXAMPLES:: - + sage: R. = QQ[] sage: q0 = invariant_theory.quadratic_form(x^2 + y^2) sage: q1 = invariant_theory.quadratic_form(x*y) @@ -2560,7 +2560,7 @@ def Delta_invariant(self): Return the `\Delta` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2579,7 +2579,7 @@ def Delta_prime_invariant(self): Return the `\Delta'` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2627,7 +2627,7 @@ def Theta_invariant(self): Return the `\Theta` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2646,7 +2646,7 @@ def Theta_prime_invariant(self): Return the `\Theta'` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2665,7 +2665,7 @@ def Phi_invariant(self): Return the `\Phi'` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2784,7 +2784,7 @@ def T_covariant(self): The `T`-covariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2806,7 +2806,7 @@ def T_prime_covariant(self): The `T'`-covariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a0*x^2 + a1*y^2 + a2*z^2 + a3 sage: p1 += b0*x*y + b1*x*z + b2*x + b3*y*z + b4*y + b5*z @@ -2867,7 +2867,7 @@ def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, covariants of a quaternary biquadratic. EXAMPLES:: - + sage: R. = QQ[] sage: monomials = [x^2, x*y, y^2, x*z, y*z, z^2, x*w, y*w, z*w, w^2] sage: def q_rnd(): return sum(randint(-1000,1000)*m for m in monomials) @@ -3344,7 +3344,7 @@ def quaternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): Distance between two spheres [Salmon]_ :: - + sage: R. = QQ[] sage: S1 = -r1^2 + x^2 + y^2 + z^2 sage: S2 = -r2^2 + (x-a)^2 + (y-b)^2 + (z-c)^2 diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index fc6669484ba..d64288a10dd 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -597,7 +597,7 @@ def uniformizer(self): a field). Otherwise, an error is raised. EXAMPLES:: - + sage: R. = LaurentSeriesRing(QQ) sage: R.uniformizer() t diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index d4c822589eb..77ac755c16e 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -61,7 +61,6 @@ import power_series_ring_element import power_series_ring import sage.rings.polynomial.polynomial_element as polynomial import sage.misc.latex -import sage.rings.ring_element as ring_element from sage.rings.integer import Integer from sage.rings.polynomial.laurent_polynomial import LaurentPolynomial_univariate @@ -642,8 +641,11 @@ cdef class LaurentSeries(AlgebraElement): """ if prec == infinity or prec >= self.prec(): return self + P = self._parent + if not self: + return LaurentSeries(P, P.power_series_ring()(0, prec=0), prec) u = self.__u.add_bigoh(prec - self.__n) - return LaurentSeries(self._parent, u, self.__n) + return LaurentSeries(P, u, self.__n) def degree(self): """ @@ -870,9 +872,6 @@ cdef class LaurentSeries(AlgebraElement): return self.prec() return min(self.prec(), f.prec()) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of self and right. @@ -1232,10 +1231,22 @@ cdef class LaurentSeries(AlgebraElement): sage: f.power_series() Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + TESTS: + + Check whether a polynomial over a Laurent series ring is contained in the + polynomial ring over the power series ring (see :trac:`19459`): + + sage: L. = LaurentSeriesRing(GF(2)) + sage: R. = PolynomialRing(L) + sage: O = L.power_series_ring() + sage: S. = PolynomialRing(O) + sage: t**(-1)*x*y in S + False """ if self.__n < 0: - raise ArithmeticError, "self is a not a power series" + raise TypeError("self is not a power series") u = self.__u t = u.parent().gen() return t**(self.__n) * u diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 905378f4765..594cf9ff34e 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -1137,20 +1137,6 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): _slots['__im_gens'] = self.__im_gens return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: cmp(f,g) # indirect doctest - 1 - sage: cmp(g,f) - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1175,6 +1161,14 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): sage: loads(dumps(f2)) == f2 True + :: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: cmp(f,g) # indirect doctest + 1 + sage: cmp(g,f) + -1 + EXAMPLES: A multivariate quotient over a finite field:: @@ -1441,22 +1435,6 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): _slots['__underlying'] = self.__underlying return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: S. = R[] - sage: fS = S.hom(f,S); gS = S.hom(g,S) - sage: cmp(fS,gS) # indirect doctest - 1 - sage: cmp(gS,fS) # indirect doctest - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1480,6 +1458,16 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): sage: f1P == loads(dumps(f1P)) True + TESTS:: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: S. = R[] + sage: fS = S.hom(f,S); gS = S.hom(g,S) + sage: cmp(fS,gS) # indirect doctest + 1 + sage: cmp(gS,fS) # indirect doctest + -1 + EXAMPLES: A matrix ring over a multivariate quotient over a finite field:: @@ -1615,12 +1603,12 @@ cdef class RingHomomorphism_cover(RingHomomorphism): sage: f(-5) # indirect doctest 1 - TESTS:: + TESTS: We verify that calling directly raises the expected error (just coercing into the codomain), but calling with __call__ (the second call below) gives a TypeError since 1/2 can't be - coerced into the domain. + coerced into the domain. :: sage: f._call_(1/2) Traceback (most recent call last): @@ -2109,9 +2097,6 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): codomain = self.codomain() return hash((domain, codomain, ('Frob', self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 domain = left.domain() diff --git a/src/sage/rings/multi_power_series_ring_element.py b/src/sage/rings/multi_power_series_ring_element.py index a233bcfef2a..ea027100cbb 100644 --- a/src/sage/rings/multi_power_series_ring_element.py +++ b/src/sage/rings/multi_power_series_ring_element.py @@ -1,5 +1,5 @@ r""" -Multivariate Power Series. +Multivariate Power Series Construct and manipulate multivariate power series (in finitely many variables) over a given commutative ring. Multivariate power series diff --git a/src/sage/rings/noncommutative_ideals.pyx b/src/sage/rings/noncommutative_ideals.pyx index e8c8ffc1304..3a44d706029 100644 --- a/src/sage/rings/noncommutative_ideals.pyx +++ b/src/sage/rings/noncommutative_ideals.pyx @@ -8,6 +8,8 @@ ############################################################################### """ +Ideals of non-commutative rings + Generic implementation of one- and two-sided ideals of non-commutative rings. AUTHOR: diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index bb4f9328b56..e5dc77f2e03 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -661,9 +661,31 @@ def as_hom(self): sage: G[1].as_hom() Ring endomorphism of Number Field in w with defining polynomial x^2 + 7 Defn: w |--> -w + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: R. = QQ[] + sage: f = 7/9*x^3 + 7/3*x^2 - 56*x + 123 + sage: K. = NumberField(f) + sage: G = K.galois_group() + sage: G[1].as_hom() + Ring endomorphism of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + Defn: a |--> -7/15*a^2 - 18/5*a + 96/5 + sage: prod(x - sigma(a) for sigma in G) == f.monic() + True """ - L = self.parent().splitting_field() - a = L(self.parent()._pari_data.galoispermtopol(pari(self.domain()).Vecsmall())) + G = self.parent() + L = G.splitting_field() + # First compute the image of the standard generator of the + # PARI number field. + a = G._pari_data.galoispermtopol(pari(self.domain()).Vecsmall()) + # Now convert this to a conjugate of the standard generator of + # the Sage number field. + P = L._pari_absolute_structure()[1].lift() + a = L(P(a.Mod(L.pari_polynomial('y')))) return L.hom(a, L) def __call__(self, x): diff --git a/src/sage/rings/number_field/maps.py b/src/sage/rings/number_field/maps.py index d944fcb4e0f..2c84b0f91d8 100644 --- a/src/sage/rings/number_field/maps.py +++ b/src/sage/rings/number_field/maps.py @@ -237,20 +237,20 @@ class MapRelativeVectorSpaceToRelativeNumberField(NumberFieldIsomorphism): sage: L. = NumberField(x^4 + 3*x^2 + 1) sage: K = L.relativize(L.subfields(2)[0][1], 'a'); K - Number Field in a0 with defining polynomial x^2 - b0*x + 1 over its base field + Number Field in a with defining polynomial x^2 - b0*x + 1 over its base field sage: V, fr, to = K.relative_vector_space() sage: V Vector space of dimension 2 over Number Field in b0 with defining polynomial x^2 + 1 sage: fr Isomorphism map: From: Vector space of dimension 2 over Number Field in b0 with defining polynomial x^2 + 1 - To: Number Field in a0 with defining polynomial x^2 - b0*x + 1 over its base field + To: Number Field in a with defining polynomial x^2 - b0*x + 1 over its base field sage: type(fr) sage: a0 = K.gen(); b0 = K.base_field().gen() sage: fr(to(a0 + 2*b0)), fr(V([0, 1])), fr(V([b0, 2*b0])) - (a0 + 2*b0, a0, 2*b0*a0 + b0) + (a + 2*b0, a, 2*b0*a + b0) sage: (fr * to)(K.gen()) == K.gen() True sage: (to * fr)(V([1, 2])) == V([1, 2]) @@ -278,7 +278,7 @@ def _call_(self, v): sage: a0 = K.gen(); b0 = K.base_field().gen() sage: V, fr, to = K.relative_vector_space() sage: fr(to(a0 + 2*b0)), fr(V([0, 1])), fr(V([b0, 2*b0])) # indirect doctest - (a0 + 2*b0, a0, 2*b0*a0 + b0) + (a + 2*b0, a, 2*b0*a + b0) """ K = self.codomain() B = K.base_field().absolute_field('a') @@ -288,7 +288,7 @@ def _call_(self, v): h = pari([to_B(a)._pari_('y') for a in v]).Polrev() # Rewrite the polynomial in terms of an absolute generator for # the relative number field. - g = K.pari_rnf().rnfeltreltoabs(h) + g = K._pari_rnfeq()._eltreltoabs(h) return K._element_class(K, g) class MapRelativeNumberFieldToRelativeVectorSpace(NumberFieldIsomorphism): @@ -310,7 +310,7 @@ def __init__(self, K, V): sage: V, fr, to = K.relative_vector_space() sage: to Isomorphism map: - From: Number Field in a0 with defining polynomial x^2 - b0*x + 1 over its base field + From: Number Field in a with defining polynomial x^2 - b0*x + 1 over its base field To: Vector space of dimension 2 over Number Field in b0 with defining polynomial x^2 + 1 """ NumberFieldIsomorphism.__init__(self, Hom(K, V)) @@ -347,13 +347,18 @@ def _call_(self, alpha): K = self.domain() # The element alpha is represented internally by an absolute # polynomial over QQ, and f is its PARI representation. - f = alpha._pari_('x') + f = alpha._pari_polynomial('x') # Convert f to a relative polynomial g; this is a polynomial # in x whose coefficients are polynomials in y. - g = K.pari_rnf().rnfeltabstorel(f).lift().lift() + g = K._pari_rnfeq()._eltabstorel_lift(f) + # Now g is a polynomial in the standard generator of the PARI + # field; convert it to a polynomial in the Sage generator. + if g.poldegree() > 0: + beta = K._pari_relative_structure()[2] + g = g(beta).lift() # Convert the coefficients to elements of the base field. B, from_B, _ = K.absolute_base_field() - return self.codomain()([from_B(B(z)) for z in g.Vecrev(-K.relative_degree())]) + return self.codomain()([from_B(B(z.lift(), check=False)) for z in g.Vecrev(-K.relative_degree())]) class NameChangeMap(NumberFieldIsomorphism): r""" @@ -425,19 +430,19 @@ class MapRelativeToAbsoluteNumberField(NumberFieldIsomorphism): sage: K. = NumberField(x^6 + 4*x^2 + 200) sage: L = K.relativize(K.subfields(3)[0][1], 'b'); L - Number Field in b0 with defining polynomial x^2 + a0 over its base field + Number Field in b with defining polynomial x^2 + a0 over its base field sage: fr, to = L.structure() sage: fr Relative number field morphism: - From: Number Field in b0 with defining polynomial x^2 + a0 over its base field + From: Number Field in b with defining polynomial x^2 + a0 over its base field To: Number Field in a with defining polynomial x^6 + 4*x^2 + 200 - Defn: b0 |--> a + Defn: b |--> a a0 |--> -a^2 sage: to Ring morphism: From: Number Field in a with defining polynomial x^6 + 4*x^2 + 200 - To: Number Field in b0 with defining polynomial x^2 + a0 over its base field - Defn: a |--> b0 + To: Number Field in b with defining polynomial x^2 + a0 over its base field + Defn: a |--> b sage: type(fr), type(to) (, ) @@ -448,16 +453,16 @@ class MapRelativeToAbsoluteNumberField(NumberFieldIsomorphism): sage: fr Isomorphism map: From: Number Field in c with defining polynomial x^6 + 4*x^2 + 200 - To: Number Field in b0 with defining polynomial x^2 + a0 over its base field + To: Number Field in b with defining polynomial x^2 + a0 over its base field sage: to Isomorphism map: - From: Number Field in b0 with defining polynomial x^2 + a0 over its base field + From: Number Field in b with defining polynomial x^2 + a0 over its base field To: Number Field in c with defining polynomial x^6 + 4*x^2 + 200 sage: type(fr), type(to) (, ) sage: fr(M.gen()), to(fr(M.gen())) == M.gen() - (b0, True) + (b, True) sage: to(L.gen()), fr(to(L.gen())) == L.gen() (c, True) sage: (to * fr)(M.gen()) == M.gen(), (fr * to)(L.gen()) == L.gen() @@ -573,7 +578,7 @@ class MapRelativeNumberFieldToVectorSpace(NumberFieldIsomorphism): sage: K. = NumberField(x^8 + 100*x^6 + x^2 + 5) sage: L = K.relativize(K.subfields(4)[0][1], 'b'); L - Number Field in b0 with defining polynomial x^2 + a0 over its base field + Number Field in b with defining polynomial x^2 + a0 over its base field sage: L_to_K, K_to_L = L.structure() sage: V, fr, to = L.absolute_vector_space() @@ -582,10 +587,10 @@ class MapRelativeNumberFieldToVectorSpace(NumberFieldIsomorphism): sage: fr Isomorphism map: From: Vector space of dimension 8 over Rational Field - To: Number Field in b0 with defining polynomial x^2 + a0 over its base field + To: Number Field in b with defining polynomial x^2 + a0 over its base field sage: to Isomorphism map: - From: Number Field in b0 with defining polynomial x^2 + a0 over its base field + From: Number Field in b with defining polynomial x^2 + a0 over its base field To: Vector space of dimension 8 over Rational Field sage: type(fr), type(to) (, @@ -593,7 +598,7 @@ class MapRelativeNumberFieldToVectorSpace(NumberFieldIsomorphism): sage: v = V([1, 1, 1, 1, 0, 1, 1, 1]) sage: fr(v), to(fr(v)) == v - ((-a0^3 + a0^2 - a0 + 1)*b0 - a0^3 - a0 + 1, True) + ((-a0^3 + a0^2 - a0 + 1)*b - a0^3 - a0 + 1, True) sage: to(L.gen()), fr(to(L.gen())) == L.gen() ((0, 1, 0, 0, 0, 0, 0, 0), True) """ diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 0ab6f0ae2e5..6c8a835fcc2 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -379,6 +379,40 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, l sage: sqrtn3 + zeta 2*zeta^5 + zeta + 1 + Since SageMath 6.9, number fields may be defined by polynomials + that are not necessarily integral or monic. The only notable + practical point is that in the PARI interface, a monic integral + polynomial defining the same number field is computed and used:: + + sage: K. = NumberField(2*x^3 + x + 1) + sage: K.pari_polynomial() + x^3 - x^2 - 2 + + Elements and ideals may be converted to and from PARI as follows:: + + sage: pari(a) + Mod(-1/2*y^2 + 1/2*y, y^3 - y^2 - 2) + sage: K(pari(a)) + a + sage: I = K.ideal(a); I + Fractional ideal (a) + sage: I.pari_hnf() + [1, 0, 0; 0, 1, 0; 0, 0, 1/2] + sage: K.ideal(I.pari_hnf()) + Fractional ideal (a) + + Here is an example where the field has non-trivial class group:: + + sage: L. = NumberField(3*x^2 - 1/5) + sage: L.pari_polynomial() + x^2 - 15 + sage: J = L.primes_above(2)[0]; J + Fractional ideal (2, 15*b + 1) + sage: J.pari_hnf() + [2, 1; 0, 1] + sage: L.ideal(J.pari_hnf()) + Fractional ideal (2, 15*b + 1) + An example involving a variable name that defines a function in PARI:: @@ -1154,8 +1188,6 @@ def __init__(self, polynomial, name, if check: if not polynomial.parent().base_ring() == QQ: raise TypeError("polynomial must be defined over rational field") - if not polynomial.is_monic(): - raise NotImplementedError("number fields for non-monic polynomials not yet implemented.") if not polynomial.is_irreducible(): raise ValueError("defining polynomial (%s) must be irreducible"%polynomial) @@ -1347,17 +1379,18 @@ def construction(self): embeddings.append(None if K.coerce_embedding() is None else K.coerce_embedding()(K.gen())) return (AlgebraicExtensionFunctor(polys, names, embeddings), QQ) - def _element_constructor_(self, x): + def _element_constructor_(self, x, check=True): r""" - Make x into an element of this number field, possibly not canonically. + Convert ``x`` into an element of this number field. INPUT: - - ``x`` - the element + - ``x`` -- Sage (or Python) object OUTPUT: - ``x``, as an element of this number field + A :class:`~number_field_element.NumberFieldElement` + constructed from ``x``. TESTS:: @@ -1375,6 +1408,58 @@ def _element_constructor_(self, x): sage: F([a]) a + We can create number field elements from PARI:: + + sage: K. = NumberField(x^3 - 17) + sage: K(pari(42)) + 42 + sage: K(pari("5/3")) + 5/3 + sage: K(pari("[3/2, -5, 0]~")) # Uses Z-basis + -5/3*a^2 + 5/3*a - 1/6 + + From a PARI polynomial or ``POLMOD``, note that the variable + name does not matter:: + + sage: K(pari("-5/3*q^2 + 5/3*q - 1/6")) + -5/3*a^2 + 5/3*a - 1/6 + sage: K(pari("Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 17)")) + -5/3*a^2 + 5/3*a - 1/6 + sage: K(pari("x^5/17")) + a^2 + + An error is raised when a PARI element with an incorrect + modulus is given: + + sage: K(pari("Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 999)")) + Traceback (most recent call last): + ... + TypeError: cannot convert PARI element Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 999) into Number Field in a with defining polynomial x^3 - 17 + + Test round-trip conversion to PARI and back:: + + sage: x = polygen(QQ) + sage: K. = NumberField(x^3 - 1/2*x + 1/3) + sage: b = K.random_element() + sage: K(pari(b)) == b + True + + sage: F. = NumberField(2*x^3 + x + 1) + sage: d = F.random_element() + sage: F(F.pari_nf().nfalgtobasis(d)) == d + True + + If the PARI polynomial is different from the Sage polynomial, + a warning is printed unless ``check=False`` is specified:: + + sage: b = pari(a); b + Mod(-1/12*y^2 - 1/12*y + 1/6, y^3 - 3*y - 22) + sage: K(b.lift()) + doctest:...: UserWarning: interpreting PARI polynomial -1/12*y^2 - 1/12*y + 1/6 relative to the defining polynomial x^3 - 3*x - 22 of the PARI number field + a + sage: K(b.lift(), check=False) + a + Using a GAP element may be tricky, as it may contain an exclamation mark:: @@ -1401,6 +1486,33 @@ def _element_constructor_(self, x): return self._element_class(self, x) x = L(x) return self._coerce_from_other_number_field(x) + elif isinstance(x, pari_gen): + if x.type() == "t_POLMOD": + modulus = x.mod() + if check and modulus != self.pari_polynomial(modulus.variable()): + raise TypeError("cannot convert PARI element %s into %s" % (x, self)) + x = x.lift() + check = False + elif x.type() == "t_COL": + x = self.pari_nf().nfbasistoalg_lift(x) + check = False + if x.type() in ["t_INT", "t_FRAC"]: + pass + elif x.type() == "t_POL": + if check and self.pari_polynomial() != self.absolute_polynomial().monic(): + from warnings import warn + warn("interpreting PARI polynomial %s relative to the defining polynomial %s of the PARI number field" + % (x, self.pari_polynomial())) + # We consider x as a polynomial in the standard + # generator of the PARI number field, and convert it + # to a polynomial in the Sage generator. + if x.poldegree() > 0: + beta = self._pari_absolute_structure()[2] + x = x(beta).lift() + else: + raise TypeError("%s has unsupported PARI type %s" % (x, x.type())) + x = self.absolute_polynomial().parent()(x) + return self._element_class(self, x) elif sage.interfaces.gap.is_GapElement(x): s = repr(x) if self.variable_name() in s: @@ -2176,17 +2288,17 @@ def maximal_totally_real_subfield(self): sage: y = polygen(E_0) sage: E. = E_0.extension(y^2 - E_0.gen() / 2) sage: E.maximal_totally_real_subfield() - [Number Field in z2 with defining polynomial x^2 - 6, Composite map: - From: Number Field in z2 with defining polynomial x^2 - 6 - To: Number Field in z with defining polynomial x^2 - 1/2*a over its base field - Defn: Ring morphism: - From: Number Field in z2 with defining polynomial x^2 - 6 - To: Number Field in z with defining polynomial x^4 - 2*x^2 + 4 - Defn: z2 |--> -1/2*z^3 + 2*z - then - Isomorphism map: - From: Number Field in z with defining polynomial x^4 - 2*x^2 + 4 - To: Number Field in z with defining polynomial x^2 - 1/2*a over its base field] + [Number Field in z1 with defining polynomial x^2 - 2*x - 5, Composite map: + From: Number Field in z1 with defining polynomial x^2 - 2*x - 5 + To: Number Field in z with defining polynomial x^2 - 1/2*a over its base field + Defn: Ring morphism: + From: Number Field in z1 with defining polynomial x^2 - 2*x - 5 + To: Number Field in z with defining polynomial x^4 - 2*x^3 + x^2 + 6*x + 3 + Defn: z1 |--> -1/3*z^3 + 1/3*z^2 + z - 1 + then + Isomorphism map: + From: Number Field in z with defining polynomial x^4 - 2*x^3 + x^2 + 6*x + 3 + To: Number Field in z with defining polynomial x^2 - 1/2*a over its base field] """ @@ -3217,26 +3329,96 @@ def _is_valid_homomorphism_(self, codomain, im_gens): except (TypeError, ValueError): return False + @cached_method + def _pari_absolute_structure(self): + r""" + Return data relating the Sage and PARI absolute polynomials. + + OUTPUT: + + Let `L` be this number field, and let `f` be the defining + polynomial of `K` over `\QQ`. This method returns a triple + ``(g, alpha, beta)``, where + + - ``g`` is the defining relative polynomial of the PARI ``nf`` + structure (see :meth:`pari_nf`); + + - ``alpha`` is the image of `x \bmod f` under some isomorphism + `\phi\colon K[x]/(f) \to K[x]/(g)` + + - ``beta`` is the image of `x \bmod g` under the inverse + isomorphism `\phi^{-1}\colon K[x]/(g) \to K[x]/(f)` + + EXAMPLES:: + + If `f` is monic and integral, the result satisfies ``g = f`` + and ``alpha = beta = x``:: + + sage: K. = NumberField(x^2 - 2) + sage: K._pari_absolute_structure() + (y^2 - 2, Mod(y, y^2 - 2), Mod(y, y^2 - 2)) + + An example where `f` neither monic nor integral:: + + sage: K. = NumberField(2*x^2 + 1/3) + sage: K._pari_absolute_structure() + (y^2 + 6, Mod(1/6*y, y^2 + 6), Mod(6*y, y^2 + 1/6)) + """ + f = self.absolute_polynomial()._pari_with_name('y') + if f.pollead() == f.content().denominator() == 1: + g = f + alpha = beta = g.variable().Mod(g) + else: + g, alpha = f.polredbest(flag=1) + beta = alpha.modreverse() + return g, alpha, beta + def pari_polynomial(self, name='x'): """ - PARI polynomial with integer coefficients corresponding to the - polynomial that defines this number field. + Return the PARI polynomial corresponding to this number field. + + INPUT: + + - ``name`` -- variable name (default: ``'x'``) + + OUTPUT: + + A monic polynomial with integral coefficients (PARI ``t_POL``) + defining the PARI number field corresponding to ``self``. + + .. WARNING:: - By default, this is a polynomial in the variable "x". PARI - prefers integral polynomials, so we clear the denominator. - Therefore, this is NOT the same as simply converting the defining - polynomial to PARI. + This is *not* the same as simply converting the defining + polynomial to PARI. EXAMPLES:: sage: y = polygen(QQ) sage: k. = NumberField(y^2 - 3/2*y + 5/3) sage: k.pari_polynomial() - 6*x^2 - 9*x + 10 + x^2 - x + 40 sage: k.polynomial()._pari_() x^2 - 3/2*x + 5/3 sage: k.pari_polynomial('a') - 6*a^2 - 9*a + 10 + a^2 - a + 40 + + Some examples with relative number fields:: + + sage: k. = NumberField([x^2 + 3, x^2 + 1]) + sage: k.pari_polynomial() + x^4 + 8*x^2 + 4 + sage: k.pari_polynomial('a') + a^4 + 8*a^2 + 4 + sage: k.absolute_polynomial() + x^4 + 8*x^2 + 4 + sage: k.relative_polynomial() + x^2 + 3 + + sage: k. = NumberField([x^2 + 1/3, x^2 + 1/4]) + sage: k.pari_polynomial() + x^4 - x^2 + 1 + sage: k.absolute_polynomial() + x^4 - x^2 + 1 This fails with arguments which are not a valid PARI variable name:: @@ -3252,28 +3434,28 @@ def pari_polynomial(self, name='x'): ... PariError: theta already exists with incompatible valence """ - try: - return self._pari_polynomial.change_variable_name(name) - except AttributeError: - polypari = self.polynomial()._pari_with_name(name) - polypari /= polypari.content() # make polypari integral - self._pari_polynomial = polypari - return self._pari_polynomial + return self._pari_absolute_structure()[0].change_variable_name(name) def pari_nf(self, important=True): """ - PARI number field corresponding to this field. - - This is the number field constructed using nfinit(). This is the same - as the number field got by doing pari(self) or gp(self). + Return the PARI number field corresponding to this field. INPUT: - - ``important`` -- (default: True) bool. If False, raise a - ``RuntimeError`` if we need to do a difficult discriminant - factorization. Useful when the PARI nf structure is useful - but not strictly required, such as for factoring polynomials - over this number field. + - ``important`` -- boolean (default: ``True``). If ``False``, + raise a ``RuntimeError`` if we need to do a difficult + discriminant factorization. This is useful when an integral + basis is not strictly required, such as for factoring + polynomials over this number field. + + OUTPUT: + + The PARI number field obtained by calling the PARI function + ``nfinit()`` with ``self.pari_polynomial('y')`` as argument. + + .. NOTE:: + + This method has the same effect as ``pari(self)``. EXAMPLES:: @@ -3289,17 +3471,11 @@ def pari_nf(self, important=True): sage: k. = NumberField(x^4 - 3/2*x + 5/3); k Number Field in a with defining polynomial x^4 - 3/2*x + 5/3 sage: k.pari_nf() - Traceback (most recent call last): - ... - TypeError: Unable to coerce number field defined by non-integral polynomial to PARI. + [y^4 - 324*y + 2160, [0, 2], 48918708, 216, ..., [1, y, 1/36*y^3 + 1/6*y^2 - 7, 1/6*y^2], [1, 0, 0, 252; 0, 1, 0, 0; 0, 0, 0, 36; 0, 0, 6, -36], [1, 0, 0, 0, 0, 0, -18, 42, 0, -18, -46, -60, 0, 42, -60, -60; 0, 1, 0, 0, 1, 0, 2, 0, 0, 2, -11, -1, 0, 0, -1, 9; 0, 0, 1, 0, 0, 0, 6, 6, 1, 6, -5, 0, 0, 6, 0, 0; 0, 0, 0, 1, 0, 6, -6, -6, 0, -6, -1, 2, 1, -6, 2, 0]] sage: pari(k) - Traceback (most recent call last): - ... - TypeError: Unable to coerce number field defined by non-integral polynomial to PARI. + [y^4 - 324*y + 2160, [0, 2], 48918708, 216, ...] sage: gp(k) - Traceback (most recent call last): - ... - TypeError: Unable to coerce number field defined by non-integral polynomial to PARI. + [y^4 - 324*y + 2160, [0, 2], 48918708, 216, ...] With ``important=False``, we simply bail out if we cannot easily factor the discriminant:: @@ -3312,8 +3488,8 @@ def pari_nf(self, important=True): RuntimeError: Unable to factor discriminant with trial division Next, we illustrate the ``maximize_at_primes`` and ``assume_disc_small`` - parameters of the NumberField contructor. The following would take a - very long time without the ``maximize_at_primes`` option:: + parameters of the ``NumberField`` constructor. The following would take + a very long time without the ``maximize_at_primes`` option:: sage: K. = NumberField(x^2 - p*q, maximize_at_primes=[p]) sage: K.pari_nf() @@ -3325,8 +3501,6 @@ def pari_nf(self, important=True): sage: K.pari_nf() [y^2 - 100000000000000000000...] """ - if self.absolute_polynomial().denominator() != 1: - raise TypeError("Unable to coerce number field defined by non-integral polynomial to PARI.") try: return self._pari_nf except AttributeError: @@ -3354,10 +3528,7 @@ def pari_zk(self): def _pari_(self): """ - Converts this number field to PARI. - - This only works if the defining polynomial of this number field is - integral and monic. + Return the PARI number field corresponding to this field. EXAMPLES:: @@ -3371,10 +3542,7 @@ def _pari_(self): def _pari_init_(self): """ - Converts this number field to PARI. - - This only works if the defining polynomial of this number field is - integral and monic. + Return the PARI number field corresponding to this field. EXAMPLES:: @@ -3428,8 +3596,6 @@ def pari_bnf(self, proof=None, units=True): try: bnf = self._pari_bnf except AttributeError: - if self.absolute_polynomial().denominator() != 1: - raise TypeError("Unable to coerce number field defined by non-integral polynomial to PARI.") f = self.pari_polynomial("y") if units: self._pari_bnf = f.bnfinit(1) @@ -3465,9 +3631,8 @@ def pari_rnfnorm_data(self, L, proof=True): if L.base_field() != self: raise ValueError("L must be an extension of self") - relpoly = L.defining_polynomial() Kbnf = self.pari_bnf(proof=proof) - return Kbnf.rnfisnorminit(relpoly._pari_with_name()) + return Kbnf.rnfisnorminit(L.pari_relative_polynomial()) def _gap_init_(self): """ @@ -3769,7 +3934,7 @@ def _S_class_group_and_units(self, S, proof=True): sage: K._S_class_group_and_units( (K.ideal(5),) ) ([5, -1], []) - TEST:: + TESTS:: sage: K. = NumberField(x^3 - 381 * x + 127) sage: K._S_class_group_and_units(tuple(K.primes_above(13))) @@ -3781,12 +3946,19 @@ def _S_class_group_and_units(self, S, proof=True): 1/13*a^2 - 19/13*a - 7/13], [(Fractional ideal (11, a - 2), 2), (Fractional ideal (19, 1/13*a^2 - 45/13*a - 332/13), 2)]) + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: K._S_class_group_and_units(tuple(K.primes_above(2) + K.primes_above(3))) + ([-6*a + 2, 6*a + 3, -1, 12*a + 5], []) """ K_pari = self.pari_bnf(proof=proof) from sage.misc.all import uniq S_pari = [p.pari_prime() for p in uniq(S)] result = K_pari.bnfsunit(S_pari) - units = [self(_) for _ in result[0]] + self.unit_group().gens_values() + units = [self(x, check=False) for x in result[0]] + self.unit_group().gens_values() orders = result[4][1].sage() gens = [self.ideal(_) for _ in result[4][2]] return units, [(gens[k], orders[k]) for k in range(len(orders)) if orders[k] > 1] @@ -4028,42 +4200,48 @@ def selmer_group_iterator(self, S, m, proof=True): def composite_fields(self, other, names=None, both_maps=False, preserve_embedding=True): """ - List of all possible composite number fields formed from self and - other, together with (optionally) embeddings into the compositum; - see the documentation for both_maps below. - - If preserve_embedding is True and if self and other both have - embeddings into the same ambient field, or into fields which are - contained in a common field, only the compositum respecting - both embeddings is returned. If one (or both) of self or other - does not have an embedding or preserve_embedding is False, - all possible composite number fields are returned. + Return the possible composite number fields formed from + ``self`` and ``other``. INPUT: - - ``other`` - a number field + - ``other`` -- number field - - ``names`` - generator name for composite fields + - ``names`` -- generator name for composite fields - - ``both_maps`` - (default: False) if True, return quadruples - (F, self_into_F, other_into_F, k) such that self_into_F is an - embedding of self in F, other_into_F is an embedding of in F, - and k is an integer such that F.gen() equals - other_into_F(other.gen()) + k*self_into_F(self.gen()) - or has the value Infinity in which case F.gen() equals - self_into_F(self.gen()), or is None (which happens when other is a - relative number field). - If both self and other have embeddings into an ambient field, then - F will have an embedding with respect to which both self_into_F - and other_into_F will be compatible with the ambient embeddings. + - ``both_maps`` -- boolean (default: ``False``) - - ``preserve_embedding`` - (default: True) if self and other have - ambient embeddings, then return only the compatible compositum. + - ``preserve_embedding`` -- boolean (default: True) OUTPUT: - - ``list`` - list of the composite fields, possibly with maps. + A list of the composite fields, possibly with maps. + If ``both_maps`` is ``True``, the list consists of quadruples + ``(F, self_into_F, other_into_F, k)`` such that + ``self_into_F`` is an embedding of ``self`` in ``F``, + ``other_into_F`` is an embedding of in ``F``, and ``k`` is one + of the following: + + - an integer such that ``F.gen()`` equals + ``other_into_F(other.gen()) + k*self_into_F(self.gen())``; + + - ``Infinity``, in which case ``F.gen()`` equals + ``self_into_F(self.gen())``; + + - ``None`` (when ``other`` is a relative number field). + + If both ``self`` and ``other`` have embeddings into an ambient + field, then each ``F`` will have an embedding with respect to + which both ``self_into_F`` and ``other_into_F`` will be + compatible with the ambient embeddings. + + If ``preserve_embedding`` is ``True`` and if ``self`` and + ``other`` both have embeddings into the same ambient field, or + into fields which are contained in a common field, only the + compositum respecting both embeddings is returned. In all + other cases, all possible composite number fields are + returned. EXAMPLES:: @@ -4085,7 +4263,8 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin sage: f(K1.0), g(K2.0) (a, -a) - With preserve_embedding set to False, the embeddings are ignored:: + With ``preserve_embedding`` set to ``False``, the embeddings + are ignored:: sage: K1.composite_fields(K2, preserve_embedding=False) [Number Field in a with defining polynomial x^4 - 2, @@ -4099,7 +4278,8 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin sage: f(K1.0), g(K3.0) (1/240*a0^5 - 41/120*a0, 1/120*a0^5 + 19/60*a0) - If no embeddings are specified, the maps into the composite are chosen arbitrarily:: + If no embeddings are specified, the maps into the compositum + are chosen arbitrarily:: sage: Q1. = NumberField(x^4 + 10*x^2 + 1) sage: Q2. = NumberField(x^4 + 16*x^2 + 4) @@ -4112,7 +4292,8 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin To: Number Field in c with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600 Defn: a |--> 19/14400*c^7 + 137/1800*c^5 + 2599/3600*c^3 + 8/15*c - This is just one of four embeddings of Q1 into F:: + This is just one of four embeddings of ``Q1`` into ``F``:: + sage: Hom(Q1, F).order() 4 @@ -4156,6 +4337,51 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin Defn: w |--> -1/2*b - 1/2, Relative number field endomorphism of Number Field in a with defining polynomial x^3 - 5 over its base field Defn: a |--> a b |--> b, None)] + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(x^2 + 1/2) + sage: L. = NumberField(3*x^2 - 1) + sage: K.composite_fields(L) + [Number Field in ab with defining polynomial 36*x^4 + 12*x^2 + 25] + sage: C = K.composite_fields(L, both_maps=True); C + [(Number Field in ab with defining polynomial 36*x^4 + 12*x^2 + 25, + Ring morphism: + From: Number Field in a with defining polynomial x^2 + 1/2 + To: Number Field in ab with defining polynomial 36*x^4 + 12*x^2 + 25 + Defn: a |--> -3/5*ab^3 - 7/10*ab, + Ring morphism: + From: Number Field in b with defining polynomial 3*x^2 - 1 + To: Number Field in ab with defining polynomial 36*x^4 + 12*x^2 + 25 + Defn: b |--> -3/5*ab^3 + 3/10*ab, + -1)] + sage: M, f, g, k = C[0] + sage: M.gen() == g(b) + k*f(a) + True + + This also fixes the bugs reported at :trac:`14164` and + :trac:`18243`:: + + sage: R. = QQ[] + sage: f = 6*x^5 + x^4 + x^2 + 5*x + 7 + sage: r = f.roots(QQbar, multiplicities=False) + sage: F1 = NumberField(f.monic(), 'a', embedding=r[0]) + sage: F2 = NumberField(f.monic(), 'a', embedding=r[1]) + sage: (F, map1, map2, k) = F1.composite_fields(F2, both_maps=True)[0] + sage: F.degree() + 20 + sage: F.gen() == map2(F2.gen()) + k*map1(F1.gen()) + True + + sage: f = x^8 - 3*x^7 + 61/3*x^6 - 9*x^5 + 298*x^4 + 458*x^3 + 1875*x^2 + 4293*x + 3099 + sage: F1 = NumberField(f, 'z', embedding=-1.18126721294295 + 3.02858651117832j) + sage: F2 = NumberField(f, 'z', embedding=-1.18126721294295 - 3.02858651117832j) + sage: (F, map1, map2, k) = F1.composite_fields(F2, both_maps=True)[0] + sage: F.degree() + 32 + sage: F.gen() == map2(F2.gen()) + k*map1(F1.gen()) + True """ if not isinstance(other, NumberField_generic): raise TypeError("other must be a number field.") @@ -4180,9 +4406,11 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin if ambient_field is None: subfields_have_embeddings = False - f = self.pari_polynomial() - g = other.pari_polynomial() - R = self.absolute_polynomial().parent() + f = self.absolute_polynomial() + g = other.absolute_polynomial() + R = f.parent() + f = f._pari_(); f /= f.content() + g = g._pari_(); g /= g.content() m = self.degree() n = other.absolute_degree() @@ -4200,16 +4428,17 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin q = sum(1 for r in C if r.degree() != max(m, n)) if q == 1 and name != sv and name != ov: - names =[name] + names = [name] else: names = [name + str(i) for i in range(q)] i = 0 rets = [] for r in C: - if r.degree() == m: + d = r.degree() + if d == m: rets.append(self) - elif r.degree() == n: + elif d == n: rets.append(other) else: rets.append(NumberField(r, names[i], check=False)) @@ -4219,7 +4448,7 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin # If flag = 1, polcompositum outputs a vector of 4-component vectors # [R, a, b, k], where R ranges through the list of all possible compositums # as above, and a (resp. b) expresses the root of P (resp. Q) as - # an element of Q(X )/(R). Finally, k is a small integer such that + # an element of Q(X)/(R). Finally, k is a small integer such that # b + ka = X modulo R. # In this case duplicates must only be eliminated if embeddings are going # to be preserved. @@ -4236,20 +4465,20 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin # evaluating the given polynomials at the corresponding embedded values. # For the case we want, the result will be zero, but rounding errors are # difficult to predict, so we just take the field which yields the - # mimumum value. + # minimum value. if subfields_have_embeddings: poly_vals = [] for r, _, _, k in C: r = R(r) - k = ZZ(k) # essential + k = ZZ(k) embedding = other.coerce_embedding()(b) + k*self.coerce_embedding()(a) - poly_vals.append(sage.rings.complex_double.CDF(r(embedding)).abs()) + poly_vals.append(r(embedding).abs()) i = poly_vals.index(min(poly_vals)) C = [C[i]] - q = sum(1 for r, _, _, _ in C if R(r).degree() != max(m, n)) + q = sum(1 for r, _, _, _ in C if r.poldegree() != max(m, n)) if q == 1 and name != sv and name != ov: - names =[name, ''] + names = [name, ''] else: names = [name + str(i) for i in range(q + 1)] @@ -4262,51 +4491,53 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin rets = [] for r, a_in_F, b_in_F, k in C: r = R(r) - if r.degree() == m and not both_maps: + d = r.degree() + if d == m and not both_maps: rets.append(self) - elif r.degree() == n and not both_maps: + elif d == n and not both_maps: rets.append(other) else: - k = ZZ(k) # essential - + k = ZZ(k) if subfields_have_embeddings: embedding = other.coerce_embedding()(b) + k*self.coerce_embedding()(a) F = NumberField(r, names[i], check=False, embedding=embedding) i += 1 if both_maps: + a_in_F = F(R(a_in_F.lift())) + b_in_F = F(R(b_in_F.lift())) if other.is_absolute(): - if r.degree() == m: - self_to_F = self.hom([a]) - other_to_F = other.hom([(~self.hom([F(a_in_F)]))(F(b_in_F))]) + if d == m: + self_to_F = self.hom([self.gen()]) + other_to_F = other.hom([(~self.hom([a_in_F]))(b_in_F)]) F = self k = Infinity i -= 1 - elif r.degree() == n: - other_to_F = other.hom([b]) - self_to_F = self.hom([(~other.hom([F(b_in_F)]))(F(a_in_F))]) + elif d == n: + other_to_F = other.hom([other.gen()]) + self_to_F = self.hom([(~other.hom([b_in_F]))(a_in_F)]) F = other - k = ZZ(0) + k = ZZ.zero() i -= 1 else: - self_to_F = self.hom([F(a_in_F)]) - other_to_F = other.hom([F(b_in_F)]) + self_to_F = self.hom([a_in_F]) + other_to_F = other.hom([b_in_F]) else: - other_abs_to_F = other_abs.hom([F(b_in_F)]) + other_abs_to_F = other_abs.hom([b_in_F]) other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(F), other_abs_to_F*to_other_abs) - if r.degree() == m: - self_to_F = self.hom([a]) - other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(self), (~self.hom([F(a_in_F)]))*other_abs_to_F*to_other_abs) + if d == m: + self_to_F = self.hom([self.gen()]) + other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(self), (~self.hom([a_in_F]))*other_abs_to_F*to_other_abs) F = self k = None i -= 1 - elif r.degree() == n: + elif d == n: other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(other), from_other_abs) - self_to_F = self.hom([from_other_abs((~other_abs_to_F)(F(a_in_F)))]) + self_to_F = self.hom([from_other_abs((~other_abs_to_F)(a_in_F))]) F = other k = None i -= 1 else: - self_to_F = self.hom([F(a_in_F)]) + self_to_F = self.hom([a_in_F]) other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(F), other_abs_to_F*to_other_abs) rets.append( (F, self_to_F, other_to_F, k) ) else: @@ -4394,12 +4625,11 @@ def discriminant(self, v=None): INPUT: + - ``v`` -- (optional) list of elements of this number field - - ``v (optional)`` - list of element of this number - field - + OUTPUT: - OUTPUT: Integer if v is omitted, and Rational otherwise. + Integer if `v` is omitted, and Rational otherwise. EXAMPLES:: @@ -4457,17 +4687,20 @@ def trace_dual_basis(self, b): return [sum([v[i]*b[i] for i in xrange(len(b))]) for v in M.inverse()] def elements_of_norm(self, n, proof=None): - r""" - Return a list of solutions modulo units of positive norm to - `Norm(a) = n`, where a can be any integer in this number - field. + """ + Return a list of elements of norm ``n``. INPUT: + - ``n`` -- integer in this number field + + - ``proof`` -- boolean (default: ``True``, unless you called + ``number_field_proof`` and set it otherwise) - - ``proof`` - default: True, unless you called - number_field_proof and set it otherwise. + OUTPUT: + A complete system of integral elements of norm `n`, modulo + units of positive norm. EXAMPLES:: @@ -4476,10 +4709,19 @@ def elements_of_norm(self, n, proof=None): [] sage: K.elements_of_norm(50) [-7*a + 1, -5*a - 5, 7*a + 1] + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(7/9*x^3 + 7/3*x^2 - 56*x + 123) + sage: K.elements_of_norm(7) + [7/225*a^2 - 7/75*a - 42/25] """ proof = proof_flag(proof) B = self.pari_bnf(proof).bnfisintnorm(n) - return [self(_) for _ in B] + return [self(x, check=False) for x in B] def extension(self, poly, name=None, names=None, check=True, embedding=None, latex_name=None, structure=None): """ @@ -4866,10 +5108,10 @@ def _pari_integral_basis(self, v=None, important=True): - ``v`` -- None, a prime, or a list of primes. See the documentation for self.maximal_order. - - ``important`` -- (default:True) bool. If False, raise a - ``RuntimeError`` if we need to do a difficult discriminant - factorization. Useful when the integral basis is useful - but not strictly required. + - ``important`` -- boolean (default: ``True``). If ``False``, + raise a ``RuntimeError`` if we need to do a difficult + discriminant factorization. This is useful when an integral + basis is not strictly required. EXAMPLES:: @@ -5623,6 +5865,15 @@ def units(self, proof=None): a^16 - a^15 - 3*a^14 - 4*a^13 - 4*a^12 - 3*a^11 - a^10 + 2*a^9 + 4*a^8 + 5*a^7 + 4*a^6 + 2*a^5 - 2*a^4 - 6*a^3 - 9*a^2 - 9*a - 7, a^15 + a^14 + 2*a^11 + a^10 - a^9 + a^8 + 2*a^7 - a^5 + 2*a^3 - a^2 - 3*a + 1, 5*a^16 - 6*a^14 + a^13 + 7*a^12 - 2*a^11 - 7*a^10 + 4*a^9 + 7*a^8 - 6*a^7 - 7*a^6 + 8*a^5 + 6*a^4 - 11*a^3 - 5*a^2 + 13*a + 4) + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(1/2*x^2 - 1/6) + sage: K.units() + (3*a - 2,) """ proof = proof_flag(proof) @@ -5641,13 +5892,14 @@ def units(self, proof=None): # get PARI to compute the units B = self.pari_bnf(proof).bnfunit() + B = tuple(self(b, check=False) for b in B) if proof: # cache the provable results and return them - self.__units = tuple(map(self, B)) + self.__units = B return self.__units else: # cache the conjectural results and return them - self.__units_no_proof = tuple(map(self, B)) + self.__units_no_proof = B return self.__units_no_proof def unit_group(self, proof=None): @@ -5897,6 +6149,13 @@ def zeta(self, n=2, all=False): ValueError: There are no 3rd roots of unity in self. sage: K.zeta(3,all=True) [] + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(1/2*x^2 + 1/6) + sage: K.zeta(3) + -3/2*a - 1/2 """ try: return self._unit_group.zeta(n,all) @@ -5926,7 +6185,7 @@ def zeta(self, n=2, all=False): # inclusion QQ(\zeta_n) -> K. if sage.rings.arith.euler_phi(n).divides(K.absolute_degree()): # Factor the n-th cyclotomic polynomial over K. - f = K.absolute_polynomial().change_variable_name('y') + f = K.pari_polynomial('y') factors = pari.polcyclo(n).factornf(f).component(1) roots = [K(-g.polcoeff(0)) for g in factors if g.poldegree() == 1] if all: @@ -5952,6 +6211,15 @@ def zeta_order(self): sage: F. = NumberField(x**2-7) sage: F.zeta_order() 2 + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(1/2*x^2 + 1/6) + sage: K.zeta_order() + 6 """ try: return self._unit_group.zeta_order() @@ -6031,6 +6299,13 @@ def primitive_root_of_unity(self): sage: UK = K.unit_group() sage: K.primitive_root_of_unity() -1/2*f + 1/2 + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(3*x^2 + 1) + sage: K.primitive_root_of_unity() + -3/2*a + 1/2 """ try: return self._unit_group.torsion_generator().value() @@ -6043,7 +6318,7 @@ def primitive_root_of_unity(self): pK = self.pari_nf() n, z = pK.nfrootsof1() - return self(z) + return self(z, check=False) def roots_of_unity(self): """ @@ -6661,6 +6936,21 @@ def optimized_representation(self, names=None, both_maps=True): b sage: to_L(from_L(L.0)) # random a14 + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(7/9*x^3 + 7/3*x^2 - 56*x + 123) + sage: K.optimized_representation() + (Number Field in a1 with defining polynomial x^3 - 7*x - 7, + Ring morphism: + From: Number Field in a1 with defining polynomial x^3 - 7*x - 7 + To: Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + Defn: a1 |--> 7/225*a^2 - 7/75*a - 42/25, + Ring morphism: + From: Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + To: Number Field in a1 with defining polynomial x^3 - 7*x - 7 + Defn: a |--> -15/7*a1^2 + 9) """ return self.optimized_subfields(degree=self.degree(), name=names, both_maps=both_maps)[0] @@ -6711,6 +7001,47 @@ def optimized_subfields(self, degree=0, name=None, both_maps=True): sage: from_M(M.0).minpoly() # random x^4 - 5*x^2 + 25 + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^4 + 6*x^2 + 1/2) + sage: K.optimized_subfields() + [ + (Number Field in a0 with defining polynomial x - 1, Ring morphism: + From: Number Field in a0 with defining polynomial x - 1 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: 1 |--> 1, None), + (Number Field in a1 with defining polynomial x^2 - 2*x + 2, Ring morphism: + From: Number Field in a1 with defining polynomial x^2 - 2*x + 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a1 |--> a^3 + 7/2*a + 1, None), + (Number Field in a2 with defining polynomial x^2 - 2*x + 2, Ring morphism: + From: Number Field in a2 with defining polynomial x^2 - 2*x + 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a2 |--> -a^3 - 7/2*a + 1, None), + (Number Field in a3 with defining polynomial x^2 - 2, Ring morphism: + From: Number Field in a3 with defining polynomial x^2 - 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a3 |--> a^2 + 3/2, None), + (Number Field in a4 with defining polynomial x^2 + 1, Ring morphism: + From: Number Field in a4 with defining polynomial x^2 + 1 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a4 |--> a^3 + 7/2*a, None), + (Number Field in a5 with defining polynomial x^2 + 2, Ring morphism: + From: Number Field in a5 with defining polynomial x^2 + 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a5 |--> 2*a^3 + 5*a, None), + (Number Field in a6 with defining polynomial x^4 + 1, Ring morphism: + From: Number Field in a6 with defining polynomial x^4 + 1 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a6 |--> a^3 + 1/2*a^2 + 5/2*a + 3/4, Ring morphism: + From: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + To: Number Field in a6 with defining polynomial x^4 + 1 + Defn: a |--> -1/2*a6^3 + a6^2 - 1/2*a6) + ] """ return self._subfields_helper(degree=degree,name=name, both_maps=both_maps,optimize=True) @@ -6782,6 +7113,39 @@ def subfields(self, degree=0, name=None): 0 sage: len(L.subfields(1)) 1 + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^4 + 6*x^2 + 1/2) + sage: K.subfields() + [ + (Number Field in a0 with defining polynomial x, Ring morphism: + From: Number Field in a0 with defining polynomial x + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: 0 |--> 0, None), + (Number Field in a1 with defining polynomial x^2 + 4, Ring morphism: + From: Number Field in a1 with defining polynomial x^2 + 4 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a1 |--> 2*a^3 + 7*a, None), + (Number Field in a2 with defining polynomial x^2 + 2, Ring morphism: + From: Number Field in a2 with defining polynomial x^2 + 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a2 |--> 2*a^3 + 5*a, None), + (Number Field in a3 with defining polynomial x^2 - 2, Ring morphism: + From: Number Field in a3 with defining polynomial x^2 - 2 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a3 |--> a^2 + 3/2, None), + (Number Field in a4 with defining polynomial x^4 + 1, Ring morphism: + From: Number Field in a4 with defining polynomial x^4 + 1 + To: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + Defn: a4 |--> a^3 + 1/2*a^2 + 5/2*a + 3/4, Ring morphism: + From: Number Field in a with defining polynomial 2*x^4 + 6*x^2 + 1/2 + To: Number Field in a4 with defining polynomial x^4 + 1 + Defn: a |--> -1/2*a4^3 + a4^2 - 1/2*a4) + ] """ return self._subfields_helper(degree=degree, name=name, both_maps=True, optimize=False) @@ -6830,7 +7194,7 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) self.__subfields = {} except KeyError: pass - f = pari(self.polynomial()) + f = self.pari_polynomial() if optimize: v = f.polred(2) elts = v[0] @@ -6848,7 +7212,7 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) f = R(polys[i]) if not (degree == 0 or f.degree() == degree): continue - a = self(elts[i]) + a = self(elts[i], check=False) if self.coerce_embedding() is not None: embedding = self.coerce_embedding()(a) # trac 7695 add a _ to prevent zeta70 etc. @@ -6940,8 +7304,16 @@ def _maximal_order(self, v): sage: k.maximal_order() is k.maximal_order() # indirect doctest True + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(3*x^2 + 1) + sage: K.maximal_order().gens() + [3/2*a + 1/2, 3*a] """ - B = [self(_) for _ in self._pari_integral_basis(v=v)] + B = [self(b, check=False) for b in self._pari_integral_basis(v=v)] import sage.rings.number_field.order as order return order.absolute_order_from_module_generators(B, @@ -7281,6 +7653,24 @@ def automorphisms(self): sage: L = NumberField(x^24 - 84*x^22 + 2814*x^20 - 15880*x^18 - 409563*x^16 - 8543892*x^14 + 25518202*x^12 + 32831026956*x^10 - 672691027218*x^8 - 4985379093428*x^6 + 320854419319140*x^4 + 817662865724712*x^2 + 513191437605441, 'a') sage: len(L.automorphisms()) 24 + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: R. = QQ[] + sage: f = 7/9*x^3 + 7/3*x^2 - 56*x + 123 + sage: K. = NumberField(f) + sage: A = K.automorphisms(); A + [ + Ring endomorphism of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + Defn: a |--> a, + Ring endomorphism of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + Defn: a |--> -7/15*a^2 - 18/5*a + 96/5, + Ring endomorphism of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + Defn: a |--> 7/15*a^2 + 13/5*a - 111/5 + ] + sage: prod(x - sigma(a) for sigma in A) == f.monic() + True """ try: # this should be concordant with embeddings @@ -7289,11 +7679,16 @@ def automorphisms(self): self.__embeddings = {} except KeyError: pass - embs = sorted(map(self, self.pari_nf().nfgaloisconj())) - v = [ self.hom([ e ], check=False) for e in embs ] + f = self.pari_polynomial('y') + # Compute the conjugates of Mod(x, f). + conj = self.pari_nf().nfgaloisconj() + # Convert these to conjugates of self.gen(). + P = self._pari_absolute_structure()[1].lift() + conj = sorted([self(P(g.Mod(f))) for g in conj]) + v = [self.hom([e]) for e in conj] # check=False here? put_natural_embedding_first(v) self.__embeddings[self] = Sequence(v, cr=(v != []), immutable=True, - check=False, universe=self.Hom(self)) + check=False, universe=self.Hom(self)) return self.__embeddings[self] def embeddings(self, K): @@ -7653,9 +8048,9 @@ def relativize(self, alpha, names, structure=None): sage: L0 = K.relativize(rho(K0.gen()), 'b'); L0 Number Field in b0 with defining polynomial x^2 - b1 + 2 over its base field sage: L1 = K.relativize(rho, 'b'); L1 - Number Field in b0 with defining polynomial x^2 - a0 + 2 over its base field + Number Field in b with defining polynomial x^2 - a0 + 2 over its base field sage: L2 = K.relativize(tau, 'b'); L2 - Number Field in b0 with defining polynomial x^2 + a0 over its base field + Number Field in b with defining polynomial x^2 + a0 over its base field sage: L0.base_field() is K0 False sage: L1.base_field() is K0 @@ -7723,7 +8118,7 @@ def relativize(self, alpha, names, structure=None): sage: L = NumberField(x^4 + 1, 'a') sage: [L.relativize(h, 'c') for (f,h,i) in L.subfields()] - [Number Field in c0 with defining polynomial x^4 + 1 over its base field, Number Field in c0 with defining polynomial x^2 - 1/2*a1 over its base field, Number Field in c0 with defining polynomial x^2 - a2*x - 1 over its base field, Number Field in c0 with defining polynomial x^2 - a3*x + 1 over its base field, Number Field in c0 with defining polynomial x - a4 over its base field] + [Number Field in c with defining polynomial x^4 + 1 over its base field, Number Field in c with defining polynomial x^2 - 1/2*a1 over its base field, Number Field in c with defining polynomial x^2 - a2*x - 1 over its base field, Number Field in c with defining polynomial x^2 - a3*x + 1 over its base field, Number Field in c with defining polynomial x - a4 over its base field] We can relativize over a relative field:: @@ -7734,23 +8129,23 @@ def relativize(self, alpha, names, structure=None): Number Field in z0_0 with defining polynomial x^2 + 64 sage: L_over_F = L.relativize(F_into_L, 'c'); L_over_F - Number Field in c0 with defining polynomial x^2 - 1/2*z0_0 over its base field + Number Field in c with defining polynomial x^2 - 1/2*z0_0 over its base field sage: L_over_F_into_L, _ = L_over_F.structure() sage: K_over_rel = K.relativize(L_into_K * L_over_F_into_L, 'a'); K_over_rel - Number Field in a0 with defining polynomial x^2 - 1/2*c0 over its base field + Number Field in a with defining polynomial x^2 - 1/2*c over its base field sage: K_over_rel.base_field() is L_over_F True sage: K_over_rel.structure() (Relative number field morphism: - From: Number Field in a0 with defining polynomial x^2 - 1/2*c0 over its base field + From: Number Field in a with defining polynomial x^2 - 1/2*c over its base field To: Cyclotomic Field of order 16 and degree 8 - Defn: a0 |--> z - c0 |--> 2*z^2 + Defn: a |--> z + c |--> 2*z^2 z0_0 |--> 8*z^4, Ring morphism: From: Cyclotomic Field of order 16 and degree 8 - To: Number Field in a0 with defining polynomial x^2 - 1/2*c0 over its base field - Defn: z |--> a0) + To: Number Field in a with defining polynomial x^2 - 1/2*c over its base field + Defn: z |--> a) We can relativize over a really large field:: @@ -7767,14 +8162,24 @@ def relativize(self, alpha, names, structure=None): From: Cyclotomic Field of order 216 and degree 72 To: Number Field in t0 with defining polynomial x^9 - t1 over its base field Defn: a |--> t0) + + Only one name is required when a morphism is given (fixing :trac:`12005`):: + + sage: R. = PolynomialRing(QQ) + sage: K. = NumberField(x^2 + 1) + sage: L. = NumberField(x^4 - x^2 + 1) + sage: phi = K.hom(b^3, L) + sage: M. = L.relativize(phi) + sage: M + Number Field in r with defining polynomial x^2 - i*x - 1 over its base field + sage: M.base_field() + Number Field in i with defining polynomial x^2 + 1 """ # step 1: construct the abstract field generated by alpha.w # step 2: make a relative extension of it. # step 3: construct isomorphisms from sage.all import vector, matrix - names = sage.structure.parent_gens.normalize_names(2, names) - from sage.categories.map import is_Map if is_Map(alpha): # alpha better be a morphism with codomain self @@ -7787,10 +8192,12 @@ def relativize(self, alpha, names, structure=None): f = polygen(QQ) else: f = L.defining_polynomial() # = alpha.minpoly() + names = sage.structure.parent_gens.normalize_names(1, names) else: # alpha must be an element coercible to self alpha = self(alpha) f = alpha.minpoly() + names = sage.structure.parent_gens.normalize_names(2, names) L = NumberField(f, names[1]) # now we do some linear algebra to find the minpoly of self.gen() over L @@ -8833,7 +9240,7 @@ def _log_gen(self, x): return e gen_pow_e *= gen - def _element_constructor_(self, x): + def _element_constructor_(self, x, check=True): """ Create an element of this cyclotomic field from `x`. @@ -8886,6 +9293,8 @@ def _element_constructor_(self, x): return self._coerce_from_other_cyclotomic_field(x) else: return NumberField_absolute._element_constructor_(self, x) + elif isinstance(x, pari_gen): + return NumberField_absolute._element_constructor_(self, x, check=check) elif (sage.interfaces.gap.is_GapElement(x) or isinstance(x, sage.libs.gap.element.GapElement)): return self._coerce_from_gap(x) @@ -9424,9 +9833,9 @@ def _pari_integral_basis(self, v=None, important=True): Internal function returning an integral basis of this number field in PARI format. - This field is cyclomotic, so this is a trivial computation, since - the power basis on the generator is an integral basis. Thus the ``v`` - and ``important`` parameters are ignored. + This field is cyclotomic, so this is a trivial computation, + since the power basis on the generator is an integral basis. + Thus the ``v`` and ``important`` parameters are ignored. EXAMPLES:: diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 88e74c629f4..030c8d4dda5 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -37,7 +37,6 @@ include "sage/ext/stdsage.pxi" from sage.libs.gmp.mpz cimport * from sage.libs.gmp.mpq cimport * -import sage.rings.field_element import sage.rings.infinity import sage.rings.polynomial.polynomial_element import sage.rings.rational_field @@ -56,9 +55,13 @@ from sage.categories.fields import Fields from sage.modules.free_module_element import vector from sage.libs.pari.all import pari_gen -from sage.structure.element cimport Element, generic_power_c +from sage.libs.pari.pari_instance cimport PariInstance + +from sage.structure.element cimport Element, generic_power_c, FieldElement from sage.structure.element import canonical_coercion, parent, coerce_binop +cdef PariInstance pari = sage.libs.pari.pari_instance.pari + QQ = sage.rings.rational_field.QQ ZZ = sage.rings.integer_ring.ZZ Integer_sage = sage.rings.integer.Integer @@ -253,48 +256,14 @@ cdef class NumberFieldElement(FieldElement): sage: (b^2 + b + 1)^3 12*b^2 + 15*b + 19 - We can create number field elements from PARI:: - - sage: K. = NumberField(x^3 - 17) - sage: K(pari(42)) - 42 - sage: K(pari("5/3")) - 5/3 - sage: K(pari("[3/2, -5, 0]~")) # Uses Z-basis - -5/3*a^2 + 5/3*a - 1/6 - - From a PARI polynomial or ``POLMOD``, note that the variable - name does not matter:: - - sage: K(pari("-5/3*q^2 + 5/3*q - 1/6")) - -5/3*a^2 + 5/3*a - 1/6 - sage: K(pari("Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 17)")) - -5/3*a^2 + 5/3*a - 1/6 - sage: K(pari("x^5/17")) - a^2 - sage: K(pari("Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 999)")) # Wrong modulus - Traceback (most recent call last): - ... - TypeError: Coercion of PARI polmod with modulus q^3 - 999 into number field with defining polynomial x^3 - 17 failed - This example illustrates save and load:: sage: K. = NumberField(x^17 - 2) sage: s = a^15 - 19*a + 3 sage: loads(s.dumps()) == s True - - TESTS: - - Test round-trip conversion to PARI and back:: - - sage: x = polygen(QQ) - sage: K. = NumberField(x^3 - 1/2*x + 1/3) - sage: b = K.random_element() - sage: K(pari(b)) == b - True """ - sage.rings.field_element.FieldElement.__init__(self, parent) + FieldElement.__init__(self, parent) self.__fld_numerator, self.__fld_denominator = parent.absolute_polynomial_ntl() cdef ZZ_c coeff @@ -305,7 +274,6 @@ cdef class NumberFieldElement(FieldElement): ZZX_SetCoeff( self.__numerator, 0, coeff ) ZZ_conv_from_int( self.__denominator, 1 ) return - elif isinstance(f, NumberFieldElement): if type(self) is type(f): self.__numerator = (f).__numerator @@ -314,55 +282,14 @@ cdef class NumberFieldElement(FieldElement): else: f = f.polynomial() - from sage.rings.number_field import number_field_rel - if isinstance(parent, number_field_rel.NumberField_relative): - ppr = parent.base_field().polynomial_ring() - else: - ppr = parent.polynomial_ring() + modulus = parent.absolute_polynomial() + f = modulus.parent()(f) + if f.degree() >= modulus.degree(): + f %= modulus cdef long i - if isinstance(f, pari_gen): - if f.type() in ["t_INT", "t_FRAC", "t_POL"]: - pass - elif f.type() == "t_POLMOD": - # Check whether we are dealing with a *relative* - # number field element - if parent.is_relative(): - # If the modulus is a polynomial with polynomial - # coefficients, then the element is relative. - fmod = f.mod() - for i from 0 <= i <= fmod.poldegree(): - if fmod.polcoeff(i).type() in ["t_POL", "t_POLMOD"]: - # Convert relative element to absolute. - # Sometimes the result is a polynomial, - # sometimed a polmod. Lift to convert to a - # polynomial in all cases. - f = parent.pari_rnf().rnfeltreltoabs(f).lift() - break - # Check that the modulus is actually the defining polynomial - # of the number field. - # Unfortunately, this check only works for absolute elements - # since the rnfeltreltoabs() destroys all information about - # the number field. - if f.type() == "t_POLMOD": - fmod = f.mod() - if fmod != parent.pari_polynomial(fmod.variable()): - raise TypeError("Coercion of PARI polmod with modulus %s into number field with defining polynomial %s failed"%(fmod, parent.pari_polynomial())) - f = f.lift() - else: - f = parent.pari_nf().nfbasistoalg_lift(f) - f = ppr(f) - if f.degree() >= parent.absolute_degree(): - from sage.rings.number_field import number_field_rel - if isinstance(parent, number_field_rel.NumberField_relative): - f %= ppr(parent.absolute_polynomial()) - else: - f %= parent.polynomial() - - # Set Denominator den = f.denominator() (ZZ(den))._to_ZZ(&self.__denominator) - num = f * den for i from 0 <= i <= num.degree(): (ZZ(num[i]))._to_ZZ(&coeff) @@ -581,6 +508,28 @@ cdef class NumberFieldElement(FieldElement): En = libgap(self.parent()).GeneratorsOfField()[0] return self.polynomial()(En) + def _pari_polynomial(self, name='y'): + """ + Return a PARI polynomial representing ``self``. + + TESTS: + + sage: K. = NumberField(x^3 + 2) + sage: K.zero()._pari_polynomial('x') + 0 + sage: K.one()._pari_polynomial() + 1 + sage: (a + 1)._pari_polynomial() + y + 1 + sage: a._pari_polynomial('c') + c + """ + f = pari(self._coefficients()).Polrev() + if f.poldegree() > 0: + alpha = self.number_field()._pari_absolute_structure()[1] + f = f(alpha).lift() + return f.change_variable_name(name) + def _pari_(self, name='y'): r""" Return PARI representation of self. @@ -674,17 +623,9 @@ cdef class NumberFieldElement(FieldElement): sage: c._pari_('c') Mod(c, c^12 + 2) """ - try: - return self.__pari[name] - except KeyError: - pass - except TypeError: - self.__pari = {} - f = self.polynomial()._pari_or_constant(name) + f = self._pari_polynomial(name) g = self.number_field().pari_polynomial(name) - h = f.Mod(g) - self.__pari[name] = h - return h + return f.Mod(g) def _pari_init_(self, name='y'): """ @@ -768,7 +709,7 @@ cdef class NumberFieldElement(FieldElement): raise IndexError, "index must be between 0 and degree minus 1." return self.polynomial()[n] - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: r""" EXAMPLE:: @@ -778,9 +719,6 @@ cdef class NumberFieldElement(FieldElement): sage: a + 1 < a # indirect doctest False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef NumberFieldElement _right = right return not (ZZX_equal(left.__numerator, _right.__numerator) and ZZ_equal(left.__denominator, _right.__denominator)) @@ -1234,7 +1172,7 @@ cdef class NumberFieldElement(FieldElement): from sage.rings.number_field.number_field import is_AbsoluteNumberField if is_AbsoluteNumberField(L): - Lrel = L.relativize(K.hom(L), ('a', 'b')) + Lrel = L.relativize(K.hom(L), (L.variable_name()+'0', K.variable_name()+'0') ) b, x = self.is_norm(Lrel, element=True, proof=proof) h = Lrel.structure()[0] return b, h(x) @@ -1334,6 +1272,16 @@ cdef class NumberFieldElement(FieldElement): sage: s == t[0].norm(K)*t[1] True + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(x^2 + 1/2) + sage: L. = K.extension(x^2 - 1/2) + sage: a._rnfisnorm(L) + (a*b + a + 1/2, 1) + AUTHORS: - Craig Citro (2008-04-05) @@ -1349,10 +1297,7 @@ cdef class NumberFieldElement(FieldElement): rnf_data = K.pari_rnfnorm_data(L, proof=proof) x, q = self._pari_().rnfisnorm(rnf_data) - - # Convert x to an absolute element - x = L.pari_rnf().rnfeltreltoabs(x) - return L(x), K(q) + return L(x, check=False), K(q, check=False) def _mpfr_(self, R): """ @@ -2296,7 +2241,7 @@ cdef class NumberFieldElement(FieldElement): sage: SR(b.minpoly()).solve(SR('x'), explicit_solutions=True) [] sage: SR(b) - 1/8*((sqrt(4*(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) - 4/3/(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) + 17) + 5)^2 + 4)*(sqrt(4*(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) - 4/3/(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) + 17) + 5) + 1/8*(sqrt(4*(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) - 4/3/(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) + 17) + 5)^3 + 1/2*sqrt(4*(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) - 4/3/(1/9*sqrt(109)*sqrt(3) + 2)^(1/3) + 17) + 5/2 """ K = self._parent.fraction_field() @@ -3537,8 +3482,7 @@ cdef class NumberFieldElement(FieldElement): raise ValueError, "codomain of phi must be parent of self" # Construct a relative extension over L (= QQ(beta)) - M = K.relativize(beta, ('a','b')) - # variable name a is OK, since this is temporary + M = K.relativize(beta, (K.variable_name()+'0', L.variable_name()+'0') ) # Carry self over to M. from_M, to_M = M.structure() @@ -3744,7 +3688,7 @@ cdef class NumberFieldElement(FieldElement): if len(embs) == 0: raise ValueError("K = %s does not embed into %s" % (K,L)) f = embs[0] - LK = L.relativize(f, names='b') + LK = L.relativize(f, L.variable_name()+'0') # Unfortunately the base field of LK is not K but an # isomorphic field, and we must make sure to use the correct # isomorphism! @@ -4053,38 +3997,6 @@ cdef class NumberFieldElement_relative(NumberFieldElement): sage: L. = NumberField([x^2 + 1, x^2 + 2]) sage: type(a) # indirect doctest - - We can create relative number field elements from PARI:: - - sage: y = polygen(QQ) - sage: K. = NumberField(y^2 + y + 1) - sage: x = polygen(K) - sage: L. = NumberField(x^4 + a*x + 2) - sage: e = (a*b)._pari_('x'); e - Mod(-x^4 - 2, x^8 - x^5 + 4*x^4 + x^2 - 2*x + 4) - sage: L(e) # Conversion from PARI absolute number field element - a*b - sage: e = L.pari_rnf().rnfeltabstorel(e); e - Mod(Mod(y, y^2 + y + 1)*x, x^4 + Mod(y, y^2 + y + 1)*x + 2) - sage: L(e) # Conversion from PARI relative number field element - a*b - sage: e = pari('Mod(0, x^8 + 1)'); L(e) # Wrong modulus - Traceback (most recent call last): - ... - TypeError: Coercion of PARI polmod with modulus x^8 + 1 into number field with defining polynomial x^8 - x^5 + 4*x^4 + x^2 - 2*x + 4 failed - - We test a relative number field element created "by hand":: - - sage: e = pari("Mod(Mod(y, y^2 + y + 1)*x^2 + Mod(1, y^2 + y + 1), x^4 + y*x + 2)") - sage: L(e) - a*b^2 + 1 - - This wrong modulus yields a PARI error:: - - sage: e = pari('Mod(y*x, x^4 + y^2*x + 2)'); L(e) - Traceback (most recent call last): - ... - PariError: inconsistent moduli in rnfeltreltoabs: x^4 + y^2*x + 2 != y^2 + y + 1 """ NumberFieldElement.__init__(self, parent, f) diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index 223ed701c1b..1d8ebcf6c2e 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -658,22 +658,15 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): return test return -test - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element _right, int op): r""" - Note: we may implement a more direct way of comparison for integer, - float and quadratic numbers input (ie avoiding coercion). + Rich comparison of elements. TESTS:: sage: K. = QuadraticField(-1) sage: sorted([5*i+1, 2, 3*i+1, 2-i]) [3*i + 1, 5*i + 1, -i + 2, 2] - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element _right, int op): - r""" - C implementation of comparison. TESTS: @@ -796,8 +789,8 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): mpz_clear(j) return rich_to_bool_sgn(op, test) - def __cmp__(left, right): - r""" + cpdef int _cmp_(left, Element _right) except -2: + """ Comparisons of elements. When there is a real embedding defined, the comparisons uses comparison @@ -869,12 +862,6 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: map(CDF, l) == sorted(map(CDF, l)) True """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element _right) except -2: - """ - C implementation of comparison. - """ cdef NumberFieldElement_quadratic right = _right cdef int test diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index ddf6e44ac4a..05f8c8a6f54 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -93,7 +93,7 @@ def convert_from_idealprimedec_form(field, ideal): deprecation(15767, "convert_from_idealprimedec_form() is deprecated") p = ZZ(ideal[1]) - alpha = field(field.pari_zk() * ideal[2]) + alpha = field(field.pari_zk() * ideal[2], check=False) return field.ideal(p, alpha) def convert_to_idealprimedec_form(field, ideal): @@ -166,6 +166,15 @@ def __init__(self, field, gens, coerce=True): sage: K.ideal(pari(K).idealprimedec(5)[0])._pari_prime [5, [-2, 1]~, 1, 1, [2, -1; 1, 2]] + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: I = K.ideal(a); I + Fractional ideal (a) + sage: I.norm() + 1/6 """ if not isinstance(field, number_field.NumberField_generic): raise TypeError("field (=%s) must be a number field."%field) @@ -178,14 +187,14 @@ def __init__(self, field, gens, coerce=True): gens = gens[0] if gens.type() == "t_MAT": # Assume columns are generators - gens = [field(_) for _ in field.pari_zk() * gens] + gens = [field(x, check=False) for x in field.pari_zk() * gens] elif gens.type() == "t_VEC": # Assume prime ideal form self._pari_prime = gens gens = [ZZ(gens.pr_get_p()), field(gens.pr_get_gen())] else: # Assume one element of the field - gens = [field(gens)] + gens = [field(gens, check=False)] if len(gens)==0: raise ValueError("gens must have length at least 1 (zero ideal is not a fractional ideal)") Ideal_generic.__init__(self, field, gens, coerce) @@ -461,7 +470,7 @@ def __elements_from_hnf(self, hnf): [1/17, 1/17*a, a^2 - 8/17*a - 13/17] """ K = self.number_field() - return [K(_) for _ in K.pari_zk() * hnf] + return [K(x, check=False) for x in K.pari_zk() * hnf] def __repr__(self): """ @@ -624,12 +633,12 @@ def pari_hnf(self): @cached_method def basis(self): r""" - Return an immutable sequence of elements of this ideal (note: - their parent is the number field) that form a basis for this - ideal viewed as a `\ZZ` -module. + Return a basis for this ideal viewed as a `\ZZ` -module. OUTPUT: - basis -- an immutable sequence. + + An immutable sequence of elements of this ideal (note: their + parent is the number field) forming a basis for this ideal. EXAMPLES:: @@ -645,10 +654,16 @@ def basis(self): Fractional ideal (2/11*z^5 + 2/11*z^4 + 3/11*z^3 + 2/11) sage: J.basis() # warning -- choice of basis can be somewhat random [1, z, z^2, 1/11*z^3 + 7/11*z^2 + 6/11*z + 10/11, 1/11*z^4 + 1/11*z^2 + 1/11*z + 7/11, 1/11*z^5 + 1/11*z^4 + 1/11*z^3 + 2/11*z^2 + 8/11*z + 7/11] + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: K.ideal(a).basis() + [1, a] """ hnf = self.pari_hnf() v = self.__elements_from_hnf(hnf) - O = self.number_field().maximal_order() return Sequence(v, immutable=True) @cached_method @@ -1846,6 +1861,17 @@ def factor(self): [(Fractional ideal (19, 1/2*a^2 + a - 17/2), 1), (Fractional ideal (19, 1/2*a^2 - a - 17/2), 1)] sage: F.prod() Fractional ideal (19) + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: F. = NumberField(2*x^3 + x + 1) + sage: fact = F.factor(2); fact + (Fractional ideal (2*a^2 + 1))^2 * (Fractional ideal (-2*a^2)) + sage: [p[0].norm() for p in fact] + [2, 2] """ try: return self.__factorization diff --git a/src/sage/rings/number_field/number_field_ideal_rel.py b/src/sage/rings/number_field/number_field_ideal_rel.py index 0147b2e0ea2..2939c09b1fe 100644 --- a/src/sage/rings/number_field/number_field_ideal_rel.py +++ b/src/sage/rings/number_field/number_field_ideal_rel.py @@ -258,6 +258,17 @@ def gens_reduced(self): sage: I = K.ideal((a + 1)*b/2 + 1) sage: I.gens_reduced() (1/2*b*a + 1/2*b + 1,) + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: L. = K.extension(5*x^2 + 1) + sage: P = L.primes_above(2)[0] + sage: P.gens_reduced() + (2, 15*a*b + 3*a + 1) """ try: ## Compute the single generator, if it exists @@ -265,12 +276,10 @@ def gens_reduced(self): return self.__reduced_generators except AttributeError: L = self.number_field() - K = L.base_field() - R = L.relative_polynomial().parent() gens = L.pari_rnf().rnfidealtwoelt(self.pari_rhnf()) - gens = [ L(R(x.lift().lift())) for x in gens ] + gens = [L(x, check=False) for x in gens] - # pari always returns two elements, even if only one is needed! + # PARI always returns two elements, even if only one is needed! if gens[1] in L.ideal(gens[0]): gens = gens[:1] elif gens[0] in L.ideal(gens[1]): @@ -380,13 +389,22 @@ def relative_norm(self): 815730721 sage: K.ideal(13).absolute_norm() 815730721 + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: L. = K.extension(5*x^2 + 1) + sage: P = L.primes_above(2)[0] + sage: P.relative_norm() + Fractional ideal (-6*a + 2) """ L = self.number_field() K = L.base_field() K_abs = K.absolute_field('a') to_K = K_abs.structure()[0] hnf = L.pari_rnf().rnfidealnormrel(self.pari_rhnf()) - return K.ideal([to_K(K_abs(_)) for _ in K.pari_zk() * hnf]) + return K.ideal([to_K(K_abs(x, check=False)) for x in K.pari_zk() * hnf]) def norm(self): """ @@ -435,7 +453,7 @@ def ideal_below(self): sage: K0 Number Field in a0 with defining polynomial x^2 - 6*x + 24 sage: L = K.relativize(K0_into_K, 'c'); L - Number Field in c0 with defining polynomial x^2 + a0 over its base field + Number Field in c with defining polynomial x^2 + a0 over its base field sage: L.base_field() is K0 True sage: L.ideal(7) @@ -487,13 +505,22 @@ def ideal_below(self): Fractional ideal (b) sage: J.number_field() == F True + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(2*x^2 - 1/3) + sage: L. = K.extension(5*x^2 + 1) + sage: P = L.primes_above(2)[0] + sage: P.ideal_below() + Fractional ideal (-6*a + 2) """ L = self.number_field() K = L.base_field() K_abs = K.absolute_field('a') to_K = K_abs.structure()[0] hnf = L.pari_rnf().rnfidealdown(self.pari_rhnf()) - return K.ideal([to_K(K_abs(_)) for _ in K.pari_zk() * hnf]) + return K.ideal([to_K(K_abs(x, check=False)) for x in K.pari_zk() * hnf]) def factor(self): """ diff --git a/src/sage/rings/number_field/number_field_rel.py b/src/sage/rings/number_field/number_field_rel.py index ba11e9aa98d..f90dfad51b9 100644 --- a/src/sage/rings/number_field/number_field_rel.py +++ b/src/sage/rings/number_field/number_field_rel.py @@ -200,23 +200,26 @@ def __init__(self, base, polynomial, name, sage: l.base_field().base_field() Number Field in a1 with defining polynomial x^2 + 1 - The polynomial must be monic (cf. :trac:`252`):: + Non-monic and non-integral polynomials are supported (:trac:`252`):: - sage: l. = k.extension(5*x^2 + 3) - Traceback (most recent call last): - ... - NotImplementedError: Number fields for non-monic polynomials not yet implemented. - - The polynomial must have integral coefficients (cf. :trac:`252`):: - - sage: l. = k.extension(x^2 + 3/5) - doctest:...: UserWarning: PARI only handles integral absolute polynomials. Computations in this field might trigger PARI errors + sage: l. = k.extension(5*x^2 + 3); l + Number Field in b with defining polynomial 5*x^2 + 3 over its base field + sage: l.pari_rnf() + [x^2 + (-1/2*y^2 + y - 3/2)*x + (-1/4*y^3 + 1/4*y^2 - 3/4*y - 13/4), ..., y^4 + 6*y^2 + 1, x^2 + (-1/2*y^2 + y - 3/2)*x + (-1/4*y^3 + 1/4*y^2 - 3/4*y - 13/4)], [0]] sage: b - ) failed: PariError: incorrect type in core2partial (t_FRAC)> + b - However, if the polynomial is linear, rational coefficients should work:: + sage: l. = k.extension(x^2 + 3/5); l + Number Field in b with defining polynomial x^2 + 3/5 over its base field + sage: l.pari_rnf() + [x^2 + (-1/2*y^2 + y - 3/2)*x + (-1/4*y^3 + 1/4*y^2 - 3/4*y - 13/4), ..., y^4 + 6*y^2 + 1, x^2 + (-1/2*y^2 + y - 3/2)*x + (-1/4*y^3 + 1/4*y^2 - 3/4*y - 13/4)], [0]] + sage: b + b - sage: l. = k.extension(x - 1/a0) + sage: l. = k.extension(x - 1/a0); l + Number Field in b with defining polynomial x + 1/2*a0 over its base field + sage: l.pari_rnf() + [x, [[4, -x^3 - x^2 - 7*x - 3, -x^3 + x^2 - 7*x + 3, 2*x^3 + 10*x], 1/4], ..., [x^4 + 6*x^2 + 1, -x, -1, y^4 + 6*y^2 + 1, x], [0]] sage: b -1/2*a0 @@ -246,6 +249,15 @@ def __init__(self, base, polynomial, name, Traceback (most recent call last): ... ValueError: defining polynomial (x^2 + 2) must be irreducible + + Error checks:: + + sage: x = polygen(ZZ) + sage: K. = NumberField(x^2 + 1) + sage: K.extension(x^2 + 2, 'a') + Traceback (most recent call last): + ... + ValueError: base field and extension cannot have the same name 'a' """ if embedding is not None: raise NotImplementedError("Embeddings not implemented for relative number fields") @@ -258,12 +270,10 @@ def __init__(self, base, polynomial, name, except (AttributeError, TypeError) as msg: raise TypeError("polynomial (=%s) must be a polynomial."%repr(polynomial)) if name == base.variable_name(): - raise ValueError("Base field and extension cannot have the same name") + raise ValueError("base field and extension cannot have the same name %r" % name) if polynomial.parent().base_ring() != base: polynomial = polynomial.change_ring(base) #raise ValueError, "The polynomial must be defined over the base field" - if not polynomial.is_monic(): - raise NotImplementedError("Number fields for non-monic polynomials not yet implemented.") # Generate the nf and bnf corresponding to the base field # defined as polynomials in y, e.g. for rnfisfree @@ -308,10 +318,6 @@ def __init__(self, base, polynomial, name, self.__gens = tuple(v) self._zero_element = self(0) self._one_element = self(1) - # check that we won't make PARI unhappy later on - abs_p = self.absolute_polynomial() - if polynomial.degree() > 1 and abs_p not in PolynomialRing( ZZ, abs_p.variable_name() ): - warn("PARI only handles integral absolute polynomials. Computations in this field might trigger PARI errors") def change_names(self, names): r""" @@ -391,30 +397,30 @@ def subfields(self, degree=0, name=None): sage: K. = F.extension(Y^2 - (1 + a)*(a + b)*a*b) sage: K.subfields(2) [ - (Number Field in c0 with defining polynomial x^2 - 48*x + 288, Ring morphism: - From: Number Field in c0 with defining polynomial x^2 - 48*x + 288 + (Number Field in c0 with defining polynomial x^2 - 24*x + 72, Ring morphism: + From: Number Field in c0 with defining polynomial x^2 - 24*x + 72 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: c0 |--> 12*a + 24, None), - (Number Field in c1 with defining polynomial x^2 - 48*x + 192, Ring morphism: - From: Number Field in c1 with defining polynomial x^2 - 48*x + 192 + Defn: c0 |--> -6*a + 12, None), + (Number Field in c1 with defining polynomial x^2 - 24*x + 120, Ring morphism: + From: Number Field in c1 with defining polynomial x^2 - 24*x + 120 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: c1 |--> 8*b*a + 24, None), - (Number Field in c2 with defining polynomial x^2 - 48*x + 384, Ring morphism: - From: Number Field in c2 with defining polynomial x^2 - 48*x + 384 + Defn: c1 |--> 2*b*a + 12, None), + (Number Field in c2 with defining polynomial x^2 - 24*x + 96, Ring morphism: + From: Number Field in c2 with defining polynomial x^2 - 24*x + 96 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: c2 |--> 8*b + 24, None) + Defn: c2 |--> -4*b + 12, None) ] sage: K.subfields(8, 'w') [ - (Number Field in w0 with defining polynomial x^8 - 24*x^6 + 108*x^4 - 144*x^2 + 36, Ring morphism: - From: Number Field in w0 with defining polynomial x^8 - 24*x^6 + 108*x^4 - 144*x^2 + 36 + (Number Field in w0 with defining polynomial x^8 - 12*x^6 + 36*x^4 - 36*x^2 + 9, Ring morphism: + From: Number Field in w0 with defining polynomial x^8 - 12*x^6 + 36*x^4 - 36*x^2 + 9 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: w0 |--> c, Relative number field morphism: + Defn: w0 |--> (-1/2*b*a + 1/2*b + 1/2)*c, Relative number field morphism: From: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - To: Number Field in w0 with defining polynomial x^8 - 24*x^6 + 108*x^4 - 144*x^2 + 36 - Defn: c |--> w0 - a |--> 1/12*w0^6 - 11/6*w0^4 + 11/2*w0^2 - 3 - b |--> -1/24*w0^6 + w0^4 - 17/4*w0^2 + 3) + To: Number Field in w0 with defining polynomial x^8 - 12*x^6 + 36*x^4 - 36*x^2 + 9 + Defn: c |--> -1/3*w0^7 + 4*w0^5 - 12*w0^3 + 11*w0 + a |--> 1/3*w0^6 - 10/3*w0^4 + 5*w0^2 + b |--> -2/3*w0^6 + 7*w0^4 - 14*w0^2 + 6) ] sage: K.subfields(3) [] @@ -975,7 +981,7 @@ def _coerce_non_number_field_element_in(self, x): sage: L(a) a sage: L(a).polynomial() - -2*x + -x sage: L(a).minpoly() x - a sage: L(a).absolute_minpoly() @@ -983,7 +989,7 @@ def _coerce_non_number_field_element_in(self, x): sage: L(b) -1/2*a sage: L(b).polynomial() - x + 1/2*x sage: L(b).absolute_minpoly() x^5 - 1/16 sage: L(b).minpoly() @@ -1052,38 +1058,57 @@ def _coerce_map_from_(self, R): if mor is not None: return self._internal_coerce_map_from(self.base_field()) * mor - def _rnfeltreltoabs(self, element, check=False): - r""" - Return PARI's ``rnfeltreltoabs()``, but without requiring - ``rnfinit()``. + def _element_constructor_(self, x, check=True): + """ + Construct a relative number field element from ``x``. - TESTS:: + EXAMPLES: - sage: x = polygen(ZZ) - sage: K. = NumberField(x^2 + 2) + We can create relative number field elements from PARI:: + + sage: y = polygen(QQ) + sage: K. = NumberField(y^2 + y + 1) sage: x = polygen(K) - sage: L. = K.extension(x^3 + 3*a) - sage: M. = L.extension(x^2 + 5*b) - - sage: L._rnfeltreltoabs(b^2 + a, check=True) - -2/9*x^3 - 2 - sage: M._rnfeltreltoabs(c*b, check=True) - 1/625*x^6 - """ - z, a, k = self._pari_rnfequation() - # If the relative extension is L/K, then z is the absolute - # polynomial for L, a is a polynomial giving the absolute - # generator alpha of K as a polynomial in a root of z, and k - # is a small integer such that - # theta = beta + k*alpha, - # where theta is a root of z and beta is a root of the - # relative polynomial for L/K. - pol = element.polynomial('y') - t2 = pol(a).lift() - if check: - t1 = self.pari_rnf().rnfeltreltoabs(pol).lift() - assert t1 == t2 - return t2 + sage: L. = NumberField(x^4 + a*x + 2) + sage: e = a._pari_(); e + Mod(y, y^2 + y + 1) + sage: L(e) # Conversion from PARI base field element + a + sage: e = (a*b)._pari_('x'); e + Mod(-x^4 - 2, x^8 - x^5 + 4*x^4 + x^2 - 2*x + 4) + sage: L(e) # Conversion from PARI absolute number field element + a*b + sage: e = L.pari_rnf().rnfeltabstorel(e); e + Mod(Mod(y, y^2 + y + 1)*x, x^4 + Mod(y, y^2 + y + 1)*x + 2) + sage: L(e) # Conversion from PARI relative number field element + a*b + sage: e = pari('Mod(0, x^8 + 1)'); L(e) # Wrong modulus + Traceback (most recent call last): + ... + TypeError: cannot convert PARI element Mod(0, x^8 + 1) into Number Field in b with defining polynomial x^4 + a*x + 2 over its base field + + We test a relative number field element created "by hand":: + + sage: e = pari("Mod(Mod(y, y^2 + y + 1)*x^2 + Mod(1, y^2 + y + 1), x^4 + y*x + 2)") + sage: L(e) + a*b^2 + 1 + + A wrong modulus yields an error:: + + sage: e = pari('Mod(y*x, x^4 + y^2*x + 2)'); L(e) + Traceback (most recent call last): + ... + TypeError: cannot convert PARI element Mod(y*x, x^4 + y^2*x + 2) into Number Field in b with defining polynomial x^4 + a*x + 2 over its base field + """ + # If x is a *relative* PARI number field element, convert it + # to an absolute element. + if isinstance(x, pari_gen) and x.type() == "t_POLMOD": + modulus = x.mod() + if (modulus == self.pari_relative_polynomial() + or modulus == self.pari_absolute_base_polynomial()): + x = self._pari_rnfeq()._eltreltoabs(x.liftpol()) + check = False + return NumberField_generic._element_constructor_(self, x, check=check) def __base_inclusion(self, element): """ @@ -1127,14 +1152,12 @@ def __base_inclusion(self, element): # Write element in terms of the absolute base field element = self.base_field().coerce(element) element = to_abs_base(element) - # Find an expression in terms of the absolute generator for self of element. - expr_x = self._rnfeltreltoabs(element) - # Convert to a Sage polynomial, then to one in gen(), and return it - R = self.polynomial_ring() + # Express element as a polynomial in the absolute generator of self + expr_x = self._pari_rnfeq()._eltreltoabs(element._pari_polynomial()) # We do NOT call self(...) because this code is called by # __init__ before we initialize self.gens(), and self(...) # uses self.gens() - return self._element_class(self, R(expr_x)) + return self._element_constructor_(expr_x, check=False) def _fractional_ideal_class_(self): """ @@ -1171,15 +1194,13 @@ def _pari_base_bnf(self, proof=False, units=True): abs_base, from_abs_base, to_abs_base = self.absolute_base_field() return abs_base.pari_bnf(proof, units) - @cached_method def _pari_base_nf(self): r""" Return the PARI number field representation of the absolute base field, in terms of the pari variable ``y``, suitable for extension by the pari variable ``x``. - In future, all caching will be done by the absolute base - field. + All caching is done by the absolute base field. EXAMPLES:: @@ -1492,18 +1513,24 @@ def absolute_base_field(self): return self.__absolute_base_field @cached_method - def _pari_rnfequation(self): - r""" - Internal helper that calls PARI's rnfequation for self without - first initializing any PARI structures. + def _pari_rnfeq(self): + """ + Return PARI data attached to this relative number field. + + OUTPUT: + + A 5-element PARI vector containing an absolute polynomial and + further data needed for ``eltabstorel`` and ``eltreltoabs``, + obtained from PARI's ``nf_rnfeq`` function. This means that + we do not have to initialize a full PARI ``rnf`` structure. TESTS:: sage: K. = NumberField(x^2 + 2) sage: x = polygen(K) sage: L. = K.extension(x^5 + 2*a) - sage: L._pari_rnfequation() - [x^10 + 8, Mod(-1/2*x^5, x^10 + 8), 0] + sage: L._pari_rnfeq() + [x^10 + 8, -1/2*x^5, 0, y^2 + 2, x^5 + 2*y] sage: x = polygen(ZZ) sage: NumberField(x^10 + 8, 'a').is_isomorphic(L) True @@ -1513,8 +1540,8 @@ def _pari_rnfequation(self): sage: K. = NumberField(x^10 + 2000*x + 100001) sage: x = polygen(K) sage: L. = K.extension(x^10 + 2*a) - sage: L._pari_rnfequation() - [x^100 - 1024000*x^10 + 102401024, Mod(-1/2*x^10, x^100 - 1024000*x^10 + 102401024), 0] + sage: L._pari_rnfeq() + [x^100 - 1024000*x^10 + 102401024, -1/2*x^10, 0, y^10 + 2000*y + 100001, x^10 + 2*y] sage: a + b b + a sage: b^100 @@ -1522,9 +1549,87 @@ def _pari_rnfequation(self): sage: (-2*a)^10 -2048000*a - 102401024 """ - # Perhaps in future this should check if base_nf is known and - # use that, since it might be faster. - return self.pari_absolute_base_polynomial().rnfequation(self.pari_relative_polynomial(), 1) + f = self.pari_absolute_base_polynomial() + g = self.pari_relative_polynomial() + return f._nf_rnfeq(g) + + @cached_method + def _pari_relative_structure(self): + r""" + Return data relating the Sage and PARI relative polynomials. + + OUTPUT: + + Let `L` be this relative number field, let `K` be its base + field, and let `f` be the defining polynomial of `L` over `K`. + This method returns a triple ``(g, alpha, beta)``, where + + - ``g`` is the defining relative polynomial of the PARI + ``rnf`` structure (see :meth:`pari_rnf`); + + - ``alpha`` is the image of `x \bmod f` under some isomorphism + `\phi\colon K[x]/(f) \to K[x]/(g)`; + + - ``beta`` is the image of `x \bmod g` under the inverse + isomorphism `\phi^{-1}\colon K[x]/(g) \to K[x]/(f)`. + + EXAMPLES:: + + If the defining polynomials are monic and integral, the result + satisfies ``g = f`` and ``alpha = beta = x``:: + + sage: R. = QQ[] + sage: K. = NumberField(x^2 + 1) + sage: L. = K.extension(x^2 - a) + sage: L._pari_relative_structure() + (Mod(1, y^2 + 1)*x^2 + Mod(-y, y^2 + 1), + Mod(x, Mod(1, y^2 + 1)*x^2 + Mod(-y, y^2 + 1)), + Mod(x, Mod(1, y^2 + 1)*x^2 + Mod(-y, y^2 + 1))) + + An example where the base field is defined by a monic integral + polynomial, but the extension is not:: + + sage: K. = NumberField(x^2 + 1) + sage: L. = K.extension(x^2 - 1/2) + sage: L._pari_relative_structure() + (x^2 + Mod(-y, y^2 + 1), + Mod(Mod(1/2*y - 1/2, y^2 + 1)*x, x^2 + Mod(-y, y^2 + 1)), + Mod(Mod(-y - 1, y^2 + 1)*x, x^2 + Mod(-1/2, y^2 + 1))) + + An example where both fields are defined by non-integral or + non-monic polynomials:: + + sage: K. = NumberField(2*x^2 + 1) + sage: L. = K.extension(x^2 - 1/3) + sage: L._pari_relative_structure() + (x^2 + Mod(y, y^2 + 2)*x + 1, + Mod(Mod(-1/3*y, y^2 + 2)*x + Mod(1/3, y^2 + 2), x^2 + Mod(y, y^2 + 2)*x + 1), + Mod(Mod(3/2*y, y^2 + 2)*x + Mod(-1/2*y, y^2 + 2), x^2 + Mod(-1/3, y^2 + 2))) + + Note that in the last example, the *absolute* defining + polynomials is the same for Sage and PARI, even though this is + not the case for the base field:: + + sage: K._pari_absolute_structure() + (y^2 + 2, Mod(1/2*y, y^2 + 2), Mod(2*y, y^2 + 1/2)) + sage: L._pari_absolute_structure() + (y^4 + 4*y^2 + 1, Mod(y, y^4 + 4*y^2 + 1), Mod(y, y^4 + 4*y^2 + 1)) + """ + f = self.relative_polynomial()._pari_with_name('x') + if f.pollead() == f.content().lift().content().denominator() == 1: + g = f + alpha = beta = f.variable().Mod(f) + elif f.poldegree() == 1: + # PARI's rnfpolredbest() does not always return a + # polynomial with integral coefficients in this case. + from sage.libs.pari.all import pari + g = f.variable() + alpha = -f[0]/f[1] + beta = pari(0).Mod(f) + else: + g, alpha = self._pari_base_nf().rnfpolredbest(f, flag=1) + beta = alpha.modreverse() + return g, alpha, beta @cached_method def _gen_relative(self): @@ -1543,48 +1648,17 @@ def _gen_relative(self): b sage: c^2 + 3 0 - """ - from sage.libs.pari.all import pari - rnfeqn = self._pari_rnfequation() - f = (pari('x') - rnfeqn[2]*rnfeqn[1]).lift() - g = self._element_class(self, f) - return g - - def pari_polynomial(self, name='x'): - """ - PARI polynomial with integer coefficients corresponding to the - polynomial that defines this field as an absolute number field. - - By default, this is a polynomial in the variable "x". PARI - prefers integral polynomials, so we clear the denominator. - Therefore, this is NOT the same as simply converting the absolute - defining polynomial to PARI. - EXAMPLES:: - - sage: k. = NumberField([x^2 + 3, x^2 + 1]) - sage: k.pari_polynomial() - x^4 + 8*x^2 + 4 - sage: k.pari_polynomial('a') - a^4 + 8*a^2 + 4 - sage: k.absolute_polynomial() - x^4 + 8*x^2 + 4 - sage: k.relative_polynomial() - x^2 + 3 + An example where the defining polynomials are not monic or + integral:: - :: - - sage: k. = NumberField([x^2 + 1/3, x^2 + 1/4]) - sage: k.pari_polynomial() - 144*x^4 + 168*x^2 + 1 - sage: k.absolute_polynomial() - x^4 + 7/6*x^2 + 1/144 + sage: K. = NumberField(3*x^2 + 1) + sage: L. = K.extension(x^2 - 1/2) + sage: L._gen_relative() + b """ - try: - return self._pari_polynomial.change_variable_name(name) - except AttributeError: - self._pari_polynomial = self._pari_rnfequation()[0].change_variable_name(name) - return self._pari_polynomial + alpha = self._pari_relative_structure()[1].liftpol() + return self._element_constructor_(self._pari_rnfeq()._eltreltoabs(alpha), check=False) @cached_method def pari_rnf(self): @@ -1652,7 +1726,7 @@ def pari_relative_polynomial(self): sage: l.pari_relative_polynomial() Mod(1, y^4 + 1)*x^2 + Mod(y, y^4 + 1) """ - return self.relative_polynomial()._pari_with_name() + return self._pari_relative_structure()[0] def number_of_roots_of_unity(self): """ @@ -1776,6 +1850,17 @@ def absolute_polynomial(self): Return the polynomial over `\QQ` that defines this field as an extension of the rational numbers. + .. NOTE:: + + The absolute polynomial of a relative number field is + chosen to be equal to the defining polynomial of the + underlying PARI absolute number field (it cannot be + specified by the user). In particular, it is always a + monic polynomial with integral coefficients. On the other + hand, the defining polynomial of an absolute number field + and the relative polynomial of a relative number field are + in general different from their PARI counterparts. + EXAMPLES:: sage: k. = NumberField([x^2 + 1, x^3 + x + 1]); k @@ -1783,16 +1868,24 @@ def absolute_polynomial(self): sage: k.absolute_polynomial() x^6 + 5*x^4 - 2*x^3 + 4*x^2 + 4*x + 1 - :: + An example comparing the various defining polynomials to their + PARI counterparts:: sage: k. = NumberField([x^2 + 1/3, x^2 + 1/4]) sage: k.absolute_polynomial() - x^4 + 7/6*x^2 + 1/144 + x^4 - x^2 + 1 + sage: k.pari_polynomial() + x^4 - x^2 + 1 + sage: k.base_field().absolute_polynomial() + x^2 + 1/4 + sage: k.pari_absolute_base_polynomial() + y^2 + 1 sage: k.relative_polynomial() x^2 + 1/3 + sage: k.pari_relative_polynomial() + x^2 + Mod(y, y^2 + 1)*x - 1 """ - paripol = self._pari_rnfequation()[0] - return QQ['x'](paripol/paripol.pollead()) + return QQ['x'](self._pari_rnfeq()[0]) def relative_polynomial(self): """ @@ -2157,14 +2250,22 @@ def relative_discriminant(self): sage: K. = F.extension(Y^2 - (1 + a)*(a + b)*a*b) sage: K.relative_discriminant() == F.ideal(4*b) True + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(x^2 + 1/2) + sage: L. = K.extension(x^2 - 1/2) + sage: L.relative_discriminant() + Fractional ideal (2) """ nf = self._pari_base_nf() base = self.base_field() - abs_base = base.absolute_field('a') - to_base = abs_base.structure()[0] + abs_base, _, to_base = self.absolute_base_field() D, d = nf.rnfdisc(self.pari_relative_polynomial()) - D = map(abs_base, abs_base.pari_zk() * D) - D = [to_base(_) for _ in D] + D = [to_base(abs_base(x, check=False)) for x in abs_base.pari_zk() * D] return base.ideal(D) def discriminant(self): @@ -2327,15 +2428,26 @@ def lift_to_base(self, element): Traceback (most recent call last): ... ValueError: The element b is not in the base field + + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: R. = QQ[] + sage: K. = NumberField(x^2 + 1/2) + sage: L. = K.extension(x^2 - a/2) + sage: L.lift_to_base(b^2) + 1/2*a """ - polmodmod_xy = self.pari_rnf().rnfeltabstorel( self(element)._pari_('x') ) - # polmodmod_xy is a POLMOD with POLMOD coefficients in general. - # These POLMOD coefficients represent elements of the base field K. - # We do two lifts so we get a polynomial. We need the simplify() to - # make PARI check which variables really appear in the resulting - # polynomial (otherwise we always have a polynomial in two variables - # even though not all variables actually occur). - r = polmodmod_xy.lift().lift().simplify() + # Convert the element to a PARI polynomial with t_POLMOD + # coefficients representing elements of the base field. + r = self._pari_rnfeq()._eltabstorel_lift(self(element)._pari_polynomial('x')) + # Lift the coefficients and call simplify() to make PARI check + # which variables really appear in the resulting polynomial + # (otherwise we always have a polynomial in two variables even + # though not all variables actually occur). + r = r.lift().simplify() # Special case: check whether the result is simply an integer or rational if r.type() in ["t_INT", "t_FRAC"]: @@ -2344,7 +2456,7 @@ def lift_to_base(self, element): # Otherwise we're not in the base field. if r.type() != "t_POL" or str(r.variable()) != 'y': raise ValueError("The element %s is not in the base field"%element) - return self.base_field()(r) + return self.base_field()(r, check=False) def relativize(self, alpha, names): r""" @@ -2389,10 +2501,10 @@ def relativize(self, alpha, names): sage: K_into_M = L_into_M * K_into_L sage: L_over_K = L.relativize(K_into_L, 'c'); L_over_K - Number Field in c0 with defining polynomial x^2 + a0_0 over its base field + Number Field in c with defining polynomial x^2 + a0_0 over its base field sage: L_over_K_to_L, L_to_L_over_K = L_over_K.structure() sage: M_over_L_over_K = M.relativize(L_into_M * L_over_K_to_L, 'd'); M_over_L_over_K - Number Field in d0 with defining polynomial x^2 + c0 over its base field + Number Field in d with defining polynomial x^2 + c over its base field sage: M_over_L_over_K.base_field() is L_over_K True @@ -2410,7 +2522,7 @@ def relativize(self, alpha, names): generator):: sage: L = K.relativize(K3_into_K, 'b'); L - Number Field in b0 with defining polynomial x^2 + a0 over its base field + Number Field in b with defining polynomial x^2 + a0 over its base field sage: L_to_K, K_to_L = L.structure() sage: L_over_K2 = L.relativize(K_to_L(K2_into_K(K2.gen() + 1)), 'c'); L_over_K2 Number Field in c0 with defining polynomial x^3 - c1 + 1 over its base field @@ -2421,7 +2533,7 @@ def relativize(self, alpha, names): sage: K2_into_L = K_to_L * K2_into_K sage: L_over_K2 = L.relativize(K2_into_L, 'c'); L_over_K2 - Number Field in c0 with defining polynomial x^3 - a0 over its base field + Number Field in c with defining polynomial x^3 - a0 over its base field sage: L_over_K2.base_field() is K2 True """ diff --git a/src/sage/rings/number_field/totallyreal_rel.py b/src/sage/rings/number_field/totallyreal_rel.py index 99d0567e1be..9a13f54447b 100644 --- a/src/sage/rings/number_field/totallyreal_rel.py +++ b/src/sage/rings/number_field/totallyreal_rel.py @@ -197,7 +197,7 @@ def integral_elements_in_box(K, C): S = [] try: - pts = P.points_pc() + pts = P.points() except ValueError: return [] diff --git a/src/sage/rings/number_field/unit_group.py b/src/sage/rings/number_field/unit_group.py index fc3131dab29..7925b61d350 100644 --- a/src/sage/rings/number_field/unit_group.py +++ b/src/sage/rings/number_field/unit_group.py @@ -241,7 +241,17 @@ def __init__(self, number_field, proof=True, S=None): sage: SUK = UnitGroup(K,S=2); SUK S-unit group with structure C26 x Z x Z x Z x Z x Z x Z of Cyclotomic Field of order 13 and degree 12 with S = (Fractional ideal (2),) - """ + TESTS: + + Number fields defined by non-monic and non-integral + polynomials are supported (:trac:`252`):: + + sage: K. = NumberField(7/9*x^3 + 7/3*x^2 - 56*x + 123) + sage: K.unit_group() + Unit group with structure C2 x Z x Z of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 + sage: UnitGroup(K, S=tuple(K.primes_above(7))) + S-unit group with structure C2 x Z x Z x Z of Number Field in a with defining polynomial 7/9*x^3 + 7/3*x^2 - 56*x + 123 with S = (Fractional ideal (7/225*a^2 - 7/75*a - 42/25),) + """ proof = get_flag(proof, "number_field") K = number_field pK = K.pari_bnf(proof) @@ -270,13 +280,13 @@ def __init__(self, number_field, proof=True, S=None): self.__pS = pS = [P.pari_prime() for P in S] # compute the fundamental units via pari: - fu = [K(u) for u in pK.bnfunit()] + fu = [K(u, check=False) for u in pK.bnfunit()] self.__nfu = len(fu) # compute the additional S-unit generators: if S: self.__S_unit_data = pK.bnfsunit(pS) - su = [K(u) for u in self.__S_unit_data[0]] + su = [K(u, check=False) for u in self.__S_unit_data[0]] else: su = [] self.__nsu = len(su) @@ -286,7 +296,7 @@ def __init__(self, number_field, proof=True, S=None): n, z = pK.nfrootsof1() n = ZZ(n) self.__ntu = n - z = K(z) + z = K(z, check=False) # If we replaced z by another torsion generator we would need # to allow for this in the dlog function! So we do not. diff --git a/src/sage/rings/padics/CA_template.pxi b/src/sage/rings/padics/CA_template.pxi index 9ca4a54a8c5..1f56e90b3af 100644 --- a/src/sage/rings/padics/CA_template.pxi +++ b/src/sage/rings/padics/CA_template.pxi @@ -160,22 +160,6 @@ cdef class CAElement(pAdicTemplateElement): """ return unpickle_cae_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow), self.absprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpCA(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index 45c70d9ccec..41f63a68b0e 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -270,22 +270,6 @@ cdef class CRElement(pAdicTemplateElement): """ return unpickle_cre_v2, (self.__class__, self.parent(), cpickle(self.unit, self.prime_pow), self.ordp, self.relprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = Zp(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b # indirect doctest - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/FM_template.pxi b/src/sage/rings/padics/FM_template.pxi index e9c21608613..f7c446439bb 100644 --- a/src/sage/rings/padics/FM_template.pxi +++ b/src/sage/rings/padics/FM_template.pxi @@ -155,22 +155,6 @@ cdef class FMElement(pAdicTemplateElement): """ return unpickle_fme_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow)) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpFM(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): r""" Return the additive inverse of this element. diff --git a/src/sage/rings/padics/local_generic.py b/src/sage/rings/padics/local_generic.py index 48bdfb4f64c..8ee98a1b41d 100644 --- a/src/sage/rings/padics/local_generic.py +++ b/src/sage/rings/padics/local_generic.py @@ -55,6 +55,7 @@ def __init__(self, base, prec, names, element_class, category=None): category = CompleteDiscreteValuationFields() else: category = CompleteDiscreteValuationRings() + category = category.Metric().Complete() if default_category is not None: category = check_default_category(default_category, category) Parent.__init__(self, base, names=(names,), normalize=False, category=category, element_constructor=element_class) diff --git a/src/sage/rings/padics/morphism.pyx b/src/sage/rings/padics/morphism.pyx index 49bb5fb5ff1..20c6a31a720 100644 --- a/src/sage/rings/padics/morphism.pyx +++ b/src/sage/rings/padics/morphism.pyx @@ -1,12 +1,16 @@ -"Frobenius endomorphisms on padic fields" +""" +Frobenius endomorphisms on p-adic fields +""" -############################################################################# -# Copyright (C) 2013 Xavier Caruso -# -# Distributed under the terms of the GNU General Public License (GPL) +#***************************************************************************** +# Copyright (C) 2013 Xavier Caruso # +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -#**************************************************************************** +#***************************************************************************** from sage.rings.integer cimport Integer from sage.rings.infinity import Infinity @@ -288,9 +292,6 @@ cdef class FrobeniusEndomorphism_padics(RingHomomorphism): codomain = self.codomain() return hash((domain,codomain,('Frob',self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Compare left and right diff --git a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx index 377f2f6713b..317b69f56dc 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx @@ -445,25 +445,6 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): ans.prime_pow = self.prime_pow return ans - def __richcmp__(left, right, op): - """ - Compares ``left`` and ``right`` under the operation ``op``. - - EXAMPLES:: - - sage: R = ZpFM(5,5) - sage: S. = R[] - sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 - sage: W. = R.ext(f) - sage: w == 1 # indirect doctest - False - sage: y = 1 + w - sage: z = 1 + w + w^27 - sage: y == z - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ First compare valuations, then compare the values. diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index a43eb7ef9af..55de7f5c625 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -231,7 +231,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCR(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCR(next_prime(10^60)) sage: TestSuite(R).run() @@ -320,7 +320,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCA(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCA(next_prime(10^60)) sage: TestSuite(R).run() @@ -411,7 +411,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpFM(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpFM(next_prime(10^60)) sage: TestSuite(R).run() @@ -525,10 +525,12 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(2^10)], max_runs = 2^12) # long time sage: R = Qp(3, 1) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = Qp(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^9)]) + sage: TestSuite(R).run(elements=[R.random_element() for i in range(3^9)], + ....: skip="_test_metric") # Skip because too long + sage: R._test_metric(elements=[R.random_element() for i in range(3^3)]) sage: R = Qp(next_prime(10^60)) sage: TestSuite(R).run() diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 1fba8833650..6cccca5d9e8 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -24,6 +24,10 @@ # http://www.gnu.org/licenses/ #***************************************************************************** + +from sage.misc.prandom import sample +from sage.misc.misc import some_tuples + from sage.categories.principal_ideal_domains import PrincipalIdealDomains from sage.categories.fields import Fields from sage.rings.infinity import infinity @@ -34,6 +38,7 @@ from sage.rings.padics.precision_error import PrecisionError from sage.misc.cachefunc import cached_method + class pAdicGeneric(PrincipalIdealDomain, LocalGeneric): def __init__(self, base, p, prec, print_mode, names, element_class, category=None): """ @@ -56,6 +61,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non category = Fields() else: category = PrincipalIdealDomains() + category = category.Metric().Complete() LocalGeneric.__init__(self, base, prec, names, element_class, category) self._printer = pAdicPrinter(self, print_mode) @@ -518,12 +524,7 @@ def _test_add(self, **options): tester.assertEqual(y.precision_absolute(),x.precision_absolute()) tester.assertEqual(y.precision_relative(),x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x + y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -552,19 +553,14 @@ def _test_sub(self, **options): """ tester = self._tester(**options) - elements = tester.some_elements() + elements = list(tester.some_elements()) for x in elements: y = x - self.zero() tester.assertEqual(y, x) tester.assertEqual(y.precision_absolute(), x.precision_absolute()) tester.assertEqual(y.precision_relative(), x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x - y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -628,12 +624,8 @@ def _test_mul(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): z = x * y tester.assertIs(z.parent(), self) tester.assertLessEqual(z.precision_relative(), min(x.precision_relative(), y.precision_relative())) @@ -659,12 +651,8 @@ def _test_div(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): try: z = x / y except (ZeroDivisionError, PrecisionError, ValueError): diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 7d9fd5dacd4..1a334b28f17 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -76,6 +76,42 @@ cdef class pAdicGenericElement(LocalGenericElement): 3 + O(19^5) sage: a < b True + + :: + + sage: R = Zp(5); a = R(5, 6); b = R(5 + 5^6, 8) + sage: a == b #indirect doctest + True + + :: + + sage: R = Zp(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpCA(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpFM(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True """ m = min(left.precision_absolute(), right.precision_absolute()) x_ordp = left.valuation() diff --git a/src/sage/rings/pari_ring.py b/src/sage/rings/pari_ring.py index e777a561ccf..0ef56fc8308 100644 --- a/src/sage/rings/pari_ring.py +++ b/src/sage/rings/pari_ring.py @@ -23,11 +23,11 @@ import sage.libs.pari.all as pari import sage.rings.ring as ring -import ring_element +from sage.structure.element import RingElement from sage.misc.fast_methods import Singleton -class Pari(ring_element.RingElement): +class Pari(RingElement): """ Element of Pari pseudo-ring. """ @@ -45,7 +45,7 @@ def __init__(self, x, parent=None): """ if parent is None: parent = _inst - ring_element.RingElement.__init__(self, parent) + RingElement.__init__(self, parent) self.__x = pari.pari(x) def __repr__(self): diff --git a/src/sage/rings/polynomial/complex_roots.py b/src/sage/rings/polynomial/complex_roots.py index 5056ea53222..42432118f68 100644 --- a/src/sage/rings/polynomial/complex_roots.py +++ b/src/sage/rings/polynomial/complex_roots.py @@ -24,128 +24,25 @@ [(1.167303978261419?, 1), (-0.764884433600585? - 0.352471546031727?*I, 1), (-0.764884433600585? + 0.352471546031727?*I, 1), (0.181232444469876? - 1.083954101317711?*I, 1), (0.181232444469876? + 1.083954101317711?*I, 1)] """ +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# 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 copy import copy -from sage.rings.real_mpfi import RealIntervalField from sage.rings.complex_field import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.qqbar import AA, QQbar from sage.rings.arith import sort_complex_numbers_for_display +from sage.rings.polynomial.refine_root import refine_root -def refine_root(ip, ipd, irt, fld): - """ - We are given a polynomial and its derivative (with complex - interval coefficients), an estimated root, and a complex interval - field to use in computations. We use interval arithmetic to - refine the root and prove that we have in fact isolated a unique - root. - - If we succeed, we return the isolated root; if we fail, we return - None. - - EXAMPLES:: - - sage: from sage.rings.polynomial.complex_roots import * - sage: x = polygen(ZZ) - sage: p = x^9 - 1 - sage: ip = CIF['x'](p); ip - x^9 - 1 - sage: ipd = CIF['x'](p.derivative()); ipd - 9*x^8 - sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt - 0.76604444311897802? + 0.64278760968653926?*I - sage: ip(irt) - 0.?e-14 + 0.?e-14*I - sage: ipd(irt) - 6.89439998807080? - 5.78508848717885?*I - sage: refine_root(ip, ipd, irt, CIF) - 0.766044443118978? + 0.642787609686540?*I - """ - - # There has got to be a better way to do this, but I don't know - # what it is... - - # We start with a basic fact: if we do an interval Newton-Raphson - # step, and the refined interval is contained in the original interval, - # then the refined interval contains exactly one root. - - # Unfortunately, our initial estimated root almost certainly does not - # contain the actual root (our initial interval is a point, which - # is exactly equal to whatever floating-point estimate we got from - # the external solver). So we need to do multiple Newton-Raphson - # steps, and check this inclusion property on each step. - - # After a few steps of refinement, if the initial root estimate was - # close to a root, we should have an essentially perfect interval - # bound on the root (since Newton-Raphson has quadratic convergence), - # unless either the real or imaginary component of the root is zero. - # If the real or imaginary component is zero, then we could spend - # a long time computing closer and closer approximations to that - # component. (This doesn't happen for non-zero components, because - # of the imprecision of floating-point numbers combined with the - # outward interval rounding; but close to zero, MPFI provides - # extremely precise numbers.) - - # If the root is actually a real root, but we start with an imaginary - # component, we can bounce back and forth between having a positive - # and negative imaginary component, without ever hitting zero. - # To deal with this, on every other Newton-Raphson step, instead of - # replacing the old interval with the new one, we take the union. - - # If the containment check continues to fail many times in a row, - # we give up and return None; we also return None if we detect - # that the slope in our current interval is not bounded away - # from zero at any step. - - # After every refinement step, we check to see if the real or - # imaginary component of our interval includes zero. If so, we - # try setting it to exactly zero. This gives us a good chance of - # detecting real roots. However, we do this replacement at most - # once per component. - - refinement_steps = 10 - - smashed_real = False - smashed_imag = False - - for i in range(refinement_steps): - slope = ipd(irt) - if slope.contains_zero(): - return None - center = fld(irt.center()) - val = ip(center) - - nirt = center - val / slope - # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope - if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): - # If the new diameter is much less than the original diameter, - # then we have not yet converged. (Perhaps we were asked - # for a particularly high-precision result.) So we don't - # return yet. - return nirt - - if i & 1: - irt = nirt - else: - irt = irt.union(nirt) - # If we don't find a root after a while, try (approximately) - # tripling the size of the region. - if i >= 6: - rD = irt.real().absolute_diameter() - iD = irt.imag().absolute_diameter() - md = max(rD, iD) - md_intv = RealIntervalField(rD.prec())(-md, md) - md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) - irt = irt + md_cintv - - if not smashed_real and irt.real().contains_zero(): - irt = irt.parent()(0, irt.imag()) - smashed_real = True - if not smashed_imag and irt.imag().contains_zero(): - irt = irt.parent()(irt.real(), 0) - smashed_imag = True - - return None def interval_roots(p, rts, prec): """ @@ -335,7 +232,7 @@ def complex_roots(p, skip_squarefree=False, retval='interval', min_prec=0): TESTS: - Verify that trac 12026 is fixed:: + Verify that :trac:`12026` is fixed:: sage: f = matrix(QQ, 8, lambda i, j: 1/(i + j + 1)).charpoly() sage: from sage.rings.polynomial.complex_roots import complex_roots diff --git a/src/sage/rings/polynomial/ideal.py b/src/sage/rings/polynomial/ideal.py index a23f3e8567a..508496c75d4 100644 --- a/src/sage/rings/polynomial/ideal.py +++ b/src/sage/rings/polynomial/ideal.py @@ -64,4 +64,3 @@ def residue_field(self, names=None, check=True): from sage.rings.finite_rings.residue_field import ResidueField return ResidueField(self, names, check=False) - diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 59e873fe00d..081b6d4f340 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -255,16 +255,15 @@ def _repr_(self): """ return repr(self._p) - def _hash_(self): + def __hash__(self): """ TESTS:: sage: X. = InfinitePolynomialRing(QQ) sage: a = x[0] + x[1] sage: hash(a) # indirect doctest - -6172640511012239345 # 64-bit - -957478897 # 32-bit - + 971115012877883067 # 64-bit + -2103273797 # 32-bit """ return hash(self._p) diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 091fc1a2fb0..956c0c09ee5 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -844,30 +844,6 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): rl = LaurentPolynomial_univariate(self._parent, r, 0) return (ql, rl) - def __richcmp__(left, right, int op): - """ - Return the rich comparison of ``left`` and ``right`` defined by ``op``. - - EXAMPLES:: - - sage: R. = LaurentPolynomialRing(QQ) - sage: f = x^(-1) + 1 + x - sage: g = x^(-1) + 2 - sage: f == g - False - sage: f != g - True - sage: f < g - True - sage: f <= g - True - sage: f > g - False - sage: f >= g - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of ``self`` and ``right_r``. @@ -886,10 +862,16 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): sage: g = x^(-1) + 2 sage: f == g False + sage: f != g + True sage: f < g True + sage: f <= g + True sage: f > g False + sage: f >= g + False :: @@ -1332,8 +1314,25 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): sage: R. = LaurentPolynomialRing(QQ) sage: loads(dumps(x1)) == x1 # indirect doctest True + sage: z = x1/x2 + sage: loads(dumps(z)) == z # not tested (bug) + True + """ + # one should also record the monomial self._mon + return self._parent, (self._poly,) # THIS IS WRONG ! + + def __hash__(self): + r""" + TESTS: + + Test that the hash is non-constant (the hash does not need to be + deterministic so we leave some slack for collisions):: + + sage: L. = LaurentPolynomialRing(QQ) + sage: len({hash(w^i*z^j) for i in [-2..2] for j in [-2..2]}) > 20 + True """ - return self._parent, (self._poly,) + return hash(self._poly) ^ hash(self._mon) cdef _new_c(self): """ @@ -1374,7 +1373,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): else: e = e.emin(k) if len(e.nonzero_positions()) > 0: - self._poly = self._poly / self._poly.parent()({e: 1}) + self._poly = self._poly // self._poly.parent()({e: 1}) self._mon = self._mon.eadd(e) else: e = None @@ -1382,7 +1381,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): if e is None or k[i] < e: e = k[i] if e > 0: - self._poly = self._poly / self._poly.parent().gen(i) + self._poly = self._poly // self._poly.parent().gen(i) self._mon = self._mon.eadd_p(e, i) def _dict(self): @@ -2047,8 +2046,17 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): x^2 - x*y^-1 + y^-2 sage: h * (f // h) == f True + + TESTS: + + Check that :trac:`19357` is fixed:: + + sage: x // y + x*y^-1 """ cdef LaurentPolynomial_mpair ans = self._new_c() + self._normalize() + right._normalize() ans._mon = self._mon.esub((right)._mon) ans._poly = self._poly.__floordiv__((right)._poly) return ans diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 0c56d646477..6abf48db402 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -4,7 +4,7 @@ Base class for elements of multivariate polynomial rings from sage.rings.integer cimport Integer from sage.rings.integer_ring import ZZ - +from sage.structure.element cimport coercion_model from sage.misc.derivative import multi_derivative from sage.rings.infinity import infinity @@ -1207,8 +1207,6 @@ cdef class MPolynomial(CommutativeRingElement): from sage.matrix.constructor import matrix if self.parent() != right.parent(): - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) if variable: variable = a.parent()(variable) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 6773851b470..2b236665a4e 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -440,7 +440,7 @@ def degrees(self): else: return self._MPolynomial_element__element.max_exp() - def degree(self, x=None): + def degree(self, x=None, std_grading=False): """ Return the degree of self in x, where x must be one of the generators for the parent of self. @@ -450,7 +450,9 @@ def degree(self, x=None): - ``x`` - multivariate polynomial (a generator of the parent of self). If ``x`` is not specified (or is None), return the total degree, which is the maximum degree of any - monomial. + monomial. Note that a weighted term ordering alters the + grading of the generators of the ring; see the tests below. + To avoid this behavior, set the optional argument ``std_grading=True``. OUTPUT: integer @@ -467,6 +469,29 @@ def degree(self, x=None): sage: (y^10*x - 7*x^2*y^5 + 5*x^3).degree(y) 10 + Note that total degree takes into account if we are working in a polynomial + ring with a weighted term order. + + :: + + sage: R = PolynomialRing(QQ,'x,y',order=TermOrder('wdeglex',(2,3))) + sage: x,y = R.gens() + sage: x.degree() + 2 + sage: y.degree() + 3 + sage: x.degree(y),x.degree(x),y.degree(x),y.degree(y) + (0, 1, 0, 1) + sage: f = (x^2*y+x*y^2) + sage: f.degree(x) + 2 + sage: f.degree(y) + 2 + sage: f.degree() + 8 + sage: f.degree(std_grading=True) + 3 + Note that if ``x`` is not a generator of the parent of self, for example if it is a generator of a polynomial algebra which maps naturally to this one, then it is converted to an element @@ -489,9 +514,33 @@ def degree(self, x=None): Traceback (most recent call last): ... TypeError: x must be one of the generators of the parent + + TEST:: + + sage: R = PolynomialRing(GF(2)['t'],'x,y',order=TermOrder('wdeglex',(2,3))) + sage: x,y = R.gens() + sage: x.degree() + 2 + sage: y.degree() + 3 + sage: x.degree(y),x.degree(x),y.degree(x),y.degree(y) + (0, 1, 0, 1) + sage: f = (x^2*y+x*y^2) + sage: f.degree(x) + 2 + sage: f.degree(y) + 2 + sage: f.degree() + 8 + sage: f.degree(std_grading=True) + 3 + sage: R(0).degree() + -1 """ if x is None: - return self.element().degree(None) + if std_grading or not self.parent().term_order().is_weighted_degree_order(): + return self.element().degree(None) + return self.weighted_degree(self.parent().term_order().weights()) if isinstance(x, MPolynomial): if not x.parent() is self.parent(): try: diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 52120c0a1b6..460b2ddb4a6 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1272,7 +1272,8 @@ def _groebner_basis_ginv(self, algorithm="TQ", criteria='CritPartially', divisio try: import ginv except ImportError: - raise ImportError("""GINV is missing, to install use "install_package('ginv')".""") + from sage.misc.package import PackageNotFoundError + raise PackageNotFoundError("ginv") st = ginv.SystemType("Polynomial") @@ -2204,7 +2205,6 @@ def variety(self, ring=None): This is due to precision error, which causes the computation of an intermediate Groebner basis to fail. - If the ground field's characteristic is too large for Singular, we resort to a toy implementation:: @@ -2216,6 +2216,22 @@ def variety(self, ring=None): verbose 0 (...: multi_polynomial_ideal.py, variety) Warning: falling back to very slow toy implementation. [{y: 0, x: 0}] + The dictionary expressing the variety will be indexed by generators + of the polynomial ring after changing to the target field. + But the mapping will also accept generators of the original ring, + or even generator names as strings, when provided as keys:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? + TESTS:: sage: K. = GF(27) @@ -2245,27 +2261,27 @@ def variety(self, ring=None): x26^2 + x26, x27^2 + x27, x28^2 + x28, x29^2 + x29, x30^2 + x30]) sage: I.basis_is_groebner() True - sage: for V in I.variety(): print V # long time (6s on sage.math, 2011) - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} + sage: sorted("".join(str(V[g]) for g in R.gens()) for V in I.variety()) # long time (6s on sage.math, 2011) + ['101000100000000110001000100110', + '101000100000000110001000101110', + '101000100100000101001000100110', + '101000100100000101001000101110', + '101010100000000110001000100110', + '101010100000000110001000101110', + '101010100010000110001000100110', + '101010100010000110001000101110', + '101010100110000110001000100110', + '101010100110000110001000101110', + '101100100000000110001000100110', + '101100100000000110001000101110', + '101100100100000101001000100110', + '101100100100000101001000101110', + '101110100000000110001000100110', + '101110100000000110001000101110', + '101110100010000110001000100110', + '101110100010000110001000101110', + '101110100110000110001000100110', + '101110100110000110001000101110'] Check that the issue at :trac:`7425` is fixed:: @@ -2352,15 +2368,16 @@ def _variety(T, V, v=None): else: raise TypeError("Local/unknown orderings not supported by 'toy_buchberger' implementation.") + from sage.misc.converting_dict import KeyConvertingDict V = [] for t in T: Vbar = _variety([P(f) for f in t], []) #Vbar = _variety(list(t.gens()),[]) for v in Vbar: - V.append(dict([(P(var),val) for var,val in v.iteritems()])) + V.append(KeyConvertingDict(P, v)) V.sort() - return Sequence(V) + return V @require_field def hilbert_polynomial(self): @@ -2691,10 +2708,14 @@ def __init__(self, ring, gens, coerce=True, side = "left"): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) # indirect doctest - sage: I + sage: I #random Left Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") + sage: sorted(I.gens(),key=str) + [x^2, y^2, z^2 - 1] + sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") #random Twosided Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(H.ideal([y^2, x^2, z^2-H.one()], side="twosided").gens(),key=str) + [x^2, y^2, z^2 - 1] sage: H.ideal([y^2, x^2, z^2-H.one()], side="right") Traceback (most recent call last): ... @@ -2725,8 +2746,10 @@ def __call_singular(self, cmd, arg = None): sage: H.inject_variables() Defining x, y, z sage: id = H.ideal(x + y, y + z) - sage: id.std() # indirect doctest + sage: id.std() # indirect doctest # random Left Ideal (z, y, x) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(id.std().gens(),key=str) + [x, y, z] """ from sage.libs.singular.function import singular_function fun = singular_function(cmd) @@ -2747,23 +2770,34 @@ def std(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + If the ideal is a left ideal, then std returns a left Groebner basis. But if it is a two-sided ideal, then the output of std and :meth:`twostd` coincide:: sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL + sage: JL #random Left Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JL.std() + sage: sorted(JL.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JL.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*z^2 + 2*x*z, x^3, y*z^2 - 2*y*z, y^3, z^3 - 4*z] sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT + sage: JT #random Twosided Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JT.std() + sage: sorted(JT.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JT.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*y^2 - y*z, x*z^2 + 2*x*z, x^2*y - x*z - 2*x, x^2*z + 2*x^2, x^3, y*z^2 - 2*y*z, y^2*z - 2*y^2, y^3, z^3 - 4*z] sage: JT.std() == JL.twostd() True @@ -2786,8 +2820,10 @@ def twostd(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.twostd() + sage: I.twostd() #random Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field... + sage: sorted(I.twostd().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] ALGORITHM: Uses Singular's twostd command """ @@ -2808,7 +2844,7 @@ def _groebner_strategy(self): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I._groebner_strategy() + sage: I._groebner_strategy() #random Groebner Strategy for ideal generated by 6 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} @@ -2836,7 +2872,7 @@ def reduce(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False, side='twosided') - sage: Q = H.quotient(I); Q + sage: Q = H.quotient(I); Q #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} by the ideal (y^2, x^2, z^2 - 1) @@ -2846,10 +2882,8 @@ def reduce(self,p): Here, we see that the relation that we just found in the quotient is actually a consequence of the given relations:: - sage: I.std() - Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) - of Noncommutative Multivariate Polynomial Ring in x, y, z over - Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H.2^2-H.one() in I.std().gens() + True Here is the corresponding direct test:: @@ -2868,10 +2902,10 @@ def _contains_(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL.std() + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT.std() + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} Apparently, ``x*y^2-y*z`` should be in the two-sided, but not @@ -2995,6 +3029,18 @@ def __init__(self, ring, gens, coerce=True): Ideal_generic.__init__(self, ring, gens, coerce=coerce) self._gb_by_ordering = dict() + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R. = PolynomialRing(IntegerRing(), 2, order='lex') + sage: hash(R.ideal([x, y])) + 0 + """ + return 0 + @cached_method def gens(self): """ @@ -3132,7 +3178,9 @@ def __gt__(self, other): #. Return ``other`` < ``self``. - .. SEEALSO: :meth:`__lt__`. + .. SEEALSO:: + + :meth:`__lt__`. EXAMPLE:: @@ -3415,6 +3463,9 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal 'ginv:TQ', 'ginv:TQBlockHigh', 'ginv:TQBlockLow' and 'ginv:TQDegree' One of GINV's implementations (if available) + 'giac:gbasis' + Giac's ``gbasis`` command (if available) + If only a system is given - e.g. 'magma' - the default algorithm is chosen for that system. @@ -3470,6 +3521,28 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal sage: I.groebner_basis('libsingular:slimgb') [a - 60*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 - 79/7*c^2 + 3/7*c, c^4 - 10/21*c^3 + 1/84*c^2 + 1/84*c] + Giac only supports the degree reverse lexicographical ordering:: + + sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching + sage: J = I.change_ring(P.change_ring(order='degrevlex')) + sage: gb = J.groebner_basis('giac') # optional - giacpy, random + sage: gb # optional - giacpy + [c^3 - 79/210*c^2 + 1/30*b + 1/70*c, b^2 - 3/5*c^2 - 1/5*b + 1/5*c, b*c + 6/5*c^2 - 1/10*b - 2/5*c, a + 2*b + 2*c - 1] + + sage: ideal(J.transformed_basis()).change_ring(P).interreduced_basis() # optional - giacpy + [a - 60*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 - 79/7*c^2 + 3/7*c, c^4 - 10/21*c^3 + 1/84*c^2 + 1/84*c] + + Giac's gbasis over `\QQ` can benefit from a probabilistic lifting and + multi threaded operations:: + + sage: A9=PolynomialRing(QQ,9,'x') # optional - giacpy + sage: I9=sage.rings.ideal.Katsura(A9) # optional - giacpy + sage: I9.groebner_basis("giac",proba_epsilon=1e-7) # optional - giacpy, long time (3s) + Running a probabilistic check for the reconstructed Groebner basis... + Polynomial Sequence with 143 Polynomials in 9 Variables + + The list of available Giac options is provided at :func:`sage.libs.giac.groebner_basis`. + Note that ``toy:buchberger`` does not return the reduced Groebner basis, :: @@ -3621,7 +3694,7 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal ALGORITHM: Uses Singular, Magma (if available), Macaulay2 (if available), - or a toy implementation. + Giac (if available), or a toy implementation. """ from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence @@ -3636,6 +3709,8 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal algorithm = "macaulay2:gb" elif algorithm.lower() == "toy": algorithm = "toy:buchberger2" + elif algorithm.lower() == "giac": + algorithm = "giac:gbasis" if not algorithm: try: @@ -3686,6 +3761,10 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal gb = self._groebner_basis_ginv(algorithm=alg,*args, **kwds) else: raise NameError("Algorithm '%s' unknown."%algorithm) + elif algorithm == 'giac:gbasis': + from sage.libs.giac import groebner_basis as groebner_basis_libgiac + gb = groebner_basis_libgiac(self, prot=prot, *args, **kwds) + else: raise NameError("Algorithm '%s' unknown."%algorithm) @@ -4367,8 +4446,9 @@ def weil_restriction(self): Ring in x0, x1, x2, x3, x4, y0, y1, y2, y3, y4, z0, z1, z2, z3, z4 over Finite Field of size 3 sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal - sage: J.variety() - [{y1: 0, y4: 0, x4: 0, y2: 0, y3: 0, y0: 0, x2: 0, z4: 0, z3: 0, z2: 0, x1: 0, z1: 0, z0: 0, x0: 1, x3: 0}] + sage: from sage.doctest.fixtures import reproducible_repr + sage: print(reproducible_repr(J.variety())) + [{x0: 1, x1: 0, x2: 0, x3: 0, x4: 0, y0: 0, y1: 0, y2: 0, y3: 0, y4: 0, z0: 0, z1: 0, z2: 0, z3: 0, z4: 0}] Weil restrictions are often used to study elliptic curves over diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index a4ef220b276..f96796667b1 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -219,7 +219,7 @@ from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.number_field.number_field_base cimport NumberField from sage.rings.arith import gcd -from sage.structure.element import coerce_binop, get_coercion_model +from sage.structure.element import coerce_binop from sage.structure.parent cimport Parent from sage.structure.parent_base cimport ParentWithBase @@ -231,6 +231,7 @@ from sage.structure.element cimport RingElement from sage.structure.element cimport ModuleElement from sage.structure.element cimport Element from sage.structure.element cimport CommutativeRingElement +from sage.structure.element cimport coercion_model from sage.structure.factorization import Factorization from sage.structure.sequence import Sequence @@ -1142,7 +1143,7 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): EXAMPLES:: sage: P. = PolynomialRing(QQ) - sage: M2 = P._macaulay2_set_ring() # optional - M2 + sage: M2 = P._macaulay2_set_ring() # optional - macaulay2 """ if not self.__m2_set_ring_cache is None: base_str, gens, order = self.__m2_set_ring_cache @@ -2114,7 +2115,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn cdef poly *res # ownership will be transferred to us in the next line singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) - res_parent = get_coercion_model().common_parent(parent._base, *x) + res_parent = coercion_model.common_parent(parent._base, *x) if res == NULL: return res_parent(0) @@ -2128,10 +2129,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage_res = res_parent(sage_res) return sage_res - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. - def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -2155,7 +2152,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -2207,9 +2204,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage: (66*x^2 + 23) > (66*x^2 + 2) True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx index a35253ed3be..773cdebd99b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx @@ -903,6 +903,24 @@ cdef class MPolynomialRing_generic(sage.rings.ring.CommutativeRing): from polynomial_ring_constructor import PolynomialRing return PolynomialRing(base_ring, self.ngens(), names, order=order) + def monomial(self,*exponents): + """ + Return the monomial with given exponents. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, 3) + sage: R.monomial(1,1,1) + x*y*z + sage: e=(1,2,3) + sage: R.monomial(*e) + x*y^2*z^3 + sage: m = R.monomial(1,2,3) + sage: R.monomial(*m.degrees()) == m + True + """ + return self({exponents:self.base_ring().one()}) + def _macaulay_resultant_getS(self,mon_deg_tuple,dlist): r""" In the Macaulay resultant algorithm the list of all monomials of the total degree is partitioned into sets `S_i`. diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index b586b4ea108..3cdca4a8535 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -157,6 +157,7 @@ from sage.misc.cachefunc import cached_method from types import GeneratorType +from sage.misc.converting_dict import KeyConvertingDict from sage.misc.package import is_package_installed from sage.structure.sequence import Sequence, Sequence_generic @@ -1212,8 +1213,8 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc """ from sage.rings.polynomial.pbori import BooleanPolynomialRing - from polybori import gauss_on_polys - from polybori.ll import eliminate,ll_encode,ll_red_nf_redsb + from brial import gauss_on_polys + from brial.ll import eliminate,ll_encode,ll_red_nf_redsb R = self.ring() @@ -1346,10 +1347,12 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver Without argument, a single arbitrary solution is returned:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: S = Sequence([x*y+z, y*z+x, x+y+z+1]) - sage: sol = S.solve(); sol # random - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve() + sage: print(reproducible_repr(sol)) + [{x: 0, y: 1, z: 0}] We check that it is actually a solution:: @@ -1358,7 +1361,8 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We obtain all solutions:: - sage: sols = S.solve(n=Infinity); sols # random + sage: sols = S.solve(n=Infinity) + sage: print(reproducible_repr(sols)) [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] sage: map( lambda x: S.subs(x), sols) [[0, 0, 0], [0, 0, 0]] @@ -1366,15 +1370,17 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We can force the use of exhaustive search if the optional package ``FES`` is present:: - sage: sol = S.solve(algorithm='exhaustive_search'); sol # random, optional - FES + sage: sol = S.solve(algorithm='exhaustive_search') # optional - FES + sage: print(reproducible_repr(sol)) # optional - FES [{x: 1, y: 1, z: 1}] sage: S.subs( sol[0] ) [0, 0, 0] And we may use SAT-solvers if they are available:: - sage: sol = S.solve(algorithm='sat'); sol # random, optional - cryptominisat - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve(algorithm='sat'); sol # optional - cryptominisat + sage: print(reproducible_repr(sol)) # optional - cryptominisat + [{x: 0, y: 1, z: 0}] sage: S.subs( sol[0] ) [0, 0, 0] @@ -1418,7 +1424,8 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver if S != []: if algorithm == "exhaustive_search": if not is_package_installed('fes'): - raise ValueError('algorithm=exhaustive_search requires the optional library FES. Run "install_package(\'fes\')" to install it.') + from sage.misc.package import PackageNotFoundError + raise PackageNotFoundError("fes") from sage.libs.fes import exhaustive_search solutions = exhaustive_search(S, max_sols=n, verbose=verbose, **kwds) @@ -1450,15 +1457,19 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver eliminated_variables = { f.lex_lead() for f in reductors } leftover_variables = { x.lm() for x in R_origin.gens() } - solved_variables - eliminated_variables + key_convert = lambda x: R_origin(x).lm() if leftover_variables != set(): partial_solutions = solutions solutions = [] for sol in partial_solutions: for v in VectorSpace( GF(2), len(leftover_variables) ): - new_solution = sol.copy() + new_solution = KeyConvertingDict(key_convert, sol) for var,val in zip(leftover_variables, v): new_solution[ var ] = val solutions.append( new_solution ) + else: + solutions = [ KeyConvertingDict(key_convert, sol) + for sol in solutions ] for r in reductors: for sol in solutions: @@ -1488,7 +1499,7 @@ def reduced(self): R = self.ring() if isinstance(R, BooleanPolynomialRing): - from polybori.interred import interred as inter_red + from brial.interred import interred as inter_red l = [p for p in self if not p==0] l = sorted(inter_red(l, completely=True), reverse=True) return PolynomialSequence(l, R, immutable=True) diff --git a/src/sage/rings/polynomial/pbori.pyx b/src/sage/rings/polynomial/pbori.pyx index 4ff7170e1c4..ab92d9dd498 100644 --- a/src/sage/rings/polynomial/pbori.pyx +++ b/src/sage/rings/polynomial/pbori.pyx @@ -153,7 +153,7 @@ Access to the original PolyBoRi interface The re-implementation PolyBoRi's native wrapper is available to the user too:: - sage: from polybori import * + sage: from brial import * sage: declare_ring([Block('x',2),Block('y',3)],globals()) Boolean PolynomialRing in x0, x1, y0, y1, y2 sage: r @@ -1484,7 +1484,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_generic): ALGORITHM: Calls ``interpolate_smallest_lex`` as described in the PolyBoRi tutorial. """ - #from polybori.interpolate import interpolate_smallest_lex + #from brial.interpolate import interpolate_smallest_lex from sage.misc.misc_c import prod n = self.ngens() x = self.gens() @@ -1809,7 +1809,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(2) sage: M = BooleanMonomialMonoid(P) sage: M @@ -1834,7 +1834,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): TESTS:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: B. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(B) sage: M @@ -1859,7 +1859,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): """ EXAMPLE:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(2) sage: M = BooleanMonomialMonoid(P) sage: M # indirect doctest @@ -1873,7 +1873,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLE:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(2) sage: M = BooleanMonomialMonoid(P) sage: {M:1} # indirect doctest @@ -1887,7 +1887,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P = BooleanPolynomialRing(100, 'x') sage: M = BooleanMonomialMonoid(P) sage: M.ngens() @@ -1905,7 +1905,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M.gen(0) @@ -1932,7 +1932,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M.gens() @@ -1946,7 +1946,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M.get_action(ZZ) # indirect doctest @@ -1966,7 +1966,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x_monom = M(x); x_monom @@ -1977,7 +1977,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): Convert elements from :class:`BooleanMonomialMonoid` where the generators of self include the generators of the other monoid:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: R. = BooleanPolynomialRing(2) sage: N = BooleanMonomialMonoid(R) sage: m = M(N(y*z)); m @@ -1987,7 +1987,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): TESTS:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: R. = BooleanPolynomialRing(2) sage: N = BooleanMonomialMonoid(R) sage: m = M(N(y)); m @@ -1995,7 +1995,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): ... TypeError: list indices must be integers, not sage.rings.polynomial.pbori.BooleanMonomial - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: R. = BooleanPolynomialRing(4) sage: N = BooleanMonomialMonoid(R) sage: m = M(N(x*y*z)); m @@ -2030,7 +2030,7 @@ class BooleanMonomialMonoid(UniqueRepresentation,Monoid_class): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x_monom = M(x); x_monom @@ -2169,7 +2169,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLE:: - sage: from polybori import BooleanMonomialMonoid, BooleanMonomial + sage: from brial import BooleanMonomialMonoid, BooleanMonomial sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: BooleanMonomial(M) @@ -2184,7 +2184,7 @@ cdef class BooleanMonomial(MonoidElement): """ EXAMPLE:: - sage: from polybori import BooleanMonomialMonoid, BooleanMonomial + sage: from brial import BooleanMonomialMonoid, BooleanMonomial sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: BooleanMonomial(M) @@ -2200,16 +2200,13 @@ cdef class BooleanMonomial(MonoidElement): self._ring = parent._ring self._pbmonom = PBMonom_Constructor((self._ring)._pbring) - def __dealloc__(self): - pass # destruction by C++ object's destructor - def __reduce__(self): """ Pickling TEST:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: R. = BooleanPolynomialRing(2) sage: M = BooleanMonomialMonoid(R) sage: t = M.0*M.1 @@ -2219,13 +2216,13 @@ cdef class BooleanMonomial(MonoidElement): gens = self._parent.gens() return self._parent, (tuple([gens.index(x) for x in self.variables()]),) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare BooleanMonomial objects. EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M(x) < M(y) @@ -2243,10 +2240,6 @@ cdef class BooleanMonomial(MonoidElement): sage: M(x) >= M(x) True """ - # boilerplate code from sage.structure.parent - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: cdef int res res = left._pbmonom.compare((right)._pbmonom) return res @@ -2403,7 +2396,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M(x*y).deg() @@ -2432,7 +2425,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M(x*y).degree() @@ -2537,7 +2530,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: len(M(x*y)) @@ -2551,7 +2544,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: list(M(x*z)) # indirect doctest @@ -2565,7 +2558,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLE:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: M(x*z).variables() # indirect doctest @@ -2579,7 +2572,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: list(M(x*z).iterindex()) @@ -2593,7 +2586,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x = M(x); xy = M(x*y); z=M(z) @@ -2619,7 +2612,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x = M(x); xy = M(x*y) @@ -2663,7 +2656,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLES:: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x = M(x); xy = M(x*y) @@ -2715,7 +2708,7 @@ cdef class BooleanMonomial(MonoidElement): EXAMPLE:: - sage: from polybori import BooleSet + sage: from brial import BooleSet sage: B = BooleanPolynomialRing(5,'x') sage: x0,x1,x2,x3,x4 = B.gens() sage: f = x1*x2+x2*x3*x4+x2*x4+x3+x4+1 @@ -2893,7 +2886,7 @@ cdef class BooleanPolynomial(MPolynomial): TEST:: - sage: from polybori import BooleanPolynomial + sage: from brial import BooleanPolynomial sage: B. = BooleanPolynomialRing(3) sage: BooleanPolynomial(B) 0 @@ -4121,7 +4114,7 @@ cdef class BooleanPolynomial(MPolynomial): sage: loads(dumps(a)) == a True """ - from polybori.parallel import _encode_polynomial + from brial.parallel import _encode_polynomial return unpickle_BooleanPolynomial0, (self._parent, _encode_polynomial(self)) def _magma_init_(self, magma): @@ -4346,7 +4339,7 @@ cdef class BooleanPolynomial(MPolynomial): EXAMPLE:: - sage: from polybori import BooleSet + sage: from brial import BooleSet sage: B = BooleanPolynomialRing(5,'x') sage: x0,x1,x2,x3,x4 = B.gens() sage: f = x1*x2+x2*x3*x4+x2*x4+x3+x4+1 @@ -4706,7 +4699,7 @@ cdef class BooleanPolynomial(MPolynomial): ... TypeError: argument must be a BooleanPolynomial. """ - from polybori import red_tail + from brial import red_tail if not I: return self if isinstance(I, BooleanPolynomialIdeal): @@ -4732,7 +4725,7 @@ cdef class PolynomialConstruct: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: PolynomialConstruct().lead(a) a @@ -4745,7 +4738,7 @@ cdef class PolynomialConstruct: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: PolynomialConstruct()(1, B) 1 @@ -4782,7 +4775,7 @@ cdef class MonomialConstruct: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: MonomialConstruct()(B) 1 @@ -4818,7 +4811,7 @@ cdef class VariableConstruct: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: VariableConstruct()(B) a @@ -5090,7 +5083,7 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): [x*z, x*y + y*z + y] """ - from polybori.gbcore import groebner_basis + from brial.gbcore import groebner_basis if self.ring().term_order()[0].name() == "degrevlex": # PolyBoRi's groebner_basis assumes increasing indices @@ -5110,10 +5103,11 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): A Simple example:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: I = ideal( [ x*y*z + x*z + y + 1, x+y+z+1 ] ) - sage: I.variety() - [{z: 0, y: 1, x: 0}, {z: 1, y: 1, x: 1}] + sage: print(reproducible_repr(I.variety())) + [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] TESTS: @@ -5130,14 +5124,14 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): x1*x2 + x1*x4 + x1*x5 + x1*x6 + x2*x3 + x2*x4 + x2*x5 + x3*x5 + x5*x6 + x5 + x6, \ x1*x2 + x1*x6 + x2*x4 + x2*x5 + x2*x6 + x3*x6 + x4*x6 + x5*x6 + x5] sage: I = R.ideal( polys ) - sage: I.variety() - [{x6: 0, x5: 0, x4: 0, x3: 0, x2: 0, x1: 0}, - {x6: 1, x5: 0, x4: 0, x3: 1, x2: 1, x1: 1}] + sage: print(reproducible_repr(I.variety())) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] sage: R = PolynomialRing(GF(2), 6, ['x%d'%(i+1) for i in range(6)], order='lex') sage: I = R.ideal( polys ) - sage: (I + sage.rings.ideal.FieldIdeal(R)).variety() - [{x2: 0, x5: 0, x4: 0, x1: 0, x6: 0, x3: 0}, {x2: 1, x5: 0, x4: 0, x1: 1, x6: 1, x3: 1}] + sage: v = (I + sage.rings.ideal.FieldIdeal(R)).variety() + sage: print(reproducible_repr(v)) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] Check that :trac:`13976` is fixed:: @@ -5148,13 +5142,21 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): sage: sols[0][y] 1 + Make sure the result is a key converting dict, as discussed in + :trac:`9788` and consistent with + :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal_singular_repr.variety`:: + + sage: sols[0]["y"] + 1 + """ + from sage.misc.converting_dict import KeyConvertingDict R_bool = self.ring() R = R_bool.cover_ring() I = R.ideal( [ R( f ) for f in self.groebner_basis() ] ) J = FieldIdeal(R) solutions = (I+J).variety(**kwds) - return [ { R_bool(var):val for var,val in s.iteritems() } for s in solutions ] + return [ KeyConvertingDict(R_bool, s) for s in solutions ] def reduce(self, f): @@ -5179,7 +5181,7 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): sage: I.reduce(gb[0]*B.gen(1)) 0 """ - from polybori import red_tail + from brial import red_tail try: g = self.__gb except AttributeError: @@ -5291,7 +5293,7 @@ cdef class BooleSet: EXAMPLE:: - sage: from polybori import BooleSet + sage: from brial import BooleSet sage: B. = BooleanPolynomialRing(4) sage: BS = BooleSet(a.set()) sage: BS @@ -5301,7 +5303,7 @@ cdef class BooleSet: sage: BS {{a,b}, {c}, {}} - sage: from polybori import * + sage: from brial import * sage: BooleSet([Monomial(B)]) {{}} @@ -5360,7 +5362,7 @@ cdef class BooleSet: """ EXAMPLE:: - sage: from polybori import BooleSet + sage: from brial import BooleSet sage: B. = BooleanPolynomialRing(4) sage: BS = BooleSet(B) sage: repr(BS) # indirect doctest @@ -5412,7 +5414,7 @@ cdef class BooleSet: EXAMPLE:: - sage: from polybori import BooleSet + sage: from brial import BooleSet sage: B = BooleanPolynomialRing(5,'x') sage: x0,x1,x2,x3,x4 = B.gens() sage: f = x1*x2+x2*x3*x4+x2*x4+x3+x4+1 @@ -6022,7 +6024,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector(l) sage: len(v) @@ -6043,7 +6045,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector(l) sage: len(v) @@ -6071,7 +6073,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector(l) sage: list(iter(v)) @@ -6084,7 +6086,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector() sage: len(v) @@ -6100,7 +6102,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector(l) sage: len(v) @@ -6131,7 +6133,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: l = [B.random_element() for _ in range(3)] sage: v = BooleanPolynomialVector(l) sage: len(v) @@ -6164,7 +6166,7 @@ cdef class BooleanPolynomialVector: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: v = BooleanPolynomialVector() sage: for i in range(5): ... v.append(B.random_element()) @@ -6229,7 +6231,7 @@ cdef class ReductionStrategy: """ EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) """ @@ -6241,7 +6243,7 @@ cdef class ReductionStrategy: """ EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: del(red) @@ -6259,7 +6261,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(x) @@ -6289,7 +6291,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(x + y + 1) @@ -6313,7 +6315,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(x + y + 1) @@ -6338,7 +6340,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.opt_red_tail = True @@ -6360,7 +6362,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(a*b + c + 1) @@ -6384,7 +6386,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(a*b + c + 1) @@ -6425,7 +6427,7 @@ cdef class ReductionStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.opt_red_tail = True @@ -6486,7 +6488,7 @@ cdef class ReductionStrategy: """ EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.opt_red_tail = True @@ -6520,7 +6522,7 @@ cdef class FGLMStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: x > y > z True @@ -6572,7 +6574,7 @@ cdef class FGLMStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: ideal = BooleanPolynomialVector([x+z, y+z]) sage: list(ideal) @@ -6603,7 +6605,7 @@ cdef class GroebnerStrategy: EXAMPLE:: - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: B. = BooleanPolynomialRing() sage: G = GroebnerStrategy(B) sage: H = GroebnerStrategy(G) @@ -6630,7 +6632,7 @@ cdef class GroebnerStrategy: """ EXAMPLE:: - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: B. = BooleanPolynomialRing() sage: G = GroebnerStrategy(B) sage: H = GroebnerStrategy(G) @@ -6655,7 +6657,7 @@ cdef class GroebnerStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: gbs = GroebnerStrategy(B) sage: gbs.add_generator(a + b) @@ -6682,7 +6684,7 @@ cdef class GroebnerStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: gbs = GroebnerStrategy(B) sage: gbs.add_generator(a + b) @@ -6710,7 +6712,7 @@ cdef class GroebnerStrategy: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: gbs = GroebnerStrategy(B) sage: gbs.add_as_you_wish(a + b) @@ -6768,7 +6770,7 @@ cdef class GroebnerStrategy: spanned by the generators but not in the set of generators:: sage: B. = BooleanPolynomialRing() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: gb = GroebnerStrategy(B) sage: gb.add_generator(a*c + a*f + d*f + d + f) sage: gb.add_generator(b*c + b*e + c + d + 1) @@ -6782,7 +6784,7 @@ cdef class GroebnerStrategy: Still, we have that:: - sage: from polybori import groebner_basis + sage: from brial import groebner_basis sage: groebner_basis(gb) [1] """ @@ -6799,7 +6801,7 @@ cdef class GroebnerStrategy: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: gb = GroebnerStrategy(B) sage: gb.add_generator(a*c + a*f + d*f + d + f) sage: gb.add_generator(b*c + b*e + c + d + 1) @@ -6809,7 +6811,7 @@ cdef class GroebnerStrategy: sage: gb.add_generator(a*b + b + c*e + e + 1) sage: gb.add_generator(a + b + c*d + c*e + 1) - sage: from polybori import BooleanPolynomialVector + sage: from brial import BooleanPolynomialVector sage: V= BooleanPolynomialVector([b*d, a*b]) sage: list(gb.faugere_step_dense(V)) [b + c*e + e + 1, c + d*f + e + f] @@ -6874,7 +6876,7 @@ cdef class GroebnerStrategy: """ EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: gbs = GroebnerStrategy(B) sage: gbs.add_as_you_wish(a + b) @@ -6906,7 +6908,7 @@ cdef class GroebnerStrategy: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: gb = GroebnerStrategy(B) sage: gb.add_generator(a*c + a*f + d*f + d + f) sage: gb.add_generator(b*c + b*e + c + d + 1) @@ -6917,7 +6919,7 @@ cdef class GroebnerStrategy: sage: gb.variable_has_value(0) False - sage: from polybori import groebner_basis + sage: from brial import groebner_basis sage: g = groebner_basis(gb) sage: list(g) [a, b + 1, c + 1, d, e + 1, f] @@ -6946,7 +6948,7 @@ cdef class GroebnerStrategy: sage: I = B.ideal([B(f) for f in I.gens()]) sage: gb = I.groebner_basis() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: G = GroebnerStrategy(B) sage: _ = [G.add_generator(f) for f in gb] @@ -6981,7 +6983,7 @@ cdef class GroebnerStrategy: sage: B. = BooleanPolynomialRing() sage: f = B.random_element() sage: g = B.random_element() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: strat = GroebnerStrategy(B) sage: strat.add_generator(f) sage: strat.add_generator(g) @@ -7001,7 +7003,7 @@ cdef class GroebnerStrategy: EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import GroebnerStrategy + sage: from brial import GroebnerStrategy sage: G = GroebnerStrategy(B) sage: G.add_as_you_wish(a) @@ -7085,7 +7087,7 @@ class BooleanMulAction(Action): def _call_(self, left, right): """ EXAMPLES: - sage: from polybori import BooleanMonomialMonoid + sage: from brial import BooleanMonomialMonoid sage: P. = BooleanPolynomialRing(3) sage: M = BooleanMonomialMonoid(P) sage: x = M(x); xy = M(x*y); z=M(z) @@ -7144,7 +7146,7 @@ def add_up_polynomials(BooleanPolynomialVector v, BooleanPolynomial init): EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: v = BooleanPolynomialVector() sage: l = [B.random_element() for _ in range(5)] @@ -7171,7 +7173,7 @@ def red_tail(ReductionStrategy s, BooleanPolynomial p): EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: red = ReductionStrategy(B) sage: red.add_generator(x + y + 1) @@ -7192,7 +7194,7 @@ def map_every_x_to_x_plus_one(BooleanPolynomial p): sage: B. = BooleanPolynomialRing(3) sage: f = a*b + z + 1; f a*b + z + 1 - sage: from polybori import map_every_x_to_x_plus_one + sage: from brial import map_every_x_to_x_plus_one sage: map_every_x_to_x_plus_one(f) a*b + a + b + z + 1 sage: f(a+1,b+1,z+1) @@ -7227,7 +7229,7 @@ def zeros(pol, BooleSet s): This encodes the points (1,1,1,0), (1,1,0,0), (0,0,1,1) and (0,1,1,0). But of these only (1,1,0,0) evaluates to zero.:: - sage: from polybori import zeros + sage: from brial import zeros sage: zeros(f,s) {{a,b}} @@ -7261,7 +7263,7 @@ def interpolate(zero, one): sage: B = BooleanPolynomialRing(4,"x0,x1,x2,x3") sage: x = B.gen - sage: from polybori.interpolate import * + sage: from brial.interpolate import * sage: V=(x(0)+x(1)+x(2)+x(3)+1).set() sage: V @@ -7325,7 +7327,7 @@ def interpolate_smallest_lex(zero, one): sage: B = BooleanPolynomialRing(4,"x0,x1,x2,x3") sage: x = B.gen - sage: from polybori.interpolate import * + sage: from brial.interpolate import * sage: V=(x(0)+x(1)+x(2)+x(3)+1).set() We take V = {e0,e1,e2,e3,0}, where ei describes the i-th unit @@ -7416,7 +7418,7 @@ def ll_red_nf_redsb(p, BooleSet reductors): EXAMPLE:: - sage: from polybori import ll_red_nf_redsb + sage: from brial import ll_red_nf_redsb sage: B. = BooleanPolynomialRing() sage: p = a*b + c + d + 1 sage: f,g = a + c + 1, b + d + 1; @@ -7457,7 +7459,7 @@ def ll_red_nf_noredsb(BooleanPolynomial p, BooleSet reductors): EXAMPLE:: - sage: from polybori import ll_red_nf_noredsb + sage: from brial import ll_red_nf_noredsb sage: B. = BooleanPolynomialRing() sage: p = a*b + c + d + 1 sage: f,g = a + c + 1, b + d + 1; @@ -7489,7 +7491,7 @@ def ll_red_nf_noredsb_single_recursive_call(BooleanPolynomial p, BooleSet reduct EXAMPLE:: - sage: from polybori import ll_red_nf_noredsb_single_recursive_call + sage: from brial import ll_red_nf_noredsb_single_recursive_call sage: B. = BooleanPolynomialRing() sage: p = a*b + c + d + 1 sage: f,g = a + c + 1, b + d + 1; @@ -7528,7 +7530,7 @@ def if_then_else(root, a, b): EXAMPLE:: - sage: from polybori import if_then_else + sage: from brial import if_then_else sage: B = BooleanPolynomialRing(6,'x') sage: x0,x1,x2,x3,x4,x5 = B.gens() sage: f0 = x2*x3+x3 @@ -7605,7 +7607,7 @@ def top_index(s): EXAMPLE:: sage: B. = BooleanPolynomialRing(3) - sage: from polybori import top_index + sage: from brial import top_index sage: top_index(x.lm()) 0 sage: top_index(y*z) @@ -7710,7 +7712,7 @@ def gauss_on_polys(inp): EXAMPLE:: sage: B. = BooleanPolynomialRing() - sage: from polybori import * + sage: from brial import * sage: l = [B.random_element() for _ in range(B.ngens())] sage: A,v = Sequence(l,B).coefficient_matrix() sage: A @@ -7751,7 +7753,7 @@ def substitute_variables(BooleanPolynomialRing parent, vec, BooleanPolynomial po sage: B. = BooleanPolynomialRing() sage: f = a*b + c + 1 - sage: from polybori import substitute_variables + sage: from brial import substitute_variables sage: substitute_variables(B, [a,b,c],f) a*b + c + 1 sage: substitute_variables(B, [a+1,b,c],f) @@ -7767,7 +7769,7 @@ def substitute_variables(BooleanPolynomialRing parent, vec, BooleanPolynomial po sage: f = a*b + c + 1 sage: B. = BooleanPolynomialRing(order='deglex') - sage: from polybori import substitute_variables + sage: from brial import substitute_variables sage: substitute_variables(B, [x,y,z], f) * w w*x*y + w*z + w @@ -7789,7 +7791,7 @@ def set_random_seed(seed): EXAMPLE:: - sage: from polybori import random_set, set_random_seed + sage: from brial import random_set, set_random_seed sage: B. = BooleanPolynomialRing() sage: (a*b*c*d).lm() a*b*c*d @@ -7814,7 +7816,7 @@ def random_set(BooleanMonomial variables, length): EXAMPLE:: - sage: from polybori import random_set, set_random_seed + sage: from brial import random_set, set_random_seed sage: B. = BooleanPolynomialRing() sage: (a*b*c*d).lm() a*b*c*d @@ -7829,7 +7831,7 @@ def random_set(BooleanMonomial variables, length): def easy_linear_factors(BooleanPolynomial p): return new_BPV_from_PBPolyVector(p._parent, pb_easy_linear_factors(p._pbpoly)) -# todo: merge with pickling from polybori.parallel +# todo: merge with pickling from brial.parallel def unpickle_BooleanPolynomial(ring, string): """ Unpickle boolean polynomials @@ -7843,7 +7845,7 @@ def unpickle_BooleanPolynomial(ring, string): """ return ring(eval(string,ring.gens_dict())) -# todo: merge with pickling from polybori.parallel +# todo: merge with pickling from brial.parallel def unpickle_BooleanPolynomial0(ring, l): """ Unpickle boolean polynomials @@ -7855,11 +7857,11 @@ def unpickle_BooleanPolynomial0(ring, l): sage: loads(dumps(a+b)) == a+b # indirect doctest True """ - from polybori.parallel import _decode_polynomial + from brial.parallel import _decode_polynomial return _decode_polynomial(l) -# todo: merge with pickling from polybori.parallel +# todo: merge with pickling from brial.parallel def unpickle_BooleanPolynomialRing(n, names, order): """ Unpickle boolean polynomial rings. @@ -7885,7 +7887,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: [BooleConstant(i) for i in range(5)] [0, 1, 0, 1, 0] """ @@ -7895,7 +7897,7 @@ cdef class BooleConstant: """ EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: repr((BooleConstant(0),BooleConstant(1))) # indirect doctest '(0, 1)' @@ -7910,7 +7912,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(0).deg() -1 sage: BooleConstant(1).deg() @@ -7924,7 +7926,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(0).variables() () sage: BooleConstant(1).variables() @@ -7938,7 +7940,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(0).is_one() False sage: BooleConstant(1).is_one() @@ -7952,7 +7954,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(1).is_zero() False sage: BooleConstant(0).is_zero() @@ -7966,7 +7968,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(1).is_constant() True sage: BooleConstant(0).is_constant() @@ -7980,7 +7982,7 @@ cdef class BooleConstant: EXAMPLE:: - sage: from polybori import BooleConstant + sage: from brial import BooleConstant sage: BooleConstant(1).has_constant_part() True sage: BooleConstant(0).has_constant_part() @@ -8027,7 +8029,7 @@ cdef class VariableFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: fac = VariableFactory() sage: fac = VariableFactory(B) @@ -8044,7 +8046,7 @@ cdef class VariableFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: VariableFactory()(B) a @@ -8076,7 +8078,7 @@ cdef class MonomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: fac = MonomialFactory() sage: fac = MonomialFactory(B) @@ -8089,7 +8091,7 @@ cdef class MonomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: MonomialFactory()(B) 1 @@ -8111,7 +8113,7 @@ cdef class MonomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: MonomialFactory()(B) 1 @@ -8156,7 +8158,7 @@ cdef class PolynomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: fac = PolynomialFactory() @@ -8173,7 +8175,7 @@ cdef class PolynomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: PolynomialFactory().lead(a) a @@ -8187,7 +8189,7 @@ cdef class PolynomialFactory: EXAMPLE:: - sage: from polybori import * + sage: from brial import * sage: B. = BooleanPolynomialRing() sage: PolynomialFactory()(1, B) 1 diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 5983834ef69..650918aa013 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -114,7 +114,7 @@ from sage.libs.singular.function cimport RingWrap from sage.libs.singular.polynomial cimport (singular_polynomial_call, singular_polynomial_cmp, singular_polynomial_add, singular_polynomial_sub, singular_polynomial_neg, singular_polynomial_pow, singular_polynomial_mul, singular_polynomial_rmul, singular_polynomial_deg, singular_polynomial_str_with_changed_varnames, singular_polynomial_latex, singular_polynomial_str, singular_polynomial_div_coeff) import sage.libs.singular.ring -from sage.libs.singular.ring cimport singular_ring_new, singular_ring_delete, wrap_ring +from sage.libs.singular.ring cimport singular_ring_new, singular_ring_delete, wrap_ring, singular_ring_reference from sage.libs.singular.singular cimport si2sa, sa2si, overflow_check @@ -161,9 +161,9 @@ class G_AlgFactory(UniqueFactory): TEST:: sage: A. = FreeAlgebra(QQ, 3) - sage: A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) # indirect doctest - Noncommutative Multivariate Polynomial Ring in x, y, z over Rational - Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H=A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) + sage: sorted(H.relations().iteritems(),key=str) + [(y*x, x*y - z), (z*x, x*z + 2*x), (z*y, y*z - 2*y)] """ # key = (base_ring,names, c,d, order, category) @@ -327,7 +327,7 @@ cdef class NCPolynomialRing_plural(Ring): cdef RingWrap rw = ncalgebra(self._c, self._d, ring = P) # rw._output() - self._ring = rw._ring + self._ring = singular_ring_reference(rw._ring) self._ring.ShortOut = 0 self.__ngens = n @@ -1385,9 +1385,6 @@ cdef class NCPolynomial_plural(RingElement): """ return unpickle_NCPolynomial_plural, (self._parent, self.dict()) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -1411,7 +1408,7 @@ cdef class NCPolynomial_plural(RingElement): """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -1431,9 +1428,6 @@ cdef class NCPolynomial_plural(RingElement): sage: y^2 > x False -## sage: (2/3*x^2 + 1/2*y + 3) > (2/3*x^2 + 1/4*y + 10) -# True - TESTS:: sage: A. = FreeAlgebra(QQ, 3) @@ -1458,22 +1452,7 @@ cdef class NCPolynomial_plural(RingElement): sage: (x+1) > x True - -# sage: f = 3/4*x^2*y + 1/2*x + 2/7 -# sage: f > f -# False -# sage: f < f -# False -# sage: f == f -# True - -# sage: P. = PolynomialRing(GF(127), order='degrevlex') -# sage: (66*x^2 + 23) > (66*x^2 + 2) -# True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly @@ -1711,10 +1690,13 @@ cdef class NCPolynomial_plural(RingElement): The Groebner basis shows that the result is correct:: - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + """ cdef ideal *_I @@ -2954,8 +2936,12 @@ def ExteriorAlgebra(base_ring, names,order='degrevlex'): EXAMPLES:: sage: from sage.rings.polynomial.plural import ExteriorAlgebra - sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E + sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: -x*z, z*y: -y*z, y*x: -x*y} by the ideal (z^2, y^2, x^2) + sage: sorted(E.cover().domain().relations().iteritems(),key=str) + [(y*x, -x*y), (z*x, -x*z), (z*y, -y*z)] + sage: sorted(E.cover().kernel().gens(),key=str) + [x^2, y^2, z^2] sage: E.inject_variables() Defining xbar, ybar, zbar sage: x,y,z = (xbar,ybar,zbar) diff --git a/src/sage/rings/polynomial/polydict.pyx b/src/sage/rings/polynomial/polydict.pyx index 8b7ce33155f..c2e62a98ac4 100644 --- a/src/sage/rings/polynomial/polydict.pyx +++ b/src/sage/rings/polynomial/polydict.pyx @@ -28,20 +28,16 @@ AUTHORS: """ #***************************************************************************** -# Copyright (C) 2005 William Stein (wstein@ucsd.edu) -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: +# Copyright (C) 2005 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/ #***************************************************************************** + include "sage/ext/stdsage.pxi" from libc.string cimport memcpy from cpython.dict cimport * @@ -52,7 +48,6 @@ from sage.structure.element import generic_power from sage.misc.misc import cputime from sage.misc.latex import latex -import sage.rings.ring_element as ring_element cdef class PolyDict: def __init__(PolyDict self, pdict, zero=0, remove_zero=False, force_int_exponents=True, force_etuples=True): diff --git a/src/sage/rings/polynomial/polynomial_element.pxd b/src/sage/rings/polynomial/polynomial_element.pxd index 36da30a87f2..c9eedac5fb0 100644 --- a/src/sage/rings/polynomial/polynomial_element.pxd +++ b/src/sage/rings/polynomial/polynomial_element.pxd @@ -9,6 +9,7 @@ cdef class Polynomial(CommutativeAlgebraElement): cdef char _is_gen cdef CompiledPolynomialFunction _compiled cpdef Polynomial truncate(self, long n) + cpdef Polynomial inverse_series_trunc(self, long prec) cdef long _hash_c(self) except -1 cpdef constant_coefficient(self) cpdef Polynomial _new_constant_poly(self, a, Parent P) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 07f92975d10..da70b794007 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -41,7 +41,6 @@ TESTS:: cdef is_FractionField, is_RealField, is_ComplexField -cdef coerce_binop, generic_power, parent cdef ZZ, QQ, RR, CC, RDF, CDF import operator, copy, re @@ -50,14 +49,12 @@ import sage.rings.rational import sage.rings.integer import polynomial_ring import sage.rings.arith -#import sage.rings.ring_element import sage.rings.integer_ring import sage.rings.rational_field import sage.rings.finite_rings.integer_mod_ring import sage.rings.complex_field import sage.rings.fraction_field_element import sage.rings.infinity as infinity -#import sage.misc.misc as misc from sage.misc.sage_eval import sage_eval from sage.misc.latex import latex from sage.structure.factorization import Factorization @@ -75,8 +72,10 @@ from sage.rings.real_double import is_RealDoubleField, RDF from sage.rings.complex_double import is_ComplexDoubleField, CDF from sage.rings.real_mpfi import is_RealIntervalField -from sage.structure.element import RingElement, generic_power, parent -from sage.structure.element cimport Element, RingElement, ModuleElement, MonoidElement +from sage.structure.element import generic_power +from sage.structure.element cimport parent_c as parent +from sage.structure.element cimport (Element, RingElement, + ModuleElement, MonoidElement, coercion_model) from sage.rings.rational_field import QQ, is_RationalField from sage.rings.integer_ring import ZZ, is_IntegerRing @@ -433,24 +432,30 @@ cdef class Polynomial(CommutativeAlgebraElement): return self.__call__(*x, **kwds) substitute = subs - def __call__(self, *x, **kwds): + def __call__(self, *args, **kwds): """ - Evaluate polynomial at x=a. + Evaluate this polynomial. INPUT: + - ``*args`` -- ring elements, need not be in the coefficient ring of + the polynomial. The **first** positional argument is substituted + for the polynomial's indeterminate. Remaining arguments, if any, + are used **from left to right** to evaluate the coefficients. + - ``**kwds`` -- variable name-value pairs. - - ``x``: + OUTPUT: - - ring element a; need not be in the - coefficient ring of the polynomial. + The value of the polynomial at the point specified by the arguments. - - a dictionary for kwds:value pairs. If the variable name - of the polynomial is a kwds it is substituted in; - otherwise this polynomial is returned unchanged. + ALGORITHM: + By default, use Horner's method or create a + :class:`~sage.rings.polynomial.polynomial_compiled.CompiledPolynomialFunction` + depending on the polynomial's degree. - OUTPUT: the value of f at a. + Element classes may define a method called `_evaluate_polynomial` to + provide a specific evaluation algorithm for a given argument type. EXAMPLES:: @@ -505,8 +510,8 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f(x=10) # x isn't mentioned w^3 + 3*w + 2 - Nested polynomial ring elements can be called like multi-variate - polynomials. :: + Nested polynomial ring elements can be called like multivariate + polynomials. Note the order of the arguments:: sage: R. = QQ[]; S. = R[] sage: f = x+y*x+y^2 @@ -529,7 +534,15 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f(x=y) 2*y^2 + y - Polynomial ring elements can also, like multi-variate + Also observe that ``f(y0, x0)`` means ``f(x=x0)(y=y0)``, not + ``f(y=y0)(x=x0)``. The two expressions may take different values:: + + sage: f(y, x) + y^2 + x*y + x + sage: f(y)(x) + 2*x^2 + x + + Polynomial ring elements can also, like multivariate polynomials, be called with an argument that is a list or tuple containing the values to be substituted, though it is perhaps more natural to just unpack the list:: @@ -614,6 +627,50 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: g(x=3) 3*y + 1 + :: + + sage: Pol_x. = QQ[] + sage: Pol_xy. = Pol_x[] + sage: pol = 1000*x^2*y^2 + 100*y + 10*x + 1 + + sage: pol(y, 0) + 100*y + 1 + + sage: pol(~y, 0) + (y + 100)/y + + sage: pol(y=x, x=1) + 1000*x^2 + 100*x + 11 + + sage: zero = Pol_xy(0) + sage: zero(1).parent() + Univariate Polynomial Ring in x over Rational Field + + sage: zero = QQ['x'](0) + sage: a = matrix(ZZ, [[1]]) + sage: zero(a).parent() + Full MatrixSpace of 1 by 1 dense matrices over Rational Field + + sage: pol(y, x).parent() is pol(x, y).parent() is pol(y, y).parent() is Pol_xy + True + + sage: pol(x, x).parent() + Univariate Polynomial Ring in x over Rational Field + + sage: one = Pol_xy(1) + sage: one(1, 1.).parent() + Real Field with 53 bits of precision + + sage: zero = GF(2)['x'](0) + sage: zero(1.).parent() # should raise an error + Traceback (most recent call last): + TypeError: unsupported operand parent(s) for '+': ... + + sage: pol(x, y, x=1) + Traceback (most recent call last): + ... + TypeError: Wrong number of arguments + AUTHORS: - David Joyner (2005-04-10) @@ -634,105 +691,72 @@ cdef class Polynomial(CommutativeAlgebraElement): - Francis Clarke (2012-08-26): fix keyword substitution in the leading coefficient. """ - cdef long i, d = self.degree() + cdef long i + cdef long d = self.degree() + cdef Polynomial pol + + # Isolate the variable we are interested in, check remaining arguments + + a = kwds.pop(self.variable_name(), None) + if args: + if a is not None: + raise TypeError("unsupported mix of keyword and positional arguments") + if isinstance(args[0], (list, tuple)): + if len(args) > 1: + raise TypeError("invalid arguments") + args = args[0] + a, args = args[0], args[1:] + if a is None: + a = self._parent.gen() + + cst = self[0] + eval_coeffs = False + if args or kwds: + try: + # Note that we may be calling a different implementation that + # is more permissive about its arguments than we are. + cst = cst(*args, **kwds) + eval_coeffs = True + except TypeError: + if args: # bwd compat: nonsense *keyword* arguments are okay + raise TypeError("Wrong number of arguments") - if kwds: - P = self.parent() - name = P.variable_name() - if name in kwds: - if len(x) > 0: - raise ValueError("must not specify both a keyword and positional argument") - a = self(kwds[name]) - del kwds[name] - try: - return a(**kwds) - except TypeError: - return a - elif len(x) > 0: - a = self(*x) - try: - return a(**kwds) - except TypeError: - return a - else: - try: - result = self[d](**kwds) - except TypeError: - result = self[d] - a = P.gen() - i = d - 1 - while i >= 0: - try: - s = self[i](**kwds) - except TypeError: - s = self[i] - result = result * a + s - i -= 1 - return result + # Handle optimized special cases. The is_exact() clause should not be + # necessary, but power series and perhaps other inexact rings use + # is_zero() in the sense of "is zero up to the precision to which it is + # known". - if len(x) == 0: - return self + if d <= 0 or (isinstance(a, Element) and a.parent().is_exact() + and a.is_zero()): + return cst + parent(a)(0) - if isinstance(x[0], (list, tuple)): - x = x[0] - a = x[0] + # Evaluate the coefficients - result = self[d] - if len(x) > 1: - other_args = x[1:] - try: #if hasattr(result, '__call__'): - result = result(other_args) - except TypeError: #else: - raise TypeError("Wrong number of arguments") + if eval_coeffs: + pol = self.map_coefficients(lambda c: c(*args, **kwds), + new_base_ring=cst.parent()) + else: + pol = self - if d == -1: - try: - return a.parent().zero() - except AttributeError: - pass - try: # for things like the interface to the PARI C library - return a.parent()(0) - except AttributeError: - return result + # Evaluate the resulting univariate polynomial - if d == 0: - try: - return a.parent().one() * result - except AttributeError: - pass - try: # for things like the interface to the PARI C library - return a.parent()(1) * result - except AttributeError: - return result + if parent(a) is pol._parent and a.is_gen(): + return pol - if parent(a) is self._parent and a.is_gen(): - return self - elif is_FractionField(parent(a)): - try: - a_inverse = ~a - if a_inverse.denominator() == 1: - a_inverse = a_inverse.numerator() - return self.reverse()(a_inverse) / a_inverse**self.degree() - except AttributeError: - pass + try: + return a._evaluate_polynomial(pol) + except (AttributeError, NotImplementedError): + pass - i = d - 1 - if len(x) > 1: - while i >= 0: - result = result * a + self[i](other_args) - i -= 1 - elif not a and isinstance(a, Element) and a.parent().is_exact(): #without the exactness test we can run into trouble with interval fields. - c = self[0] - return c + c*a - elif (d < 4 or d > 50000) and self._compiled is None: - while i >= 0: - result = result * a + self[i] - i -= 1 - else: - if self._compiled is None: - self._compiled = CompiledPolynomialFunction(self.list()) - result = self._compiled.eval(a) - return result + if pol._compiled is None: + if d < 4 or d > 50000: + result = pol[d] + for i in xrange(d - 1, -1, -1): + result = result * a + pol[i] + return result + pol._compiled = CompiledPolynomialFunction(pol.list()) + + return pol._compiled.eval(a) def _compile(self): # For testing @@ -901,9 +925,6 @@ cdef class Polynomial(CommutativeAlgebraElement): if c: return c return 0 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __nonzero__(self): """ EXAMPLES:: @@ -948,9 +969,6 @@ cdef class Polynomial(CommutativeAlgebraElement): """ return (self.parent(), tuple(self)) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() @@ -1095,7 +1113,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: R. = QQ[] sage: f = x^3 + x sage: g = f._symbolic_(SR); g - (x^2 + 1)*x + x^3 + x sage: g(x=2) 10 @@ -1157,6 +1175,12 @@ cdef class Polynomial(CommutativeAlgebraElement): polynomial or an ideal (for consistency with inverse_mod in other rings). + .. SEEALSO:: + + If you are only interested in the inverse modulo a monomial `x^k` + then you might use the specialized method + :meth:`inverse_series_trunc` which is much faster. + EXAMPLES:: sage: S. = QQ[] @@ -1255,6 +1279,86 @@ cdef class Polynomial(CommutativeAlgebraElement): else: raise ValueError("Impossible inverse modulo") + cpdef Polynomial inverse_series_trunc(self, long prec): + r""" + Return a polynomial approximation of precision ``prec`` of the inverse + series of this polynomial. + + .. SEEALSO:: + + The method :meth:`inverse_mod` allows more generally to invert this + polynomial with respect to any ideal. + + EXAMPLES:: + + sage: x = polygen(ZZ) + sage: s = (1+x).inverse_series_trunc(5) + sage: s + x^4 - x^3 + x^2 - x + 1 + sage: s * (1+x) + x^5 + 1 + + Note that the constant coefficient needs to be a unit:: + + sage: ZZx. = ZZ[] + sage: ZZxy. = ZZx[] + sage: (1+x + y**2).inverse_series_trunc(4) + Traceback (most recent call last): + ... + ValueError: constant term x + 1 is not a unit + sage: (1+x + y**2).change_ring(ZZx.fraction_field()).inverse_series_trunc(4) + (-1/(x^2 + 2*x + 1))*y^2 + 1/(x + 1) + + The method works over any polynomial ring:: + + sage: R = Zmod(4) + sage: Rx. = R[] + sage: Rxy. = Rx[] + + sage: p = 1 + (1+2*x)*y + x**2*y**4 + sage: q = p.inverse_series_trunc(10) + sage: (p*q).truncate(11) + (2*x^4 + 3*x^2 + 3)*y^10 + 1 + + Even noncommutative ones:: + + sage: M = MatrixSpace(ZZ,2) + sage: x = polygen(M) + sage: p = M([1,2,3,4])*x^3 + M([-1,0,0,1])*x^2 + M([1,3,-1,0])*x + M.one() + sage: q = p.inverse_series_trunc(5) + sage: (p*q).truncate(5) == M.one() + True + sage: q = p.inverse_series_trunc(13) + sage: (p*q).truncate(13) == M.one() + True + + TESTS:: + + sage: x = polygen(ZZ['a','b']) + sage: (x+1).inverse_series_trunc(0) + Traceback (most recent call last): + ... + ValueError: the precision must be positive, got 0 + """ + if prec <= 0: + raise ValueError("the precision must be positive, got {}".format(prec)) + + if not self[0].is_unit(): + raise ValueError("constant term {} is not a unit".format(self[0])) + + R = self._parent + A = R.base_ring() + try: + first_coeff = self[0].inverse_of_unit() + except AttributeError: + first_coeff = A(~self[0]) + + current = R(first_coeff) + for next_prec in sage.misc.misc.newton_method_sizes(prec)[1:]: + z = current._mul_trunc_(self, next_prec)._mul_trunc_(current, next_prec) + current = current + current - z + return current + def __long__(self): """ EXAMPLES:: @@ -1362,7 +1466,7 @@ cdef class Polynomial(CommutativeAlgebraElement): ordinary multiplication, and it's frequently used, so subclasses may choose to provide a specialised squaring routine. - - Perhaps this even belongs at a lower level? ring_element or + - Perhaps this even belongs at a lower level? RingElement or something? AUTHORS: @@ -1842,6 +1946,31 @@ cdef class Polynomial(CommutativeAlgebraElement): 3*t^3 + (2*x + 3)*t^2 + (2*x + 2)*t + 2*x + 2 sage: pow(f, 10**7, h) 4*x*t^3 + 2*x*t^2 + 4*x*t + 4 + + Check that :trac:`18457` is fixed:: + + sage: R. = PolynomialRing(GF(5), sparse=True) + sage: (1+x)^(5^10) # used to hang forever + x^9765625 + 1 + sage: S. = GF(3)[] + sage: R1. = PolynomialRing(S, sparse=True) + sage: (1+x+t)^(3^10) + x^59049 + t^59049 + 1 + sage: R2. = PolynomialRing(S, sparse=False) + sage: (1+x+t)^(3^10) + x^59049 + t^59049 + 1 + + Check that the algorithm used is indeed correct:: + + sage: from sage.structure.element import generic_power + sage: R1 = PolynomialRing(GF(8,'a'), 'x') + sage: R2 = PolynomialRing(GF(9,'b'), 'x', sparse=True) + sage: R3 = PolynomialRing(R2, 'y') + sage: R4 = PolynomialRing(R1, 'y', sparse=True) + sage: for d in range(20,40): # long time + ....: for R in [R1, R2, R3, R3]: + ....: a = R.random_element() + ....: assert a^d == generic_power(a,d) """ if type(right) is not Integer: try: @@ -1851,12 +1980,12 @@ cdef class Polynomial(CommutativeAlgebraElement): if self.degree() <= 0: return self.parent()(self[0]**right) - elif right < 0: + if right < 0: return (~self)**(-right) - elif modulus: + if modulus: from sage.rings.arith import power_mod return power_mod(self, right, modulus) - elif (self) == self.parent().gen(): # special case x**n should be faster! + if (self).is_gen(): # special case x**n should be faster! P = self.parent() R = P.base_ring() if P.is_sparse(): @@ -1864,8 +1993,34 @@ cdef class Polynomial(CommutativeAlgebraElement): else: v = [R.zero()]*right + [R.one()] return self.parent()(v, check=False) - else: - return generic_power(self,right) + if right > 20: # no gain below + p = self.parent().characteristic() + if p > 0 and p <= right and (self.base_ring() in sage.categories.integral_domains.IntegralDomains() or p.is_prime()): + x = self.parent().gen() + one = self.parent().one() + ret = one + e = 1 + q = right + sparse = self.parent().is_sparse() + if sparse: + d = self.dict() + else: + c = self.list() + while q > 0: + q, r = q.quo_rem(p) + if r != 0: + if sparse: + tmp = self.parent()({e*k : d[k]**e for k in d}) + else: + tmp = [0] * (e * len(c) - e + 1) + for i in range(len(c)): + tmp[e*i] = c[i]**e + tmp = self.parent()(tmp) + ret *= generic_power(tmp, r, one=one) + e *= p + return ret + + return generic_power(self,right) def _pow(self, right): # TODO: fit __pow__ into the arithmetic structure @@ -2966,7 +3121,7 @@ cdef class Polynomial(CommutativeAlgebraElement): .. NOTE:: - The integral is always chosen so the constant term is 0. + The integral is always chosen so that the constant term is 0. EXAMPLES:: @@ -3036,17 +3191,59 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f = x*t +5*t^2 sage: f.integral(x) 5*x*t^2 + 1/2*x^2*t + + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: Sx. = ZZ[] + sage: Sxy. = Sx[] + sage: Sxyz. = Sxy[] + sage: p = 1 + x*y + x*z + y*z^2 + sage: q = p.integral() + sage: q + 1/3*y*z^3 + 1/2*x*z^2 + (x*y + 1)*z + sage: q.parent() + Univariate Polynomial Ring in z over Univariate Polynomial Ring in y + over Univariate Polynomial Ring in x over Rational Field + sage: q.derivative() == p + True + sage: p.integral(y) + 1/2*y^2*z^2 + x*y*z + 1/2*x*y^2 + y + sage: p.integral(y).derivative(y) == p + True + sage: p.integral(x).derivative(x) == p + True + + Check that it works with non-integral domains (:trac:`18600`):: + + sage: x = polygen(Zmod(4)) + sage: p = x**4 + 1 + sage: p.integral() + x^5 + x + sage: p.integral().derivative() == p + True """ - if var is not None and var != self._parent.gen(): + R = self._parent + + # TODO: + # calling the coercion model bin_op is much more accurate than using the + # true division (which is bypassed by polynomials). But it does not work + # in all cases!! + cm = coercion_model + try: + S = cm.bin_op(R.one(), ZZ.one(), operator.div).parent() + Q = S.base_ring() + except TypeError: + Q = (R.base_ring().one()/ZZ.one()).parent() + S = R.change_ring(Q) + if var is not None and var != R.gen(): # call integral() recursively on coefficients - return self._parent([coeff.integral(var) for coeff in self.list()]) - cdef Py_ssize_t n, degree = self.degree() - R = self.parent() - Q = (self.constant_coefficient()/1).parent() - coeffs = self.list() - v = [0] + [coeffs[n]/(n+1) for n from 0 <= n <= degree] - S = R.change_ring(Q) - return S(v) + return S([coeff.integral(var) for coeff in self]) + cdef Py_ssize_t n + zero = Q.zero() + p = [zero] + [cm.bin_op(Q(self[n]), n+1, operator.div) if self[n] else zero for n in range(self.degree()+1)] + return S(p) def dict(self): """ @@ -3799,6 +3996,72 @@ cdef class Polynomial(CommutativeAlgebraElement): raise NotImplementedError("splitting_field() is only implemented over number fields and finite fields") + def pseudo_quo_rem(self,other): + """ + Compute the pseudo-division of two polynomials. + + INPUT: + + - ``other`` -- a nonzero polynomial + + OUTPUT: + + `Q` and `R` such that `l^{m-n+1} \mathrm{self} = Q \cdot\mathrm{other} + R` + where `m` is the degree of this polynomial, `n` is the degree of + ``other``, `l` is the leading coefficient of ``other``. The result is + such that `\deg(R) < \deg(\mathrm{other})`. + + ALGORITHM: + + Algorithm 3.1.2 in [GTM138]_. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^4 + 6*x^3 + x^2 - x + 2 + sage: q = 2*x^2 - 3*x - 1 + sage: (quo,rem)=p.pseudo_quo_rem(q); quo,rem + (4*x^2 + 30*x + 51, 175*x + 67) + sage: 2^(4-2+1)*p == quo*q + rem + True + + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if other.is_zero(): + raise ZeroDivisionError("Pseudo-division by zero is not possible") + + # if other is a constant, then R = 0 and Q = self * other^(deg(self)) + if other in self.parent().base_ring(): + return (self * other**(self.degree()), self.parent().zero()) + + R = self + B = other + Q = self.parent().zero() + e = self.degree() - other.degree() + 1 + d = B.leading_coefficient() + + while not R.degree() < B.degree(): + c = R.leading_coefficient() + diffdeg = R.degree() - B.degree() + Q = d*Q + self.parent()(c).shift(diffdeg) + R = d*R - c*B.shift(diffdeg) + e -= 1 + + q = d**e + return (q*Q,q*R) + @coerce_binop def gcd(self, other): """ @@ -3831,23 +4094,23 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: (2*x).gcd(0) x - One can easily add gcd functionality to new rings by providing a - method ``_gcd_univariate_polynomial``:: + One can easily add gcd functionality to new rings by providing a method + ``_gcd_univariate_polynomial``:: - sage: R. = QQ[] - sage: S. = R[] - sage: h1 = y*x - sage: h2 = y^2*x^2 - sage: h1.gcd(h2) + sage: O = ZZ[-sqrt(5)] + sage: R. = O[] + sage: a = O.1 + sage: p = x + a + sage: q = x^2 - 5 + sage: p.gcd(q) Traceback (most recent call last): ... - NotImplementedError: Univariate Polynomial Ring in x over Rational Field does not provide a gcd implementation for univariate polynomials - sage: T. = QQ[] - sage: R._gcd_univariate_polynomial = lambda f,g: S(T(f).gcd(g)) - sage: h1.gcd(h2) - x*y - sage: del R._gcd_univariate_polynomial - + NotImplementedError: Order in Number Field in a with defining polynomial x^2 - 5 does not provide a gcd implementation for univariate polynomials + sage: S. = O.number_field()[] + sage: O._gcd_univariate_polynomial = lambda f,g : R(S(f).gcd(S(g))) + sage: p.gcd(q) + x + a + sage: del O._gcd_univariate_polynomial """ if hasattr(self.base_ring(), '_gcd_univariate_polynomial'): return self.base_ring()._gcd_univariate_polynomial(self, other) @@ -4308,7 +4571,6 @@ cdef class Polynomial(CommutativeAlgebraElement): # sylvester_matrix() in multi_polynomial.pyx. if self.parent() != right.parent(): - coercion_model = sage.structure.element.get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) variable = a.parent()(self.variables()[0]) #We add the variable to cover the case that right is a multivariate @@ -4425,6 +4687,15 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f.is_unit() False + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: c = x^2^100 + 1 + sage: c.is_unit() + False + EXERCISE (Atiyah-McDonald, Ch 1): Let `A[x]` be a polynomial ring in one variable. Then `f=\sum a_i x^i \in A[x]` is a unit if and only if @@ -4432,8 +4703,13 @@ cdef class Polynomial(CommutativeAlgebraElement): nilpotent. """ if self.degree() > 0: - for i in range(1,self.degree()+1): - if not self[i].is_nilpotent(): + try: + if self._parent.base_ring().is_integral_domain(): + return False + except NotImplementedError: + pass + for c in self.coefficients()[1:]: + if not c.is_nilpotent(): return False return self[0].is_unit() @@ -4458,9 +4734,17 @@ cdef class Polynomial(CommutativeAlgebraElement): polynomial ring in one variable. Then `f=\sum a_i x^i \in A[x]` is nilpotent if and only if every `a_i` is nilpotent. + + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(Zmod(4), sparse=True) + sage: (2*x^2^100 + 2).is_nilpotent() + True """ - for i in range(self.degree()+1): - if not self[i].is_nilpotent(): + for c in self.coefficients(): + if not c.is_nilpotent(): return False return True @@ -4682,17 +4966,25 @@ cdef class Polynomial(CommutativeAlgebraElement): Traceback (most recent call last): ... ValueError: n must be at least 0 + + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: (x^2^100 + x^8 - 1).padded_list(10) + [-1, 0, 0, 0, 0, 0, 0, 0, 1, 0] """ - v = self.list() if n is None: - return v + return self.list() if n < 0: raise ValueError("n must be at least 0") - if len(v) < n: + if self.degree() < n: + v = self.list() z = self._parent.base_ring().zero() return v + [z]*(n - len(v)) else: - return v[:int(n)] + return self[:int(n)].padded_list(n) def coeffs(self): r""" @@ -4786,12 +5078,16 @@ cdef class Polynomial(CommutativeAlgebraElement): raise ValueError("given variable is not the generator of parent.") raise NotImplementedError - def newton_slopes(self, p): + def newton_slopes(self, p, lengths=False): """ Return the `p`-adic slopes of the Newton polygon of self, when this makes sense. - OUTPUT: list of rational numbers + OUTPUT: + + If `lengths` is `False`, a list of rational numbers. If `lengths` is + `True`, a list of couples `(s,l)` where `s` is the slope and `l` the + length of the corresponding segment in the Newton polygon. EXAMPLES:: @@ -4799,12 +5095,46 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f = x^3 + 2 sage: f.newton_slopes(2) [1/3, 1/3, 1/3] + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^5 + 6*x^2 + 4 + sage: p.newton_slopes(2) + [1/2, 1/2, 1/3, 1/3, 1/3] + sage: p.newton_slopes(2, lengths=True) + [(1/2, 2), (1/3, 3)] + sage: (x^2^100 + 27).newton_slopes(3, lengths=True) + [(3/1267650600228229401496703205376, 1267650600228229401496703205376)] + + ALGORITHM: Uses PARI if `lengths` is `False`. + """ + if not lengths: + f = self._pari_() + v = list(f.newtonpoly(p)) + return [sage.rings.rational.Rational(x) for x in v] + + e = self.exponents() + c = self.coefficients() + if len(e) == 0: return [] + if len(e) == 1: + if e[0] == 0: return [] + else: return [(infinity.infinity, e[0])] + + if e[0] == 0: slopes = [] + else: slopes = [(infinity.infinity, e[0])] + + points = [(e[0], c[0].valuation(p)), (e[1], c[1].valuation(p))] + slopes.append((-(c[1].valuation(p)-c[0].valuation(p))/(e[1] - e[0]), e[1]-e[0])) + for i in range(2, len(e)): + v = c[i].valuation(p) + s = -(v-points[-1][1])/(e[i]-points[-1][0]) + while len(slopes) > 0 and s >= slopes[-1][0]: + slopes = slopes[:-1] + points = points[:-1] + s = -(v-points[-1][1])/(e[i]-points[-1][0]) + slopes.append((s,e[i]-points[-1][0])) + points.append((e[i],v)) + + return slopes - ALGORITHM: Uses PARI. - """ - f = self._pari_() - v = list(f.newtonpoly(p)) - return [sage.rings.rational.Rational(x) for x in v] ##################################################################### # Conversions to other systems @@ -6613,15 +6943,10 @@ cdef class Polynomial(CommutativeAlgebraElement): False sage: R(0).is_squarefree() False - - This can obviously fail if the ring does not implement ``gcd()``:: - sage: S. = QQ[] sage: R. = S[] - sage: (2*x*y).is_squarefree() # R does not provide a gcd implementation - Traceback (most recent call last): - ... - NotImplementedError: Univariate Polynomial Ring in y over Rational Field does not provide a gcd implementation for univariate polynomials + sage: (2*x*y).is_squarefree() + True sage: (2*x*y^2).is_squarefree() False @@ -6800,16 +7125,21 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f.norm(int(2)) 2.00000000000000 + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: (x^2^100 + 1).norm(1) + 2.00000000000000 + AUTHORS: - Didier Deshommes - - William Stein: fix bugs, add definition, etc. """ if p <= 0 : raise ValueError("The degree of the norm must be positive") - coeffs = self.coefficients(sparse=False) + coeffs = self.coefficients() if p == infinity.infinity: return RR(max([abs(i) for i in coeffs])) @@ -7596,15 +7926,9 @@ cdef class Polynomial_generic_dense(Polynomial): del x[n] n -= 1 - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __getitem__(self, n): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index a6691ef9a36..1e11a5e2bbf 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -40,12 +40,18 @@ from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.rings.integer import Integer class Polynomial_generic_sparse(Polynomial): """ A generic sparse polynomial. + The ``Polynomial_generic_sparse`` class defines functionality for sparse + polynomials over any base ring. A sparse polynomial is represented using a + dictionary which maps each exponent to the corresponding coefficient. The + coefficients must never be zero. + EXAMPLES:: sage: R. = PolynomialRing(PolynomialRing(QQ, 'y'), sparse=True) @@ -158,7 +164,9 @@ def exponents(self): sage: f.exponents() [0, 1997, 10000] """ - return [c[0] for c in sorted(self.__coeffs.iteritems())] + keys = self.__coeffs.keys() + keys.sort() + return keys def valuation(self): """ @@ -223,6 +231,71 @@ def _derivative(self, var=None): del d[-1] return P(d) + def integral(self, var=None): + """ + Return the integral of this polynomial. + + By default, the integration variable is the variable of the + polynomial. + + Otherwise, the integration variable is the optional parameter ``var`` + + .. NOTE:: + + The integral is always chosen so that the constant term is 0. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: (1 + 3*x^10 - 2*x^100).integral() + -2/101*x^101 + 3/11*x^11 + x + + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: (x^2^100).integral() + 1/1267650600228229401496703205377*x^1267650600228229401496703205377 + + Check the correctness when the base ring is a polynomial ring:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: S. = PolynomialRing(R, sparse=True) + sage: (x*t+1).integral() + 1/2*x*t^2 + t + sage: (x*t+1).integral(x) + 1/2*x^2*t + x + + Check the correctness when the base ring is not an integral domain:: + + sage: R. = PolynomialRing(Zmod(4), sparse=True) + sage: (x^4 + 2*x^2 + 3).integral() + x^5 + 2*x^3 + 3*x + sage: x.integral() + Traceback (most recent call last): + ... + ZeroDivisionError: Inverse does not exist. + """ + R = self.parent() + # TODO: + # calling the coercion model bin_op is much more accurate than using the + # true division (which is bypassed by polynomials). But it does not work + # in all cases!! + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + import operator + try: + Q = cm.bin_op(R.one(), ZZ.one(), operator.div).parent() + except TypeError: + F = (R.base_ring().one()/ZZ.one()).parent() + Q = R.change_ring(F) + + if var is not None and var != R.gen(): + return Q({k:v.integral(var) for k,v in self.__coeffs.iteritems()}, check=False) + + return Q({ k+1:v/(k+1) for k,v in self.__coeffs.iteritems()}, check=False) + def _dict_unsafe(self): """ Return unsafe access to the underlying dictionary of coefficients. @@ -530,6 +603,75 @@ def _lmul_(self, right): output.__normalize() return output + def _cmp_(self, other): + """ + Compare this polynomial with other. + + Polynomials are first compared by degree, then in dictionary order + starting with the coefficient of largest degree. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: 3*x^100 - 12 > 12*x + 5 + True + sage: 3*x^100 - 12 > 3*x^100 - x^50 + 5 + True + sage: 3*x^100 - 12 < 3*x^100 - x^50 + 5 + False + sage: x^100 + x^10 - 1 < x^100 + x^10 + True + sage: x^100 < x^100 - x^10 + False + + TESTS:: + + sage: R. = PolynomialRing(QQ, sparse=True) + sage: 2*x^2^500 > x^2^500 + True + + sage: Rd = PolynomialRing(ZZ, 'x', sparse=False) + sage: Rs = PolynomialRing(ZZ, 'x', sparse=True) + sage: for _ in range(100): + ....: pd = Rd.random_element() + ....: qd = Rd.random_element() + ....: assert cmp(pd,qd) == cmp(Rs(pd), Rs(qd)) + """ + d1 = self.__coeffs + keys1 = d1.keys() + keys1.sort(reverse=True) + + d2 = other.__coeffs + keys2 = d2.keys() + keys2.sort(reverse=True) + + zero = self.base_ring().zero() + + if not keys1 and not keys2: return 0 + if not keys1: return -1 + if not keys2: return 1 + + c = cmp(keys1[0], keys2[0]) + if c: return c + c = cmp(d1[keys1[0]],d2[keys2[0]]) + if c: return c + + for k1, k2 in zip(keys1[1:], keys2[1:]): + c = cmp(k1, k2) + if c > 0: + return cmp(d1[k1], zero) + elif c < 0: + return cmp(zero, d2[k2]) + c = cmp (d1[k1], d2[k2]) + if c: return c + + n1 = len(keys1) + n2 = len(keys2) + c = cmp(n1, n2) + if c > 0: return cmp(d1[keys1[n2]], zero) + elif c < 0: return cmp(zero, d2[keys2[n1]]) + return 0 + def shift(self, n): r""" Returns this polynomial multiplied by the power `x^n`. If `n` is negative, @@ -550,22 +692,32 @@ def shift(self, n): sage: p.shift(2) x^100002 + 2*x^3 + 4*x^2 + TESTS: + + Check that :trac:`18600` is fixed:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^2^100 - 5 + sage: p.shift(10) + x^1267650600228229401496703205386 - 5*x^10 + sage: p.shift(-10) + x^1267650600228229401496703205366 + sage: p.shift(1.5) + Traceback (most recent call last): + ... + TypeError: Attempt to coerce non-integral RealNumber to Integer + AUTHOR: - David Harvey (2006-08-06) """ - n = int(n) + n = ZZ(n) if n == 0: return self if n > 0: - output = {} - for (index, coeff) in self.__coeffs.iteritems(): - output[index + n] = coeff + output = {index+n: coeff for index, coeff in self.__coeffs.iteritems()} return self.parent()(output, check=False) if n < 0: - output = {} - for (index, coeff) in self.__coeffs.iteritems(): - if index + n >= 0: - output[index + n] = coeff + output = {index+n:coeff for index, coeff in self.__coeffs.iteritems() if index + n >= 0} return self.parent()(output, check=False) @coerce_binop @@ -650,6 +802,110 @@ def quo_rem(self, other): rem = rem[:rem.degree()] - c*other[:d].shift(e) return (quo,rem) + def gcd(self,other,algorithm=None): + """ + Return the gcd of this polynomial and ``other`` + + INPUT: + + - ``other`` -- a polynomial defined over the same ring as this + polynomial. + + ALGORITHM: + + Two algorithms are provided: + + - ``generic``: Uses the generic implementation, which depends on the + base ring being a UFD or a field. + - ``dense``: The polynomials are converted to the dense representation, + their gcd is computed and is converted back to the sparse + representation. + + Default is ``dense`` for polynomials over ZZ and ``generic`` in the + other cases. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ,sparse=True) + sage: p = x^6 + 7*x^5 + 8*x^4 + 6*x^3 + 2*x^2 + x + 2 + sage: q = 2*x^4 - x^3 - 2*x^2 - 4*x - 1 + sage: gcd(p,q) + x^2 + x + 1 + sage: gcd(p, q, algorithm = "dense") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "generic") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "foobar") + Traceback (most recent call last): + ... + ValueError: Unknown algorithm 'foobar' + """ + + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.rings.arith import lcm + + if algorithm is None: + if self.base_ring() == ZZ: + algorithm = "dense" + else: + algorithm = "generic" + if algorithm=="dense": + S = self.parent() + # FLINT is faster but a bug makes the conversion extremely slow, + # so NTL is used in those cases where the conversion is too slow. Cf + # + sd = self.degree() + od = other.degree() + if max(sd,od)<100 or \ + min(len(self.__coeffs)/sd, len(other.__coeffs)/od)>.06: + implementation="FLINT" + else: + implementation="NTL" + D = PolynomialRing(S.base_ring(),'x',implementation=implementation) + g = D(self).gcd(D(other)) + return S(g) + elif algorithm=="generic": + return Polynomial.gcd(self,other) + else: + raise ValueError("Unknown algorithm '%s'" % algorithm) + + def reverse(self, degree=None): + """ + Return this polynomial but with the coefficients reversed. + + If an optional degree argument is given the coefficient list will be + truncated or zero padded as necessary and the reverse polynomial will + have the specified degree. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^4 + 2*x^2^100 + sage: p.reverse() + x^1267650600228229401496703205372 + 2 + sage: p.reverse(10) + x^6 + """ + if degree is None: + degree = self.degree() + if not isinstance(degree, (int,Integer)): + raise ValueError("degree argument must be a nonnegative integer, got %s"%degree) + d = {degree-k: v for k,v in self.__coeffs.iteritems() if degree >= k} + return self.parent()(d, check=False) + + def truncate(self, n): + """ + Return the polynomial of degree `< n` equal to `self` modulo `x^n`. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: (x^11 + x^10 + 1).truncate(11) + x^10 + 1 + sage: (x^2^500 + x^2^100 + 1).truncate(2^101) + x^1267650600228229401496703205376 + 1 + """ + return self[:n] class Polynomial_generic_domain(Polynomial, IntegralDomainElement): def __init__(self, parent, is_gen=False, construct=False): diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index d5a917d18dd..78f9ec8f85a 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -11,7 +11,7 @@ AUTHORS: TESTS: -We check that the buggy gcd is fixed (see trac:`17816`):: +We check that the buggy gcd is fixed (see :trac:`17816`):: sage: R. = ZZ[] sage: X = 3*q^12 - 8*q^11 - 24*q^10 - 48*q^9 - 84*q^8 - 92*q^7 - 92*q^6 - 70*q^5 - 50*q^4 - 27*q^3 - 13*q^2 - 4*q - 1 @@ -1018,6 +1018,58 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sig_off() return res + cpdef Polynomial inverse_series_trunc(self, long prec): + r""" + Return a polynomial approximation of precision ``prec`` of the inverse + series of this polynomial. + + EXAMPLES:: + + sage: x = polygen(ZZ) + sage: p = 1+x+2*x**2 + sage: q5 = p.inverse_series_trunc(5) + sage: q5 + -x^4 + 3*x^3 - x^2 - x + 1 + sage: p*q5 + -2*x^6 + 5*x^5 + 1 + + sage: q100 = p.inverse_series_trunc(100) + sage: (q100 * p).truncate(100) + 1 + + TESTS:: + + sage: ZZ['x'].zero().inverse_series_trunc(4) + Traceback (most recent call last): + ... + ValueError: constant term is zero + sage: ZZ['x'](2).inverse_series_trunc(4) + Traceback (most recent call last): + ... + ValueError: constant term 2 is not a unit + sage: x = polygen(ZZ) + sage: (x+1).inverse_series_trunc(0) + Traceback (most recent call last): + ... + ValueError: the precision must be positive, got 0 + """ + if prec <= 0: + raise ValueError("the precision must be positive, got {}".format(prec)) + + if fmpz_poly_degree(self.__poly) == -1: + raise ValueError("constant term is zero") + cdef fmpz_t c = fmpz_poly_get_coeff_ptr(self.__poly, 0) + if fmpz_cmp_ui(c, 1) and fmpz_cmp_ui(c, -1): + raise ValueError("constant term {} is not a unit".format(self[0])) + + cdef Polynomial_integer_dense_flint res = self._new() + if prec <= 0: + return res + sig_on() + fmpz_poly_inv_series(res.__poly, self.__poly, prec) + sig_off() + return res + cpdef _unsafe_mutate(self, long n, value): r""" Sets coefficient of `x^n` to value. diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring_element.py b/src/sage/rings/polynomial/polynomial_quotient_ring_element.py index b122027bf01..c1208a607d6 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring_element.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring_element.py @@ -74,17 +74,21 @@ - William Stein """ -########################################################################### +#***************************************************************************** # Copyright (C) 2005, 2007 William Stein -# Distributed under the terms of the GNU General Public License (GPL) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -########################################################################### +#***************************************************************************** -import sage.rings.commutative_ring_element as commutative_ring_element +from sage.structure.element import CommutativeRingElement import sage.rings.number_field.number_field_rel as number_field_rel import sage.rings.polynomial.polynomial_singular_interface as polynomial_singular_interface -class PolynomialQuotientRingElement(polynomial_singular_interface.Polynomial_singular_repr,commutative_ring_element.CommutativeRingElement): +class PolynomialQuotientRingElement(polynomial_singular_interface.Polynomial_singular_repr, CommutativeRingElement): """ Element of a quotient of a polynomial ring. @@ -118,7 +122,7 @@ def __init__(self, parent, polynomial, check=True): from sage.rings.polynomial.polynomial_quotient_ring import PolynomialQuotientRing_generic from sage.rings.polynomial.polynomial_element import Polynomial - commutative_ring_element.CommutativeRingElement.__init__(self, parent) + CommutativeRingElement.__init__(self, parent) if check: if not isinstance(parent, PolynomialQuotientRing_generic): raise TypeError("parent must be a polynomial quotient ring") diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index 422e8740cb6..37d2dfa9678 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -1232,6 +1232,54 @@ cdef class Polynomial_rational_flint(Polynomial): sig_off() return res + cpdef Polynomial inverse_series_trunc(self, long prec): + r""" + Return a polynomial approximation of precision ``prec`` of the inverse + series of this polynomial. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: p = 2 + x - 3/5*x**2 + sage: q5 = p.inverse_series_trunc(5) + sage: q5 + 151/800*x^4 - 17/80*x^3 + 11/40*x^2 - 1/4*x + 1/2 + sage: q5 * p + -453/4000*x^6 + 253/800*x^5 + 1 + + sage: q100 = p.inverse_series_trunc(100) + sage: (q100 * p).truncate(100) + 1 + + TESTS:: + + sage: (0*x).inverse_series_trunc(4) + Traceback (most recent call last): + ... + ValueError: constant term is zero + sage: x.inverse_series_trunc(4) + Traceback (most recent call last): + ... + ValueError: constant term is zero + sage: (x+1).inverse_series_trunc(0) + Traceback (most recent call last): + ... + ValueError: the precision must be positive, got 0 + """ + if prec <= 0: + raise ValueError("the precision must be positive, got {}".format(prec)) + if fmpq_poly_degree(self.__poly) == -1 or \ + fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term is zero") + + cdef Polynomial_rational_flint res = self._new() + if prec <= 0: + return res + sig_on() + fmpq_poly_inv_series(res.__poly, self.__poly, prec) + sig_off() + return res + def __mod__(Polynomial_rational_flint self, right): """ Returns the remainder of self and right obtain by Euclidean division. @@ -1475,6 +1523,416 @@ cdef class Polynomial_rational_flint(Polynomial): sig_off() return primitive.is_irreducible() + ####################################################### + # Transcendental functions (return truncated series) # + ####################################################### + + def _log_series(self, long prec): + r""" + Return the logarithm of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: (1+x)._log_series(5) + -1/4*x^4 + 1/3*x^3 - 1/2*x^2 + x + + sage: (1/3*x^3 - 2*x^2 + x + 1)._log_series(10)._exp_series(10) + 1/3*x^3 - 2*x^2 + x + 1 + + TESTS:: + + sage: x._log_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 1 in order to take logarithm + sage: (0*x)._log_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 1 in order to take logarithm + """ + if fmpq_poly_degree(self.__poly) == -1 or \ + fmpz_cmp(fmpq_poly_numref(self.__poly), + fmpq_poly_denref(self.__poly)): + raise ValueError("constant term should be 1 in order to take logarithm") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_log_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _exp_series(self, long prec): + r""" + Return the exponential of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._exp_series(5) + 1/24*x^4 + 1/6*x^3 + 1/2*x^2 + x + 1 + sage: (1/3*x^4 - 3*x^2 - 1/2*x)._exp_series(5)._log_series(5) + 1/3*x^4 - 3*x^2 - 1/2*x + + TESTS:: + + sage: (x+1)._exp_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take exponential + sage: (0*x)._exp_series(5) + 1 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.one() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take exponential") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_exp_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _atan_series(self, long prec): + r""" + Return the arctangent of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._atan_series(7) + 1/5*x^5 - 1/3*x^3 + x + sage: (1/5*x^3 + 2*x^2 - x)._atan_series(10)._tan_series(10) + 1/5*x^3 + 2*x^2 - x + + TESTS:: + + sage: (1+x)._atan_series(3) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take arctangent + sage: (0*x)._atan_series(10) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take arctangent") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_atan_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _atanh_series(self, long prec): + r""" + Return the hyperbolic arctangent of this polynomial up to precision + ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._atanh_series(7) + 1/5*x^5 + 1/3*x^3 + x + sage: (1/5*x^3 + 2*x^2 - x)._atanh_series(10)._tanh_series(10) + 1/5*x^3 + 2*x^2 - x + + TESTS:: + + sage: (0*x)._atanh_series(10) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take hyperbolic arctangent") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_atanh_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _asin_series(self, long prec): + r""" + Return the arcsine of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._asin_series(7) + 3/40*x^5 + 1/6*x^3 + x + sage: (1/5*x^3 + 2*x^2 - x)._asin_series(10)._sin_series(10) + 1/5*x^3 + 2*x^2 - x + + TESTS:: + + sage: (x+1)._asin_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take arcsine + sage: (0*x)._asin_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take arcsine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_asin_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _asinh_series(self, long prec): + r""" + Return the hyperbolic arcsine of this polynomial up to precision + ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._asinh_series(7) + 3/40*x^5 - 1/6*x^3 + x + sage: (1/5*x^3 + 2*x^2 - x)._asinh_series(10)._sinh_series(10) + 1/5*x^3 + 2*x^2 - x + + TESTS:: + + sage: (x+1)._asinh_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take hyperbolic arcsine + sage: (0*x)._asinh_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take hyperbolic arcsine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_asinh_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _tan_series(self, long prec): + r""" + Return the tangent of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._tan_series(8) + 17/315*x^7 + 2/15*x^5 + 1/3*x^3 + x + sage: (1/5*x^3 + 2*x^2 - x)._tan_series(10)._atan_series(10) + 1/5*x^3 + 2*x^2 - x + + TESTS:: + + sage: (x+1)._tan_series(10) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take tangent + sage: (0*x)._tan_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take tangent") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_tan_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _sin_series(self, long prec): + r""" + Return the sine of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._sin_series(8) + -1/5040*x^7 + 1/120*x^5 - 1/6*x^3 + x + sage: (1/5*x^3 - 2*x^2 + 1/2*x)._sin_series(10)._asin_series(10) + 1/5*x^3 - 2*x^2 + 1/2*x + + TESTS:: + + sage: (x+1)._sin_series(10) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take sine + sage: (0*x)._sin_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take sine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_sin_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _cos_series(self, long prec): + r""" + Return the cosine of this polynomial up to precision ``prec`` + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._cos_series(10) + 1/40320*x^8 - 1/720*x^6 + 1/24*x^4 - 1/2*x^2 + 1 + + TESTS:: + + sage: (x+1)._cos_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take cosine + sage: (0*x)._cos_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take cosine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_cos_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _sinh_series(self, long prec): + r""" + Return the hyperbolic sine of this polynomial up to precision ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._sinh_series(8) + 1/5040*x^7 + 1/120*x^5 + 1/6*x^3 + x + + TESTS:: + + sage: (x+1)._sinh_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take hyperbolic sine + sage: (0*x)._sinh_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take hyperbolic sine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_sinh_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _cosh_series(self, long prec): + r""" + Return the hyperbolic cosine of this polynomial up to precision + ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._cosh_series(8) + 1/720*x^6 + 1/24*x^4 + 1/2*x^2 + 1 + + A trigonometric identity:: + + sage: x._cosh_series(8) + x._sinh_series(8) == x._exp_series(8) + True + + TESTS:: + + sage: (x+1)._cosh_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take hyperbolic cosine + sage: (0*x)._cosh_series(5) + 1 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.one() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take hyperbolic cosine") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_cosh_series(res.__poly, self.__poly, prec) + sig_off() + return res + + def _tanh_series(self, long prec): + r""" + Return the hyperbolic tangent of this polynomial up to precision + ``prec``. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: x._tanh_series(8) + -17/315*x^7 + 2/15*x^5 - 1/3*x^3 + x + + TESTS:: + + sage: (x+1)._tanh_series(5) + Traceback (most recent call last): + ... + ValueError: constant term should be 0 in order to take hyperbolic tangent + sage: (0*x)._tanh_series(5) + 0 + sage: _.parent() + Univariate Polynomial Ring in x over Rational Field + """ + if fmpq_poly_degree(self.__poly) == -1: + return self._parent.zero() + elif not fmpz_is_zero(fmpq_poly_numref(self.__poly)): + raise ValueError("constant term should be 0 in order to take hyperbolic tangent") + + cdef Polynomial_rational_flint res = self._new() + sig_on() + fmpq_poly_tanh_series(res.__poly, self.__poly, prec) + sig_off() + return res + ########################################################################### # Methods using PARI # ########################################################################### diff --git a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx index 6640ca817a5..46c8e1dcbfc 100644 --- a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +++ b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx @@ -68,7 +68,7 @@ cdef class PolynomialRealDense(Polynomial): TESTS: - Check that errors and interrupts are handled properly (see #10100):: + Check that errors and interrupts are handled properly (see :trac:`10100`):: sage: a = var('a') sage: PolynomialRealDense(RR['x'], [1,a]) @@ -83,7 +83,7 @@ cdef class PolynomialRealDense(Polynomial): sage: sig_on_count() 0 - Test that we don't clean up uninitialized coefficients (#9826):: + Test that we don't clean up uninitialized coefficients (:trac:`9826`):: sage: k. = GF(7^3) sage: P. = PolynomialRing(k) @@ -91,6 +91,11 @@ cdef class PolynomialRealDense(Polynomial): Traceback (most recent call last): ... TypeError: Unable to convert x (='a') to real number. + + Check that :trac:`17190` is fixed:: + + sage: RR['x']({}) + 0 """ Polynomial.__init__(self, parent, is_gen=is_gen) self._base_ring = parent._base @@ -108,11 +113,7 @@ cdef class PolynomialRealDense(Polynomial): elif isinstance(x, (int, float, Integer, Rational, RealNumber)): x = [x] elif isinstance(x, dict): - degree = max(x.keys()) - c = [0] * (degree+1) - for i, a in x.items(): - c[i] = a - x = c + x = self._dict_to_list(x,self._base_ring.zero()) elif isinstance(x, pari_gen): x = [self._base_ring(w) for w in x.list()] elif not isinstance(x, list): @@ -652,7 +653,7 @@ cdef class PolynomialRealDense(Polynomial): sage: f(RealField(10)(2)) 2.0 sage: f(pi) - pi^2 - 2.00000000000000 + 1.00000000000000*pi^2 - 2.00000000000000 sage: f = PolynomialRealDense(RR['x'], range(5)) @@ -665,9 +666,9 @@ cdef class PolynomialRealDense(Polynomial): sage: f = PolynomialRealDense(RR['x']) sage: f(12) 0.000000000000000 - + TESTS:: - + sage: R. = RR[] # :trac:`17311` sage: (x^2+1)(x=5) 26.0000000000000 @@ -676,13 +677,13 @@ cdef class PolynomialRealDense(Polynomial): xx = args[0] else: return Polynomial.__call__(self, *args, **kwds) - + if not isinstance(xx, RealNumber): if self._base_ring.has_coerce_map_from(parent(xx)): xx = self._base_ring(xx) else: return Polynomial.__call__(self, xx) - + cdef Py_ssize_t i cdef mp_rnd_t rnd = self._base_ring.rnd cdef RealNumber x = xx @@ -713,7 +714,7 @@ cdef class PolynomialRealDense(Polynomial): mpfr_mul(res.value, res.value, x.value, rnd) mpfr_add(res.value, res.value, self._coeffs[i], rnd) return res - + def change_ring(self, R): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index cea222adfba..9c469c9eb46 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -154,12 +154,13 @@ """ - -################################################################################# +#***************************************************************************** # Copyright (C) 2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -170,7 +171,7 @@ import sage.rings.commutative_ring as commutative_ring import sage.rings.commutative_algebra as commutative_algebra import sage.rings.ring as ring -import sage.rings.ring_element as ring_element +from sage.structure.element import is_RingElement import sage.rings.integral_domain as integral_domain import sage.rings.principal_ideal_domain as principal_ideal_domain import sage.rings.polynomial.polynomial_element_generic as polynomial_element_generic @@ -267,7 +268,7 @@ def __init__(self, base_ring, name=None, sparse=False, element_class=None, categ sage: category(ZZ['x']) Join of Category of unique factorization domains and Category of commutative algebras over - (euclidean domains and infinite enumerated sets) + (euclidean domains and infinite enumerated sets and metric spaces) sage: category(GF(7)['x']) Join of Category of euclidean domains and Category of commutative algebras over (finite fields and @@ -2516,7 +2517,7 @@ def polygen(ring_or_element, name="x"): get a tuple of indeterminates, exactly as if you called polygens. """ - if ring_element.is_RingElement(ring_or_element): + if is_RingElement(ring_or_element): base_ring = ring_or_element.parent() elif ring.is_Ring(ring_or_element): base_ring = ring_or_element diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 237fbec2c6d..57f4550b27d 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -529,7 +529,7 @@ cdef class Polynomial_template(Polynomial): """ return not celement_is_zero(&self.x, (self)._cparent) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ EXAMPLE:: @@ -541,14 +541,6 @@ cdef class Polynomial_template(Polynomial): sage: x > 1 True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - EXAMPLE:: - - sage: P. = GF(2)[] - """ return celement_cmp(&(left).x, &(right).x, (left)._cparent) def __hash__(self): @@ -763,7 +755,16 @@ cdef class Polynomial_template(Polynomial): x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 sage: f.truncate(6) x^5 + x^4 + x^3 + x^2 + x + 1 + + If the precision is higher than the degree of the polynomial then + the polynomial itself is returned:: + + sage: f.truncate(10) is f + True """ + if n >= celement_len(&self.x, (self)._cparent): + return self + cdef type T = type(self) cdef Polynomial_template r = T.__new__(T) celement_construct(&r.x, (self)._cparent) diff --git a/src/sage/rings/polynomial/polynomial_zz_pex.pyx b/src/sage/rings/polynomial/polynomial_zz_pex.pyx index b0d59f3d4fe..2b54b749c44 100644 --- a/src/sage/rings/polynomial/polynomial_zz_pex.pyx +++ b/src/sage/rings/polynomial/polynomial_zz_pex.pyx @@ -251,7 +251,7 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template): sage: P. = F[] sage: p = y^4 + x*y^3 + y^2 + (x + 1)*y + x + 1 sage: SR(p) #indirect doctest - (((y + x)*y + 1)*y + x + 1)*y + x + 1 + y^4 + x*y^3 + y^2 + (x + 1)*y + x + 1 sage: p(2) x + 1 sage: p(y=2) diff --git a/src/sage/rings/polynomial/refine_root.pyx b/src/sage/rings/polynomial/refine_root.pyx new file mode 100644 index 00000000000..aafbeb442f3 --- /dev/null +++ b/src/sage/rings/polynomial/refine_root.pyx @@ -0,0 +1,141 @@ +""" +Refine polynomial roots using Newton--Raphson + +This is an implementation of the Newton--Raphson algorithm to +approximate roots of complex polynomials. The implementation +is based on interval arithmetic + +AUTHORS: + +- Carl Witty (2007-11-18): initial version +""" + +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.rings.real_mpfi import RealIntervalField +from sage.rings.complex_interval_field import ComplexIntervalField + + +def refine_root(ip, ipd, irt, fld): + """ + We are given a polynomial and its derivative (with complex + interval coefficients), an estimated root, and a complex interval + field to use in computations. We use interval arithmetic to + refine the root and prove that we have in fact isolated a unique + root. + + If we succeed, we return the isolated root; if we fail, we return + None. + + EXAMPLES:: + + sage: from sage.rings.polynomial.refine_root import refine_root + sage: x = polygen(ZZ) + sage: p = x^9 - 1 + sage: ip = CIF['x'](p); ip + x^9 - 1 + sage: ipd = CIF['x'](p.derivative()); ipd + 9*x^8 + sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt + 0.76604444311897802? + 0.64278760968653926?*I + sage: ip(irt) + 0.?e-14 + 0.?e-14*I + sage: ipd(irt) + 6.89439998807080? - 5.78508848717885?*I + sage: refine_root(ip, ipd, irt, CIF) + 0.766044443118978? + 0.642787609686540?*I + """ + + # There has got to be a better way to do this, but I don't know + # what it is... + + # We start with a basic fact: if we do an interval Newton-Raphson + # step, and the refined interval is contained in the original interval, + # then the refined interval contains exactly one root. + + # Unfortunately, our initial estimated root almost certainly does not + # contain the actual root (our initial interval is a point, which + # is exactly equal to whatever floating-point estimate we got from + # the external solver). So we need to do multiple Newton-Raphson + # steps, and check this inclusion property on each step. + + # After a few steps of refinement, if the initial root estimate was + # close to a root, we should have an essentially perfect interval + # bound on the root (since Newton-Raphson has quadratic convergence), + # unless either the real or imaginary component of the root is zero. + # If the real or imaginary component is zero, then we could spend + # a long time computing closer and closer approximations to that + # component. (This doesn't happen for non-zero components, because + # of the imprecision of floating-point numbers combined with the + # outward interval rounding; but close to zero, MPFI provides + # extremely precise numbers.) + + # If the root is actually a real root, but we start with an imaginary + # component, we can bounce back and forth between having a positive + # and negative imaginary component, without ever hitting zero. + # To deal with this, on every other Newton-Raphson step, instead of + # replacing the old interval with the new one, we take the union. + + # If the containment check continues to fail many times in a row, + # we give up and return None; we also return None if we detect + # that the slope in our current interval is not bounded away + # from zero at any step. + + # After every refinement step, we check to see if the real or + # imaginary component of our interval includes zero. If so, we + # try setting it to exactly zero. This gives us a good chance of + # detecting real roots. However, we do this replacement at most + # once per component. + + refinement_steps = 10 + + smashed_real = False + smashed_imag = False + + for i in range(refinement_steps): + slope = ipd(irt) + if slope.contains_zero(): + return None + center = fld(irt.center()) + val = ip(center) + + nirt = center - val / slope + # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope + if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): + # If the new diameter is much less than the original diameter, + # then we have not yet converged. (Perhaps we were asked + # for a particularly high-precision result.) So we don't + # return yet. + return nirt + + if i & 1: + irt = nirt + else: + irt = irt.union(nirt) + # If we don't find a root after a while, try (approximately) + # tripling the size of the region. + if i >= 6: + rD = irt.real().absolute_diameter() + iD = irt.imag().absolute_diameter() + md = max(rD, iD) + md_intv = RealIntervalField(rD.prec())(-md, md) + md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) + irt = irt + md_cintv + + if not smashed_real and irt.real().contains_zero(): + irt = irt.parent()(0, irt.imag()) + smashed_real = True + if not smashed_imag and irt.imag().contains_zero(): + irt = irt.parent()(irt.real(), 0) + smashed_imag = True + + return None diff --git a/src/sage/rings/polynomial/term_order.py b/src/sage/rings/polynomial/term_order.py index 2a1f48d4c13..537793db062 100644 --- a/src/sage/rings/polynomial/term_order.py +++ b/src/sage/rings/polynomial/term_order.py @@ -780,6 +780,7 @@ def __init__(self, name='lex', n=0, force=False): from sage.matrix.constructor import matrix self._matrix = matrix(n,name) # defined only for matrix term order + self._matrix.set_immutable() self._weights = name[:n] # the first row of the matrix gives weights else: raise TypeError("%s is not a valid term order"%(name,)) @@ -791,6 +792,16 @@ def __init__(self, name='lex', n=0, force=False): self.__doc__ = description_mapping.get(self._name, "No description available") + def __hash__(self): + r""" + A hash function + + EXAMPLE:: + + sage: _=hash(TermOrder('lex')) + """ + return hash((self._name, self._blocks, self._weights, self._matrix)) + def __copy(self, other): """ Copy other term order to self. diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index e2b3c29cd68..9881351ef45 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -89,24 +89,6 @@ cdef class PowerSeries_poly(PowerSeries): """ return self.__class__, (self._parent, self.__f, self._prec, self.__is_gen) - def __richcmp__(left, right, int op): - """ - Used for comparing power series. - - EXAMPLES:: - - sage: R. = ZZ[[]] - sage: f = 1 + t + t^7 - 5*t^10 - sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) - sage: f == f - True - sage: f < g - False - sage: f == g - True - """ - return (left)._richcmp(right, op) - def polynomial(self): """ Return the underlying polynomial of self. diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index d91af262d7d..3dc1e66c7a7 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -320,7 +320,7 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, sage: TestSuite(M).run() .. SEEALSO:: - + * :func:`sage.misc.defaults.set_series_precision` """ #multivariate case: @@ -484,7 +484,7 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, This base class inherits from :class:`~sage.rings.ring.CommutativeRing`. Since :trac:`11900`, it is also initialised as such, and since :trac:`14084` it is actually initialised as an integral domain:: - + sage: R. = ZZ[[]] sage: R.category() Category of integral domains @@ -499,6 +499,15 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, sage: R.category() Category of complete discrete valuation rings sage: TestSuite(R).run() + + It is checked that the default precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x', default_prec=-5) + Traceback (most recent call last): + ... + ValueError: default_prec (= -5) must be non-negative + """ R = PolynomialRing(base_ring, name, sparse=sparse) self.__poly_ring = R @@ -506,6 +515,9 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, if default_prec is None: from sage.misc.defaults import series_precision default_prec = series_precision() + elif default_prec < 0: + raise ValueError("default_prec (= %s) must be non-negative" + % default_prec) self.__params = (base_ring, name, default_prec, sparse) if use_lazy_mpoly_ring and (is_MPolynomialRing(base_ring) or \ @@ -708,9 +720,21 @@ def _element_constructor_(self, f, prec=infinity, check=True): sage: P(1/q) Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + It is checked that the precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x')(1, prec=-5) + Traceback (most recent call last): + ... + ValueError: prec (= -5) must be non-negative """ + if prec is not infinity: + prec = integer.Integer(prec) + if prec < 0: + raise ValueError("prec (= %s) must be non-negative" % prec) if isinstance(f, power_series_ring_element.PowerSeries) and f.parent() is self: if prec >= f.prec(): return f @@ -927,7 +951,7 @@ def is_exact(self): exact. EXAMPLES:: - + sage: R. = PowerSeriesRing(ZZ) sage: R.is_exact() False @@ -1124,7 +1148,7 @@ def is_field(self, proof = True): a field. EXAMPLES:: - + sage: R. = PowerSeriesRing(ZZ) sage: R.is_field() False diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index 493e15e8d35..ef0fc52daff 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -102,7 +102,6 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing import sage.rings.polynomial.polynomial_element import power_series_ring import sage.misc.misc -import ring_element import arith import sage.misc.latex import rational_field, integer_ring @@ -166,8 +165,6 @@ cdef class PowerSeries(AlgebraElement): """ AlgebraElement.__init__(self, parent) self.__is_gen = is_gen - if not (prec is infinity): - prec = int(prec) self._prec = prec def __hash__(self): @@ -316,18 +313,6 @@ cdef class PowerSeries(AlgebraElement): S = self._parent.change_ring(R) return S(self) - def __cmp__(left, right): - """ - Called by comparison operations. - - EXAMPLES:: - - sage: R. = PowerSeriesRing(ZZ) - sage: 1+x^2 < 2-x - True - """ - return (left)._cmp(right) - cpdef int _cmp_(self, Element right) except -2: r""" Comparison of self and ``right``. @@ -359,9 +344,23 @@ cdef class PowerSeries(AlgebraElement): sage: 1 - 2*q + q^2 +O(q^3) == 1 - 2*q^2 + q^2 + O(q^4) False + :: + + sage: R. = ZZ[[]] + sage: 1 + t^2 < 2 - t + True + sage: f = 1 + t + t^7 - 5*t^10 + sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) + sage: f == f + True + sage: f < g + False + sage: f == g + True + TESTS: - Ticket :trac:`9457` is fixed:: + :trac:`9457` is fixed:: sage: A. = PowerSeriesRing(ZZ) sage: g = t + t^3 + t^5 + O(t^6); g @@ -1024,11 +1023,17 @@ cdef class PowerSeries(AlgebraElement): ... ZeroDivisionError: leading coefficient must be a unit + A test for the case where the precision is 0:: + + sage: R. = PowerSeriesRing(ZZ, default_prec=0) + sage: ~(1+x) + O(x^0) + AUTHORS: - David Harvey (2006-09-09): changed to use Newton's method """ - if self == 1: + if self.is_one(): return self prec = self.prec() if prec is infinity and self.degree() > 0: @@ -1049,6 +1054,8 @@ cdef class PowerSeries(AlgebraElement): if prec is infinity: return self._parent(first_coeff, prec=prec) + elif not prec: + return self._parent(0, prec=0) A = self.truncate() R = A.parent() # R is the corresponding polynomial ring diff --git a/src/sage/rings/principal_ideal_domain_element.py b/src/sage/rings/principal_ideal_domain_element.py index 76cf5d4c9f7..b7707701cdf 100644 --- a/src/sage/rings/principal_ideal_domain_element.py +++ b/src/sage/rings/principal_ideal_domain_element.py @@ -17,6 +17,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.principal_ideal_domain_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import PrincipalIdealDomainElement def is_PrincipalIdealDomainElement(x): @@ -25,6 +28,9 @@ def is_PrincipalIdealDomainElement(x): EXAMPLES:: + sage: import sage.rings.principal_ideal_domain_element + doctest:...: DeprecationWarning: the module sage.rings.principal_ideal_domain_element is deprecated, import from sage.structure.element instead + See http://trac.sagemath.org/19167 for details. sage: sage.rings.principal_ideal_domain_element.is_PrincipalIdealDomainElement(ZZ(2)) True """ diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 5657269ea2a..cd3a99f14f9 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -362,8 +362,8 @@ AA(2) Just for fun, let's try ``sage_input`` on a very complicated expression. The -output of this example changed with the rewritting of polynomial multiplication -algorithms in #10255:: +output of this example changed with the rewriting of polynomial multiplication +algorithms in :trac:`10255`:: sage: rt2 = sqrt(AA(2)) sage: rt3 = sqrt(QQbar(3)) @@ -449,7 +449,7 @@ ....: def convert_test(v): ....: try: ....: return ty(v) - ....: except ValueError: + ....: except (TypeError, ValueError): ....: return None ....: return [convert_test(_) for _ in all_vals] sage: convert_test_all(float) @@ -3473,7 +3473,7 @@ def is_integer(self): Return True if this number is a integer EXAMPLES:: - + sage: QQbar(2).is_integer() True sage: QQbar(1/2).is_integer() @@ -3807,7 +3807,8 @@ def degree(self): def interval_fast(self, field): r""" - Given a ``RealIntervalField``, compute the value of this number + Given a :class:`RealIntervalField` or + :class:`ComplexIntervalField`, compute the value of this number using interval arithmetic of at least the precision of the field, and return the value in that field. (More precision may be used in the computation.) The returned interval may be arbitrarily @@ -3827,15 +3828,11 @@ def interval_fast(self, field): sage: x.interval_fast(RIF) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 0.7071067811865475244? + 0.7071067811865475244?*I to real interval """ - if field.prec() == self._value.prec(): - return field(self._value) - elif field.prec() > self._value.prec(): + while self._value.prec() < field.prec(): self._more_precision() - return self.interval_fast(field) - else: - return field(self._value) + return field(self._value) def interval_diameter(self, diam): """ @@ -3887,11 +3884,22 @@ def interval(self, field): 0.8412535328311811689? + 0.540640817455597582?*I sage: x.interval(CIF64) 0.8412535328311811689? + 0.5406408174555975822?*I + + The following implicitly use this method:: + + sage: RIF(AA(5).sqrt()) + 2.236067977499790? + sage: AA(-5).sqrt().interval(RIF) + Traceback (most recent call last): + ... + TypeError: unable to convert 2.236067977499789697?*I to real interval """ target = RR(1.0) >> field.prec() val = self.interval_diameter(target) return field(val) + _real_mpfi_ = interval + def radical_expression(self): r""" Attempt to obtain a symbolic expression using radicals. If no @@ -4553,13 +4561,13 @@ def complex_number(self, field): EXAMPLES:: sage: a = QQbar.zeta(5) - sage: a.complex_number(CIF) + sage: a.complex_number(CC) 0.309016994374947 + 0.951056516295154*I - sage: (a + a.conjugate()).complex_number(CIF) + sage: (a + a.conjugate()).complex_number(CC) 0.618033988749895 - 5.42101086242752e-20*I """ v = self.interval(ComplexIntervalField(field.prec())) - return v.center() + return field(v) def complex_exact(self, field): r""" @@ -5204,21 +5212,7 @@ def real_number(self, field): 1.41421356237309 """ v = self.interval(RealIntervalField(field.prec())) - - mode = field.rounding_mode() - if mode == 'RNDN': - return v.center() - if mode == 'RNDD': - return v.lower() - if mode == 'RNDU': - return v.upper() - if mode == 'RNDZ': - if v > 0: - return field(v.lower()) - elif v < 0: - return field(v.upper()) - else: - return field(0) + return field(v) _mpfr_ = real_number diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index f268c22cf0b..343b72fe3a9 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -29,15 +29,16 @@ ``x - I.reduce(x) in I``). Here is a toy example:: sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) = FreeAlgebra(QQ, 3) sage: I3 = PowerIdeal(F,3); I3 Twosided Ideal (x^3, x^2*y, x^2*z, x*y*x, x*y^2, x*y*z, x*z*x, x*z*y, @@ -83,15 +84,16 @@ letterplace wrapper allows to provide the above toy example more easily:: + sage: from itertools import product sage: F. = FreeAlgebra(QQ, implementation='letterplace') - sage: Q3 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*3)]*F) + sage: Q3 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=3)]*F) sage: Q3 Quotient of Free Associative Unital Algebra on 3 generators (x, y, z) over Rational Field by the ideal (x*x*x, x*x*y, x*x*z, x*y*x, x*y*y, x*y*z, x*z*x, x*z*y, x*z*z, y*x*x, y*x*y, y*x*z, y*y*x, y*y*y, y*y*z, y*z*x, y*z*y, y*z*z, z*x*x, z*x*y, z*x*z, z*y*x, z*y*y, z*y*z, z*z*x, z*z*y, z*z*z) sage: Q3.0*Q3.1-Q3.1*Q3.0 xbar*ybar - ybar*xbar sage: Q3.0*(Q3.1*Q3.2)-(Q3.1*Q3.2)*Q3.0 0 - sage: Q2 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*2)]*F) + sage: Q2 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=2)]*F) sage: Q2.is_commutative() True diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index be8028ca500..853d267a924 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -7,28 +7,21 @@ """ #***************************************************************************** -# -# Sage: System for Algebra and Geometry Experimentation -# # Copyright (C) 2005 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -import ring_element + +from sage.structure.element import RingElement from sage.interfaces.singular import singular as singular_default -class QuotientRingElement(ring_element.RingElement): +class QuotientRingElement(RingElement): """ An element of a quotient ring `R/I`. @@ -93,7 +86,7 @@ def __init__(self, parent, rep, reduce=True): sage: v = S.gens(); v (xbar,) """ - ring_element.RingElement.__init__(self, parent) + RingElement.__init__(self, parent) self.__rep = rep if reduce: self._reduce_() @@ -597,6 +590,18 @@ def __float__(self): """ return float(self.lift()) + def __hash__(self): + r""" + TESTS:: + + sage: R. = QQ[] + sage: S. = R.quo(x^2 + y^2) + sage: hash(a) + 15360174650385711 # 64-bit + 1505322287 # 32-bit + """ + return hash(self.__rep) + def __cmp__(self, other): """ EXAMPLES:: diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 13dbdd6b950..d1680334abe 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -709,9 +709,9 @@ cdef class Rational(sage.structure.element.FieldElement): l = self.continued_fraction_list() return ContinuedFraction_periodic(l) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparison between two rational numbers. + Compare two rational numbers. INPUT: @@ -730,9 +730,6 @@ cdef class Rational(sage.structure.element.FieldElement): sage: 4/5 < 0.8 False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int i i = mpq_cmp((left).value, (right).value) if i < 0: return -1 @@ -2141,10 +2138,10 @@ cdef class Rational(sage.structure.element.FieldElement): sage: 3/0 # indirect doctest Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero """ if mpq_cmp_si(( right).value, 0, 1) == 0: - raise ZeroDivisionError, "Rational division by zero" + raise ZeroDivisionError('rational division by zero') cdef Rational x x = Rational.__new__(Rational) mpq_div(x.value, self.value, (right).value) @@ -2168,7 +2165,7 @@ cdef class Rational(sage.structure.element.FieldElement): -17/4 """ if self.is_zero(): - raise ZeroDivisionError, "rational division by zero" + raise ZeroDivisionError('rational division by zero') cdef Rational x x = Rational.__new__(Rational) mpq_inv(x.value, self.value) @@ -2255,6 +2252,13 @@ cdef class Rational(sage.structure.element.FieldElement): 1/8*8^(4/5) sage: 3^(-3/2) 1/9*sqrt(3) + + TESTS:: + + sage: QQ(0)^(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: rational division by zero """ if dummy is not None: raise ValueError, "__pow__ dummy variable not used" @@ -2328,6 +2332,9 @@ cdef class Rational(sage.structure.element.FieldElement): return x if nn < 0: + if mpz_sgn(mpq_numref(_self.value)) == 0: + raise ZeroDivisionError('rational division by zero') + sig_on() # mpz_pow_ui(mpq_denref(x.value), mpq_numref(_self.value), (-nn)) # mpz_pow_ui(mpq_numref(x.value), mpq_denref(_self.value), (-nn)) diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index afe2a8aaf32..4a041a848bb 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -154,7 +154,7 @@ def __init__(self): sage: Q.is_field() True sage: Q.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Q.zeta() -1 @@ -215,7 +215,7 @@ def __init__(self): ('x',) """ from sage.categories.basic import QuotientFields - ParentWithGens.__init__(self, self, category = QuotientFields()) + ParentWithGens.__init__(self, self, category=QuotientFields().Metric()) self._assign_names(('x',),normalize=False) # ??? self._populate_coercion_lists_(element_constructor=rational.Rational, init_no_parent=True) @@ -956,6 +956,51 @@ def _an_element_(self): """ return rational.Rational((1,2)) + def some_elements(self): + r""" + Return some elements of `\QQ`. + + See :func:`TestSuite` for a typical use case. + + OUTPUT: + + An iterator over 100 elements of `\QQ`. + + EXAMPLES:: + + sage: tuple(QQ.some_elements()) + (1/2, -1/2, 2, -2, + 0, 1, -1, 42, + 2/3, -2/3, 3/2, -3/2, + 4/5, -4/5, 5/4, -5/4, + 6/7, -6/7, 7/6, -7/6, + 8/9, -8/9, 9/8, -9/8, + 10/11, -10/11, 11/10, -11/10, + 12/13, -12/13, 13/12, -13/12, + 14/15, -14/15, 15/14, -15/14, + 16/17, -16/17, 17/16, -17/16, + 18/19, -18/19, 19/18, -19/18, + 20/441, -20/441, 441/20, -441/20, + 22/529, -22/529, 529/22, -529/22, + 24/625, -24/625, 625/24, -625/24, + ...) + """ + yield self.an_element() + yield -self.an_element() + yield 1/self.an_element() + yield -1/self.an_element() + yield self(0) + yield self(1) + yield self(-1) + yield self(42) + for n in range(1, 24): + a = 2*n + b = (2*n + 1)**(n//10 + 1) + yield rational.Rational((a, b)) + yield rational.Rational((-a, b)) + yield rational.Rational((b, a)) + yield rational.Rational((-b, a)) + def random_element(self, num_bound=None, den_bound=None, *args, **kwds): """ Return an random element of `\QQ`. diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index f7ad8c92f67..49820433146 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -1,102 +1,150 @@ +# -*- coding: utf-8 r""" -Arbitrary precision real intervals using Arb +Arbitrary precision real balls using Arb -AUTHORS: - -- Clemens Heuberger (2014-10-21): Initial version. - -This is a binding to the optional `Arb library `_; it -may be useful to refer to its documentation for more details. - -You may have to run ``sage -i arb`` to use the arb library. +This is a binding to the `Arb library `_ for ball +arithmetic. It may be useful to refer to its documentation for more details. Parts of the documentation for this module are copied or adapted from Arb's own documentation, licenced under the GNU General Public License version 2, or later. +.. SEEALSO:: + + - :mod:`Complex balls using Arb ` + - :mod:`Real intervals using MPFI ` + +Data Structure +============== + +Ball arithmetic, also known as mid-rad interval arithmetic, is an extension of +floating-point arithmetic in which an error bound is attached to each variable. +This allows doing rigorous computations over the real numbers, while avoiding +the overhead of traditional (inf-sup) interval arithmetic at high precision, +and eliminating much of the need for time-consuming and bug-prone manual error +analysis associated with standard floating-point arithmetic. + +Sage :class:`RealBall` objects wrap Arb objects of type ``arb_t``. A real +ball represents a ball over the real numbers, that is, an interval `[m-r,m+r]` +where the midpoint `m` and the radius `r` are (extended) real numbers:: + + sage: RBF(pi) + [3.141592653589793 +/- 5.61e-16] + sage: RBF(pi).mid(), RBF(pi).rad() + (3.14159265358979, 4.4408921e-16) + +The midpoint is represented as an arbitrary-precision floating-point number +with arbitrary-precision exponent. The radius is a floating-point number with +fixed-precision mantissa and arbitrary-precision exponent. :: + + sage: RBF(2)^(2^100) + [2.285367694229514e+381600854690147056244358827360 +/- 2.98e+381600854690147056244358827344] + +:class:`RealBallField` objects (the parents of real balls) model the field of +real numbers represented by balls on which computations are carried out with a +certain precision:: + + sage: RBF + Real ball field with 53 bits precision + +It is possible to construct a ball whose parent is the real ball field with +precision `p` but whose midpoint does not fit on `p` bits. However, the results +of operations involving such a ball will (usually) be rounded to its parent's +precision:: + + sage: RBF(factorial(50)).mid(), RBF(factorial(50)).rad() + (3.0414093201713378043612608166064768844377641568961e64, 0.00000000) + sage: (RBF(factorial(50)) + 0).mid() + 3.04140932017134e64 + Comparison ========== .. WARNING:: - Identical :class:`RealBall` objects are understood to give - permission for algebraic simplification. This assumption is made - to improve performance. For example, setting ``z = x*x`` sets `z` - to a ball enclosing the set `\{t^2 : t \in x\}` and not the - (generally larger) set `\{tu : t \in x, u \in x\}`. + In accordance with the semantics of Arb, identical :class:`RealBall` + objects are understood to give permission for algebraic simplification. + This assumption is made to improve performance. For example, setting ``z = + x*x`` may set `z` to a ball enclosing the set `\{t^2 : t \in x\}` and not + the (generally larger) set `\{tu : t \in x, u \in x\}`. Two elements are equal if and only if they are the same object or if both are exact and equal:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb + sage: a = RBF(1) + sage: b = RBF(1) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = RBF(1/3) + sage: b = RBF(1/3) + sage: a.is_exact() False - sage: b.is_exact() # optional - arb + sage: b.is_exact() False - sage: a is b # optional - arb + sage: a is b False - sage: a == b # optional - arb + sage: a == b False -A ball is non-zero if and only if it does not contain zero. :: +A ball is non-zero in the sense of comparison if and only if it does not +contain zero. :: - sage: a = RBF(RIF(-0.5, 0.5)) # optional - arb - sage: bool(a) # optional - arb + sage: a = RBF(RIF(-0.5, 0.5)) + sage: a != 0 False - sage: a != 0 # optional - arb - False - sage: b = RBF(1/3) # optional - arb - sage: bool(b) # optional - arb + sage: b = RBF(1/3) + sage: b != 0 + True + +However, ``bool(b)`` returns ``False`` for a ball ``b`` only if ``b`` is exactly +zero:: + + sage: bool(a) True - sage: b != 0 # optional - arb + sage: bool(b) True + sage: bool(RBF.zero()) + False A ball ``left`` is less than a ball ``right`` if all elements of ``left`` are less than all elements of ``right``. :: - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(3, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 2)) + sage: b = RBF(RIF(3, 4)) + sage: a < b True - sage: a <= b # optional - arb + sage: a <= b True - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a = RBF(RIF(1, 3)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 3)) + sage: b = RBF(RIF(2, 4)) + sage: a < b False - sage: a <= b # optional - arb + sage: a <= b False - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False Comparisons with Sage symbolic infinities work with some limitations:: - sage: -infinity < RBF(1) < +infinity # optional - arb + sage: -infinity < RBF(1) < +infinity True - sage: -infinity < RBF(infinity) # optional - arb + sage: -infinity < RBF(infinity) True - sage: RBF(infinity) < infinity # optional - arb + sage: RBF(infinity) < infinity False - sage: RBF(NaN) < infinity # optional - arb + sage: RBF(NaN) < infinity Traceback (most recent call last): ... ValueError: infinite but not with +/- phase - sage: 1/RBF(0) <= infinity # optional - arb + sage: 1/RBF(0) <= infinity Traceback (most recent call last): ... ValueError: infinite but not with +/- phase @@ -104,16 +152,19 @@ Comparisons with Sage symbolic infinities work with some limitations:: Comparisons between elements of real ball fields, however, support special values and should be preferred:: - sage: RBF(NaN) < RBF(infinity) # optional - arb + sage: RBF(NaN) < RBF(infinity) False - sage: 1/RBF(0) <= RBF(infinity) # optional - arb + sage: 1/RBF(0) <= RBF(infinity) True TESTS:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (RBF(pi) * identity_matrix(QQ, 3)).parent() # optional - arb - Full MatrixSpace of 3 by 3 dense matrices over Real ball field with 53 bits precision + sage: (RBF(pi) * identity_matrix(QQ, 3)).parent() + Full MatrixSpace of 3 by 3 dense matrices over Real ball field + with 53 bits precision + + sage: polygen(RBF, x)^3 + x^3 Classes and Methods =================== @@ -128,36 +179,45 @@ Classes and Methods #***************************************************************************** include 'sage/ext/interrupt.pxi' -include "sage/ext/python.pxi" -import operator -import sage.symbolic.constants - -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.real_mpfi import RealIntervalField, RealIntervalField_class -from sage.structure.unique_representation import UniqueRepresentation - -cimport sage.rings.integer -cimport sage.rings.rational -cimport sage.structure.element +from cpython.float cimport PyFloat_AS_DOUBLE +from cpython.int cimport PyInt_AS_LONG +from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE +from libc.stdlib cimport abort from sage.libs.arb.arb cimport * -from sage.libs.arb.arf cimport arf_t, arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, arf_set_mag, arf_set +from sage.libs.arb.arf cimport ( + arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, arf_set_mag, + arf_set, arf_get_d, arf_get_fmpz_2exp, arf_abs_bound_lt_2exp_si, + ARF_RND_UP, ARF_PREC_EXACT +) from sage.libs.arb.arf cimport arf_equal, arf_is_nan, arf_is_neg_inf, arf_is_pos_inf, arf_get_mag -from sage.libs.arb.mag cimport mag_t, mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero +from sage.libs.arb.mag cimport mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero from sage.libs.flint.flint cimport flint_free -from sage.libs.flint.fmpz cimport fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear +from sage.libs.flint.fmpz cimport ( + fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear, fmpz_fdiv_ui +) from sage.libs.flint.fmpq cimport fmpq_t, fmpq_init, fmpq_set_mpq, fmpq_clear from sage.libs.gmp.mpz cimport mpz_fits_ulong_p, mpz_fits_slong_p, mpz_get_ui, mpz_get_si from sage.libs.mpfi cimport mpfi_get_left, mpfi_get_right, mpfi_interv_fr -from sage.libs.mpfr cimport mpfr_t, mpfr_init2, mpfr_clear, mpfr_sgn, MPFR_PREC_MIN -from sage.libs.mpfr cimport GMP_RNDN, GMP_RNDU, GMP_RNDD, GMP_RNDZ +from sage.libs.mpfr cimport mpfr_t, mpfr_init2, mpfr_clear, mpfr_sgn, MPFR_PREC_MIN, mpfr_equal_p +from sage.libs.mpfr cimport MPFR_RNDN, MPFR_RNDU, MPFR_RNDD, MPFR_RNDZ +from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational from sage.rings.real_double cimport RealDoubleElement from sage.rings.real_mpfr cimport RealField_class, RealField, RealNumber +from sage.rings.ring import Field from sage.structure.element cimport Element, ModuleElement, RingElement +import operator + +import sage.categories.fields +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.real_mpfi import RealIntervalField, RealIntervalField_class +from sage.structure.unique_representation import UniqueRepresentation + cdef void mpfi_to_arb(arb_t target, const mpfi_t source, const long precision): """ Convert an MPFI interval to an Arb ball. @@ -169,22 +229,32 @@ cdef void mpfi_to_arb(arb_t target, const mpfi_t source, const long precision): - ``source`` -- an ``mpfi_t``. - ``precision`` -- an integer `\ge 2`. + + TESTS:: + + sage: RBF(RIF(infinity)).endpoints() + (+infinity, +infinity) + sage: RBF(RIF(-infinity)).endpoints() + (-infinity, -infinity) + sage: RIF(RBF(infinity)).endpoints() + (+infinity, +infinity) + sage: RIF(RBF(-infinity)).endpoints() + (-infinity, -infinity) """ cdef mpfr_t left cdef mpfr_t right - if _do_sig(precision): sig_on() - mpfr_init2(left, precision) mpfr_init2(right, precision) + if _do_sig(precision): sig_on() mpfi_get_left(left, source) mpfi_get_right(right, source) - - arb_set_interval_mpfr(target, - left, - right, - precision) + arb_set_interval_mpfr(target, left, right, precision) + # Work around weakness of arb_set_interval_mpfr(tgt, inf, inf) + if mpfr_equal_p(left, right): + mag_zero(arb_radref(target)) + if _do_sig(precision): sig_off() mpfr_clear(left) mpfr_clear(right) @@ -203,8 +273,7 @@ cdef int arb_to_mpfi(mpfi_t target, arb_t source, const long precision) except - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RIF(RBF(2)**(2**100)) # optional - arb, indirect doctest + sage: RIF(RBF(2)**(2**100)) # indirect doctest Traceback (most recent call last): ... ArithmeticError: Error converting arb to mpfi. Overflow? @@ -226,7 +295,7 @@ cdef int arb_to_mpfi(mpfi_t target, arb_t source, const long precision) except - mpfr_clear(left) mpfr_clear(right) -class RealBallField(UniqueRepresentation, Parent): +class RealBallField(UniqueRepresentation, Field): r""" An approximation of the field of real numbers using mid-rad intervals, also known as balls. @@ -237,38 +306,36 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb; indirect doctest - sage: RBF(1) # optional - arb + sage: RBF = RealBallField() # indirect doctest + sage: RBF(1) 1.000000000000000 :: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (1/2*RBF(1)) + AA(sqrt(2)) - 1 + polygen(QQ, x) # optional - arb + sage: (1/2*RBF(1)) + AA(sqrt(2)) - 1 + polygen(QQ, x) x + [0.914213562373095 +/- 4.10e-16] TESTS:: - sage: RBF.bracket(RBF(1/2), RBF(1/3)) # optional - arb + sage: RBF.bracket(RBF(1/2), RBF(1/3)) [+/- 5.56e-17] - sage: RBF.cardinality() # optional - arb + sage: RBF.cardinality() +Infinity - sage: RBF.cartesian_product(QQ).an_element()**2 # optional - arb + sage: RBF.cartesian_product(QQ).an_element()**2 ([1.440000000000000 +/- 4.98e-16], 1/4) - sage: RBF.coerce_embedding() is None # optional - arb + sage: RBF.coerce_embedding() is None True - sage: loads(dumps(RBF)) is RBF # optional - arb + sage: loads(dumps(RBF)) is RBF True - sage: RBF['x'].gens_dict_recursive() # optional - arb + sage: RBF['x'].gens_dict_recursive() {'x': x} - sage: RBF.is_finite() # optional - arb + sage: RBF.is_finite() False - sage: RBF.is_zero() # optional - arb + sage: RBF.is_zero() False - sage: RBF.one() # optional - arb + sage: RBF.one() 1.000000000000000 - sage: RBF.zero() # optional - arb + sage: RBF.zero() 0 """ Element = RealBall @@ -280,8 +347,7 @@ class RealBallField(UniqueRepresentation, Parent): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField(53) is RealBallField() # optional - arb + sage: RealBallField(53) is RealBallField() True """ return super(RealBallField, cls).__classcall__(cls, precision, category) @@ -296,26 +362,35 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(1) # optional - arb + sage: RBF = RealBallField() + sage: RBF(1) 1.000000000000000 - sage: RealBallField(0) # optional - arb + sage: RealBallField(0) Traceback (most recent call last): ... ValueError: Precision must be at least 2. - sage: RealBallField(1) # optional - arb + sage: RealBallField(1) Traceback (most recent call last): ... ValueError: Precision must be at least 2. + + TESTS:: + + sage: RBF.base() + Real ball field with 53 bits precision + sage: RBF.base_ring() + Real ball field with 53 bits precision + """ if precision < 2: raise ValueError("Precision must be at least 2.") super(RealBallField, self).__init__( - #category=category or sage.categories.magmas_and_additive_magmas.MagmasAndAdditiveMagmas().Infinite(), - # FIXME: RBF is not even associative, but CompletionFunctor only works with rings. - category=category or sage.categories.rings.Rings().Infinite()) + base_ring=self, + category=category or sage.categories.fields.Fields().Infinite()) self._prec = precision + from sage.rings.qqbar import AA + from sage.rings.real_lazy import RLF + self._populate_coercion_lists_([ZZ, QQ, AA, RLF]) def _repr_(self): r""" @@ -323,10 +398,9 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField() # optional - arb + sage: RealBallField() Real ball field with 53 bits precision - sage: RealBallField(106) # optional - arb + sage: RealBallField(106) Real ball field with 106 bits precision """ return "Real ball field with {} bits precision".format(self._prec) @@ -342,24 +416,19 @@ class RealBallField(UniqueRepresentation, Parent): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().has_coerce_map_from(RealBallField(54)) # optional - arb + sage: RealBallField().has_coerce_map_from(RealBallField(54)) True - sage: RealBallField().has_coerce_map_from(RealBallField(52)) # optional - arb + sage: RealBallField().has_coerce_map_from(RealBallField(52)) False - sage: RealBallField().has_coerce_map_from(RIF) # optional - arb + sage: RealBallField().has_coerce_map_from(RIF) False - sage: RealBallField().has_coerce_map_from(SR) # optional - arb + sage: RealBallField().has_coerce_map_from(SR) False - sage: RealBallField().has_coerce_map_from(RR) # optional - arb + sage: RealBallField().has_coerce_map_from(RR) False """ - from sage.rings.qqbar import AA - from sage.rings.real_lazy import RLF if isinstance(other, RealBallField): return (other._prec >= self._prec) - elif (other is ZZ) or (other is QQ) or (other is AA) or (other is RLF): - return True else: return False @@ -368,80 +437,73 @@ class RealBallField(UniqueRepresentation, Parent): Convert ``mid`` to an element of this real ball field, perhaps non-canonically. - In addition to the inputs supported by - :meth:`ElementConstructor.__init__`, + In addition to the inputs supported by :meth:`RealBall.__init__`, anything that is convertible to a real interval can also be used to construct a real ball:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(RIF(0, 1)) # optional - arb; indirect doctest + sage: RBF(RIF(0, 1)) # indirect doctest [+/- 1.01] - sage: RBF(1) # optional - arb + sage: RBF(1) 1.000000000000000 - sage: RBF(x) # optional - arb + sage: RBF(x) Traceback (most recent call last): ... - TypeError: unable to convert x to a RealIntervalFieldElement + TypeError: unable to convert x to a RealBall Various symbolic constants can be converted without going through real intervals. (This is faster and yields tighter error bounds.) :: - sage: RBF(e) # optional - arb + sage: RBF(e) [2.718281828459045 +/- 5.35e-16] - sage: RBF(pi) # optional - arb + sage: RBF(pi) [3.141592653589793 +/- 5.61e-16] """ try: return self.element_class(self, mid, rad) except TypeError: pass - try: return self.element_class(self, mid.pyobject(), rad) except (AttributeError, TypeError): pass - try: mid = RealIntervalField(self._prec)(mid) + return self.element_class(self, mid, rad) except TypeError: - raise TypeError("unable to convert {} to a RealIntervalFieldElement".format(mid)) - return self.element_class(self, mid, rad) - - def gens(self): - r""" - EXAMPLE:: - - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.gens() # optional - arb - (1.000000000000000,) - sage: RBF.gens_dict() # optional - arb - {'1.000000000000000': 1.000000000000000} - """ - return (self.one(),) + pass + raise TypeError("unable to convert {} to a RealBall".format(mid)) - def base(self): + def _repr_option(self, key): """ - Real ball fields are their own base. + Declare that real balls print atomically. - EXAMPLE:: + TESTS:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.base() # optional - arb - Real ball field with 53 bits precision + sage: RBF._repr_option('element_is_atomic') + True + sage: RBF['x']([-2,-2,-2/3]) + [-0.666666666666667 +/- 4.82e-16]*x^2 - 2.000000000000000*x + - 2.000000000000000 + sage: RBF._repr_option('element_is_atomic_typo') + Traceback (most recent call last): + ... + KeyError: 'element_is_atomic_typo' """ - return self + if key == 'element_is_atomic': + return True - def base_ring(self): - """ - Real ball fields are their own base ring. + return super(RealBallField, self)._repr_option(key) + def gens(self): + r""" EXAMPLE:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.base_ring() # optional - arb - Real ball field with 53 bits precision + sage: RBF.gens() + (1.000000000000000,) + sage: RBF.gens_dict() + {'1.000000000000000': 1.000000000000000} """ - return self + return (self.one(),) def construction(self): """ @@ -450,12 +512,11 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField(42) # optional - arb - sage: functor, base = RBF.construction() # optional - arb - sage: functor, base # optional - arb + sage: RBF = RealBallField(42) + sage: functor, base = RBF.construction() + sage: functor, base (Completion[+Infinity], Rational Field) - sage: functor(base) is RBF # optional - arb + sage: functor(base) is RBF True """ from sage.categories.pushout import CompletionFunctor @@ -464,14 +525,30 @@ class RealBallField(UniqueRepresentation, Parent): {'type': 'Ball'}) return functor, QQ + def complex_field(self): + """ + Return the complex ball field with the same precision. + + EXAMPLES:: + + sage: from sage.rings.complex_arb import ComplexBallField + sage: RBF.complex_field() + Complex ball field with 53 bits precision + sage: RealBallField(3).algebraic_closure() + Complex ball field with 3 bits precision + """ + from sage.rings.complex_arb import ComplexBallField + return ComplexBallField(self._prec) + + algebraic_closure = complex_field + def precision(self): """ Return the bit precision used for operations on elements of this field. EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().precision() # optional - arb + sage: RealBallField().precision() 53 """ return self._prec @@ -482,8 +559,22 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().is_exact() # optional - arb + sage: RealBallField().is_exact() + False + """ + return False + + def is_finite(self): + """ + Real ball fields are infinite. + + They already specify it via their category, but we currently need to + re-implement this method due to the legacy implementation in + :class:`sage.rings.ring.Ring`. + + EXAMPLES:: + + sage: RealBallField().is_finite() False """ return False @@ -494,8 +585,7 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().characteristic() # optional - arb + sage: RealBallField().characteristic() 0 """ return 0 @@ -507,8 +597,7 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.some_elements() # optional - arb + sage: RBF.some_elements() [1.000000000000000, [0.3333333333333333 +/- 7.04e-17], [-4.733045976388941e+363922934236666733021124 +/- 3.46e+363922934236666733021108], @@ -516,8 +605,9 @@ class RealBallField(UniqueRepresentation, Parent): [+/- inf], nan] """ + import sage.symbolic.constants return [self(1), self(1)/3, - -self(2)**(sage.rings.integer.Integer(2)**80), + -self(2)**(Integer(2)**80), self(sage.rings.infinity.Infinity), ~self(0), self.element_class(self, sage.symbolic.constants.NotANumber())] @@ -530,25 +620,24 @@ class RealBallField(UniqueRepresentation, Parent): This works even if ``x`` itself is not a ball, and may be faster or more accurate where ``x`` is a rational number. - .. seealso:: :meth:`~sage.rings.real_arb.RealBall.sin` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.sinpi(1) # optional - arb + sage: RBF.sinpi(1) 0 - sage: RBF.sinpi(1/3) # optional - arb + sage: RBF.sinpi(1/3) [0.866025403784439 +/- 5.15e-16] - sage: RBF.sinpi(1 + 2^(-100)) # optional - arb + sage: RBF.sinpi(1 + 2^(-100)) [-2.478279624546525e-30 +/- 5.90e-46] + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBall.sin` + TESTS:: - sage: RBF.sinpi(RLF(sqrt(2))) # optional - arb + sage: RBF.sinpi(RLF(sqrt(2))) [-0.96390253284988 +/- 4.11e-15] """ cdef RealBall res, x_as_ball - cdef sage.rings.rational.Rational x_as_Rational + cdef Rational x_as_Rational cdef fmpq_t tmpq res = self.element_class(self) try: @@ -577,23 +666,22 @@ class RealBallField(UniqueRepresentation, Parent): This works even if ``x`` itself is not a ball, and may be faster or more accurate where ``x`` is a rational number. - .. seealso:: :meth:`~sage.rings.real_arb.RealBall.cos` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.cospi(1) # optional - arb + sage: RBF.cospi(1) -1.000000000000000 - sage: RBF.cospi(1/3) # optional - arb + sage: RBF.cospi(1/3) 0.5000000000000000 + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBall.cos` + TESTS:: - sage: RBF.cospi(RLF(sqrt(2))) # optional - arb + sage: RBF.cospi(RLF(sqrt(2))) [-0.26625534204142 +/- 5.38e-15] """ cdef RealBall res, x_as_ball - cdef sage.rings.rational.Rational x_as_Rational + cdef Rational x_as_Rational cdef fmpq_t tmpq res = self.element_class(self) try: @@ -622,28 +710,27 @@ class RealBallField(UniqueRepresentation, Parent): This works even if ``x`` itself is not a ball, and may be more efficient in the case where ``x`` is an integer or a rational number. - .. seealso:: :meth:`~sage.rings.real_arb.RealBall.gamma` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.gamma(5) # optional - arb + sage: RBF.gamma(5) 24.00000000000000 - sage: RBF.gamma(10**20) # optional - arb + sage: RBF.gamma(10**20) [+/- 5.92e+1956570551809674821757] - sage: RBF.gamma(1/3) # optional - arb + sage: RBF.gamma(1/3) [2.678938534707747 +/- 8.99e-16] - sage: RBF.gamma(-5) # optional - arb + sage: RBF.gamma(-5) nan + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBall.gamma` + TESTS:: - sage: RBF.gamma(RLF(pi)) # optional - arb + sage: RBF.gamma(RLF(pi)) [2.2880377953400 +/- 4.29e-14] """ cdef RealBall res - cdef sage.rings.integer.Integer x_as_Integer - cdef sage.rings.rational.Rational x_as_Rational + cdef Integer x_as_Integer + cdef Rational x_as_Rational cdef fmpz_t tmpz cdef fmpq_t tmpq res = self.element_class(self) @@ -682,20 +769,19 @@ class RealBallField(UniqueRepresentation, Parent): This works even if ``s`` itself is not a ball, and may be more efficient in the case where ``s`` is an integer. - .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBall.zeta` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.zeta(3) # abs tol 5e-16, optional - arb + sage: RBF.zeta(3) # abs tol 5e-16 [1.202056903159594 +/- 2.87e-16] - sage: RBF.zeta(1) # optional - arb + sage: RBF.zeta(1) nan - sage: RBF.zeta(1/2) # optional - arb + sage: RBF.zeta(1/2) [-1.460354508809587 +/- 1.94e-16] + + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBall.zeta` """ cdef RealBall res - cdef sage.rings.integer.Integer s_as_Integer + cdef Integer s_as_Integer try: s_as_Integer = ZZ.coerce(s) if mpz_fits_ulong_p(s_as_Integer.value): @@ -714,31 +800,30 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: [RBF.bernoulli(n) for n in range(4)] # optional - arb + sage: [RBF.bernoulli(n) for n in range(4)] [1.000000000000000, -0.5000000000000000, [0.1666666666666667 +/- 7.04e-17], 0] - sage: RBF.bernoulli(2**20) # optional - arb + sage: RBF.bernoulli(2**20) [-1.823002872104961e+5020717 +/- 7.16e+5020701] - sage: RBF.bernoulli(2**1000) # optional - arb + sage: RBF.bernoulli(2**1000) Traceback (most recent call last): ... ValueError: argument too large TESTS:: - sage: RBF.bernoulli(2r) # optional - arb + sage: RBF.bernoulli(2r) [0.1666666666666667 +/- 7.04e-17] - sage: RBF.bernoulli(2/3) # optional - arb + sage: RBF.bernoulli(2/3) Traceback (most recent call last): ... TypeError: no canonical coercion from Rational Field to Integer Ring - sage: RBF.bernoulli(-1) # optional - arb + sage: RBF.bernoulli(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index """ cdef RealBall res - cdef sage.rings.integer.Integer n_as_Integer = ZZ.coerce(n) + cdef Integer n_as_Integer = ZZ.coerce(n) if mpz_fits_ulong_p(n_as_Integer.value): res = self.element_class(self) if _do_sig(self._prec): sig_on() @@ -757,8 +842,7 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: [RBF.fibonacci(n) for n in xrange(7)] # optional - arb + sage: [RBF.fibonacci(n) for n in xrange(7)] [0, 1.000000000000000, 1.000000000000000, @@ -766,14 +850,14 @@ class RealBallField(UniqueRepresentation, Parent): 3.000000000000000, 5.000000000000000, 8.000000000000000] - sage: RBF.fibonacci(-2) # optional - arb + sage: RBF.fibonacci(-2) -1.000000000000000 - sage: RBF.fibonacci(10**20) # optional - arb + sage: RBF.fibonacci(10**20) [3.78202087472056e+20898764024997873376 +/- 4.01e+20898764024997873361] """ cdef fmpz_t tmpz cdef RealBall res = self.element_class(self) - cdef sage.rings.integer.Integer n_as_Integer = ZZ.coerce(n) + cdef Integer n_as_Integer = ZZ.coerce(n) try: if _do_sig(self._prec): sig_on() fmpz_init(tmpz) @@ -784,6 +868,23 @@ class RealBallField(UniqueRepresentation, Parent): fmpz_clear(tmpz) return res + def maximal_accuracy(self): + r""" + Return the relative accuracy of exact elements measured in bits. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: RBF.maximal_accuracy() + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + + .. SEEALSO:: :meth:`RealBall.accuracy` + """ + return ARF_PREC_EXACT cdef inline bint _do_sig(long prec): """ @@ -791,9 +892,8 @@ cdef inline bint _do_sig(long prec): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: _ = RealBallField()(1).psi() # optional - arb; indirect doctest - sage: _ = RealBallField(1500)(1).psi() # optional - arb + sage: _ = RealBallField()(1).psi() # indirect doctest + sage: _ = RealBallField(1500)(1).psi() """ return (prec > 1000) @@ -807,12 +907,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(1)) # optional - arb; indirect doctest - sage: b = a.psi() # optional - arb - sage: b # optional - arb + sage: a = RealBallField()(RIF(1)) # indirect doctest + sage: b = a.psi() + sage: b [-0.577215664901533 +/- 3.85e-16] - sage: RIF(b) # optional - arb + sage: RIF(b) -0.577215664901533? """ @@ -822,8 +921,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(RIF(1)) # optional - arb; indirect doctest + sage: RealBallField()(RIF(1)) # indirect doctest 1.000000000000000 """ arb_init(self.value) @@ -834,9 +932,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(1)) # optional - arb; indirect doctest - sage: del a # optional - arb + sage: a = RealBallField()(RIF(1)) # indirect doctest + sage: del a """ arb_clear(self.value) @@ -858,36 +955,35 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF() # optional - arb + sage: RBF = RealBallField() + sage: RBF() 0 One can create exact real balls using elements of various exact parents, or using floating-point numbers:: - sage: RBF(3) # optional - arb + sage: RBF(3) 3.000000000000000 - sage: RBF(3r) # optional - arb + sage: RBF(3r) 3.000000000000000 - sage: RBF(1/3) # optional - arb + sage: RBF(1/3) [0.3333333333333333 +/- 7.04e-17] - sage: RBF(3.14) # optional - arb + sage: RBF(3.14) [3.140000000000000 +/- 1.25e-16] :: - sage: RBF(3, 0.125) # optional - arb + sage: RBF(3, 0.125) [3e+0 +/- 0.126] - sage: RBF(pi, 0.125r) # optional - arb + sage: RBF(pi, 0.125r) [3e+0 +/- 0.267] Note that integers and floating-point numbers are ''not'' rounded to the parent's precision:: - sage: b = RBF(11111111111111111111111111111111111111111111111); b # optional - arb + sage: b = RBF(11111111111111111111111111111111111111111111111); b [1.111111111111111e+46 +/- 1.12e+30] - sage: b.mid().exact_rational() # optional - arb + sage: b.mid().exact_rational() 11111111111111111111111111111111111111111111111 Similarly, converting a real ball from one real ball field to another @@ -895,37 +991,40 @@ cdef class RealBall(RingElement): the precision of operations involving it, not the actual representation of its center:: - sage: RBF100 = RealBallField(100) # optional - arb - sage: b100 = RBF100(1/3); b100 # optional - arb + sage: RBF100 = RealBallField(100) + sage: b100 = RBF100(1/3); b100 [0.333333333333333333333333333333 +/- 4.65e-31] - sage: b53 = RBF(b100); b53 # optional - arb + sage: b53 = RBF(b100); b53 [0.3333333333333333 +/- 3.34e-17] - sage: RBF100(b53) # optional - arb + sage: RBF100(b53) [0.333333333333333333333333333333 +/- 4.65e-31] Special values are supported:: - sage: RBF(oo).mid(), RBF(-oo).mid(), RBF(unsigned_infinity).mid() # optional - arb + sage: RBF(oo).mid(), RBF(-oo).mid(), RBF(unsigned_infinity).mid() (+infinity, -infinity, 0.000000000000000) - sage: RBF(NaN) # optional - arb + sage: RBF(NaN) nan + .. SEEALSO:: :meth:`RealBallField._element_constructor_` + TESTS:: - sage: from sage.rings.real_arb import RealBall # optional - arb - sage: RealBall(RBF, sage.symbolic.constants.Pi()) # abs tol 1e-16, optional - arb + sage: from sage.rings.real_arb import RealBall + sage: RealBall(RBF, sage.symbolic.constants.Pi()) # abs tol 1e-16 [3.141592653589793 +/- 5.62e-16] - sage: RealBall(RBF, sage.symbolic.constants.Log2()) # abs tol 1e-16, optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Log2()) # abs tol 1e-16 [0.693147180559945 +/- 4.06e-16] - sage: RealBall(RBF, sage.symbolic.constants.Catalan()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Catalan()) [0.915965594177219 +/- 1.23e-16] - sage: RealBall(RBF, sage.symbolic.constants.Khinchin()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Khinchin()) [2.685452001065306 +/- 6.82e-16] - sage: RealBall(RBF, sage.symbolic.constants.Glaisher()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Glaisher()) [1.282427129100623 +/- 6.02e-16] - sage: RealBall(RBF, sage.symbolic.constants.e) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.e) [2.718281828459045 +/- 5.35e-16] """ + import sage.symbolic.constants cdef fmpz_t tmpz cdef fmpq_t tmpq cdef arf_t tmpr @@ -940,17 +1039,17 @@ cdef class RealBall(RingElement): arb_set(self.value, ( mid).value) # no rounding! elif isinstance(mid, int): arb_set_si(self.value, PyInt_AS_LONG(mid)) # no rounding! - elif isinstance(mid, sage.rings.integer.Integer): + elif isinstance(mid, Integer): if _do_sig(prec(self)): sig_on() fmpz_init(tmpz) - fmpz_set_mpz(tmpz, ( mid).value) + fmpz_set_mpz(tmpz, ( mid).value) arb_set_fmpz(self.value, tmpz) # no rounding! fmpz_clear(tmpz) if _do_sig(prec(self)): sig_off() - elif isinstance(mid, sage.rings.rational.Rational): + elif isinstance(mid, Rational): if _do_sig(prec(self)): sig_on() fmpq_init(tmpq) - fmpq_set_mpq(tmpq, ( mid).value) + fmpq_set_mpq(tmpq, ( mid).value) arb_set_fmpq(self.value, tmpq, prec(self)) fmpq_clear(tmpq) if _do_sig(prec(self)): sig_off() @@ -1018,8 +1117,7 @@ cdef class RealBall(RingElement): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(2)**2 # indirect doctest, optional - arb + sage: RealBallField()(2)**2 # indirect doctest 4.000000000000000 """ @@ -1028,6 +1126,26 @@ cdef class RealBall(RingElement): x._parent = self._parent return x + def __hash__(self): + """ + TESTS:: + + sage: hash(RealBallField(10)(1)) == hash(RealBallField(20)(1)) + True + sage: hash(RBF(1/3)) == hash(RBF(1/3, rad=.1)) + False + sage: vals = [0, 1, 3/4, 5/8, 7/8, infinity, 'nan'] + sage: len({hash(RBF(v)) for v in vals}) == len(vals) + True + """ + cdef arf_t mid = arb_midref(self.value) + cdef fmpz_t mant, expo + arf_get_fmpz_2exp(mant, expo, mid) + return (fmpz_fdiv_ui(mant, 1073741789) + ^ fmpz_fdiv_ui(expo, 2**30) + ^ (arf_abs_bound_lt_2exp_si(mid) << 10) + ^ arb_rel_error_bits(self.value) << 20) + def _repr_(self): """ Return a string representation of ``self``. @@ -1038,8 +1156,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(RIF(1.9, 2)) # optional - arb + sage: RealBallField()(RIF(1.9, 2)) [2e+0 +/- 0.101] """ cdef char* c_result @@ -1065,9 +1182,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(2)) # optional - arb - sage: RIF(a) # optional - arb, indirect doctest + sage: a = RealBallField()(RIF(2)) + sage: RIF(a) # indirect doctest 2 """ cdef RealIntervalFieldElement result @@ -1081,25 +1197,24 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: ZZ(RBF(1, rad=0.1r)) # optional - arb + sage: ZZ(RBF(1, rad=0.1r)) 1 - sage: ZZ(RBF(1, rad=1.0r)) # optional - arb + sage: ZZ(RBF(1, rad=1.0r)) Traceback (most recent call last): ... ValueError: [+/- 2.01] does not contain a unique integer - sage: ZZ(RBF(pi)) # optional - arb + sage: ZZ(RBF(pi)) Traceback (most recent call last): ... ValueError: [3.141592653589793 +/- 5.61e-16] does not contain a unique integer """ - cdef sage.rings.integer.Integer res + cdef Integer res cdef fmpz_t tmp fmpz_init(tmp) try: if arb_get_unique_fmpz(tmp, self.value): - res = sage.rings.integer.Integer.__new__(sage.rings.integer.Integer) + res = Integer.__new__(Integer) fmpz_get_mpz(res.value, tmp) else: raise ValueError("{} does not contain a unique integer".format(self)) @@ -1107,6 +1222,25 @@ cdef class RealBall(RingElement): fmpz_clear(tmp) return res + def _rational_(self): + """ + Check that this ball contains a single rational number and return that + number. + + EXAMPLES:: + + sage: QQ(RBF(123456/2^12)) + 1929/64 + sage: QQ(RBF(1/3)) + Traceback (most recent call last): + ... + ValueError: [0.3333333333333333 +/- 7.04e-17] does not contain a unique rational number + """ + if arb_is_exact(self.value): + return self.mid().exact_rational() + else: + raise ValueError("{} does not contain a unique rational number".format(self)) + def _mpfr_(self, RealField_class field): """ Convert this real ball to a real number. @@ -1116,38 +1250,37 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: mypi = RBF(pi) # optional - arb - sage: RR(mypi) # optional - arb + sage: mypi = RBF(pi) + sage: RR(mypi) 3.14159265358979 - sage: Reals(rnd='RNDU')(mypi) # optional - arb + sage: Reals(rnd='RNDU')(mypi) 3.14159265358980 - sage: Reals(rnd='RNDD')(mypi) # optional - arb + sage: Reals(rnd='RNDD')(mypi) 3.14159265358979 - sage: Reals(rnd='RNDZ')(mypi) # optional - arb + sage: Reals(rnd='RNDZ')(mypi) 3.14159265358979 - sage: Reals(rnd='RNDZ')(-mypi) # optional - arb + sage: Reals(rnd='RNDZ')(-mypi) -3.14159265358979 - sage: Reals(rnd='RNDU')(-mypi) # optional - arb + sage: Reals(rnd='RNDU')(-mypi) -3.14159265358979 :: - sage: b = RBF(RIF(-1/2, 1)) # optional - arb - sage: RR(b) # optional - arb + sage: b = RBF(RIF(-1/2, 1)) + sage: RR(b) 0.250000000000000 - sage: Reals(rnd='RNDU')(b) # optional - arb + sage: Reals(rnd='RNDU')(b) 1.00000000093133 - sage: Reals(rnd='RNDD')(b) # optional - arb + sage: Reals(rnd='RNDD')(b) -0.500000000931323 - sage: Reals(rnd='RNDZ')(b) # optional - arb + sage: Reals(rnd='RNDZ')(b) 0.250000000000000 """ cdef RealNumber left, mid, right cdef long prec = field.precision() cdef int sl, sr - if (field.rnd == GMP_RNDN or - field.rnd == GMP_RNDZ and arb_contains_zero(self.value)): + if (field.rnd == MPFR_RNDN or + field.rnd == MPFR_RNDZ and arb_contains_zero(self.value)): mid = RealNumber(field, None) sig_str("unable to convert to MPFR (exponent out of range?)") arf_get_mpfr(mid.value, arb_midref(self.value), field.rnd) @@ -1159,11 +1292,11 @@ cdef class RealBall(RingElement): sig_str("unable to convert to MPFR (exponent out of range?)") arb_get_interval_mpfr(left.value, right.value, self.value) sig_off() - if field.rnd == GMP_RNDD: + if field.rnd == MPFR_RNDD: return left - elif field.rnd == GMP_RNDU: + elif field.rnd == MPFR_RNDU: return right - elif field.rnd == GMP_RNDZ: + elif field.rnd == MPFR_RNDZ: sl, sr = mpfr_sgn(left.value), mpfr_sgn(left.value) if sr > 0 and sl > 0: return left @@ -1173,49 +1306,60 @@ cdef class RealBall(RingElement): return field(0) raise ValueError("unknown rounding mode") - # Center and radius + # Center and radius, absolute value, endpoints def mid(self): """ Return the center of this ball. - .. SEEALSO:: :meth:`rad`, :meth:`squash` - EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField, RBF # optional - arb - sage: RealBallField(16)(1/3).mid() # optional - arb + sage: RealBallField(16)(1/3).mid() 0.3333 - sage: RealBallField(16)(1/3).mid().parent() # optional - arb - Real Field with 15 bits of precision + sage: RealBallField(16)(1/3).mid().parent() + Real Field with 16 bits of precision + sage: RealBallField(16)(RBF(1/3)).mid().parent() + Real Field with 53 bits of precision + sage: RBF('inf').mid() + +infinity :: - sage: b = RBF(2)^(2^1000) # optional - arb - sage: b.mid() # optional - arb + sage: b = RBF(2)^(2^1000) + sage: b.mid() Traceback (most recent call last): ... RuntimeError: unable to convert to MPFR (exponent out of range?) + + .. SEEALSO:: :meth:`rad`, :meth:`squash` """ - cdef long mid_prec = arb_bits(self.value) or prec(self) + cdef long mid_prec = max(arb_bits(self.value), prec(self)) if mid_prec < MPFR_PREC_MIN: mid_prec = MPFR_PREC_MIN cdef RealField_class mid_field = RealField(mid_prec) return self._mpfr_(mid_field) + center = mid + def rad(self): """ Return the radius of this ball. - .. SEEALSO:: :meth:`mid`, :meth:`rad_as_ball` - EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(1/3).rad() # optional - arb + sage: RBF(1/3).rad() 5.5511151e-17 - sage: RealBallField()(1/3).rad().parent() # optional - arb + sage: RBF(1/3).rad().parent() Real Field with 30 bits of precision + + .. SEEALSO:: :meth:`mid`, :meth:`rad_as_ball` + + TESTS:: + + sage: (RBF(1, rad=.1) << (2^64)).rad() + Traceback (most recent call last): + ... + RuntimeError: unable to convert the radius to MPFR (exponent out of range?) """ # Should we return a real number with rounding towards +∞ (or away from # zero if/when implemented)? @@ -1223,29 +1367,29 @@ cdef class RealBall(RingElement): cdef RealNumber rad = RealNumber(rad_field, None) cdef arf_t tmp arf_init(tmp) + sig_str("unable to convert the radius to MPFR (exponent out of range?)") arf_set_mag(tmp, arb_radref(self.value)) - cdef int rnd = arf_get_mpfr(rad.value, tmp, GMP_RNDN) + if arf_get_mpfr(rad.value, tmp, MPFR_RNDN): + abort() + sig_off() arf_clear(tmp) - if rnd != 0: - raise OverflowError("Unable to represent the radius of this ball within the exponent range of RealNumbers") return rad def squash(self): """ - Return an exact ball with the same center of this ball. - - .. SEEALSO:: :meth:`mid`, :meth:`rad_as_ball` + Return an exact ball with the same center as this ball. EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: mid = RealBallField(16)(1/3).squash() # optional - arb - sage: mid # optional - arb + sage: mid = RealBallField(16)(1/3).squash() + sage: mid [0.3333 +/- 2.83e-5] - sage: mid.is_exact() # optional - arb + sage: mid.is_exact() True - sage: mid.parent() # optional - arb + sage: mid.parent() Real ball field with 16 bits precision + + .. SEEALSO:: :meth:`mid`, :meth:`rad_as_ball` """ cdef RealBall res = self._new() arf_set(arb_midref(res.value), arb_midref(self.value)) @@ -1256,41 +1400,221 @@ cdef class RealBall(RingElement): """ Return an exact ball with center equal to the radius of this ball. - .. SEEALSO:: :meth:`squash`, :meth:`rad` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: rad = RBF(1/3).rad_as_ball() # optional - arb - sage: rad # optional - arb + sage: rad = RBF(1/3).rad_as_ball() + sage: rad [5.55111512e-17 +/- 3.13e-26] - sage: rad.is_exact() # optional - arb + sage: rad.is_exact() True - sage: rad.parent() # optional - arb + sage: rad.parent() Real ball field with 30 bits precision + + .. SEEALSO:: :meth:`squash`, :meth:`rad` """ cdef RealBall res = self._parent.element_class(RealBallField(MAG_BITS)) arf_set_mag(arb_midref(res.value), arb_radref(self.value)) mag_zero(arb_radref(res.value)) return res - # Precision + def __abs__(self): + """ + Return the absolute value of this ball. + + EXAMPLES:: + + sage: RBF(-1/3).abs() # indirect doctest + [0.3333333333333333 +/- 7.04e-17] + sage: abs(RBF(-1)) + 1.000000000000000 + """ + cdef RealBall r = self._new() + arb_abs(r.value, self.value) + return r + + def below_abs(self, test_zero=False): + """ + Return a lower bound for the absolute value of this ball. + + INPUT: + + - ``test_zero`` (boolean, default ``False``) -- if ``True``, + make sure that the returned lower bound is positive, raising + an error if the ball contains zero. + + OUTPUT: + + A ball with zero radius + + EXAMPLES:: + + sage: RealBallField(8)(1/3).below_abs() + [0.33 +/- 7.82e-5] + sage: b = RealBallField(8)(1/3).below_abs() + sage: b + [0.33 +/- 7.82e-5] + sage: b.is_exact() + True + sage: QQ(b) + 169/512 + + sage: RBF(0).below_abs() + 0 + sage: RBF(0).below_abs(test_zero=True) + Traceback (most recent call last): + ... + ValueError: ball contains zero + + .. SEEALSO:: :meth:`above_abs` + """ + cdef RealBall res = self._new() + arb_get_abs_lbound_arf(arb_midref(res.value), self.value, prec(self)) + if test_zero and arb_contains_zero(res.value): + assert arb_contains_zero(self.value) + raise ValueError("ball contains zero") + return res + + def above_abs(self): + """ + Return an upper bound for the absolute value of this ball. + + OUTPUT: + + A ball with zero radius + + EXAMPLES:: + + sage: b = RealBallField(8)(1/3).above_abs() + sage: b + [0.33 +/- 3.99e-3] + sage: b.is_exact() + True + sage: QQ(b) + 171/512 + + .. SEEALSO:: :meth:`below_abs` + """ + cdef RealBall res = self._new() + arb_get_abs_ubound_arf(arb_midref(res.value), self.value, prec(self)) + return res + + def upper(self, rnd=None): + """ + Return the right endpoint of this ball, rounded upwards. + + INPUT: + + - ``rnd`` (string) -- rounding mode for the parent of the result (does + not affect its value!), see + :meth:`sage.rings.real_mpfi.RealIntervalFieldElement.upper` + + OUTPUT: + + A real number. + + EXAMPLES:: + + sage: RBF(-1/3).upper() + -0.333333333333333 + sage: RBF(-1/3).upper().parent() + Real Field with 53 bits of precision and rounding RNDU + + .. SEEALSO:: + + :meth:`lower`, :meth:`endpoints` + """ + # naive and slow + return self._real_mpfi_(RealIntervalField(prec(self))).upper(rnd) + + def lower(self, rnd=None): + """ + Return the right endpoint of this ball, rounded downwards. + + INPUT: + + - ``rnd`` (string) -- rounding mode for the parent of the result (does + not affect its value!), see + :meth:`sage.rings.real_mpfi.RealIntervalFieldElement.lower` + + OUTPUT: + + A real number. + + EXAMPLES:: + + sage: RBF(-1/3).lower() + -0.333333333333334 + sage: RBF(-1/3).lower().parent() + Real Field with 53 bits of precision and rounding RNDD + + .. SEEALSO:: :meth:`upper`, :meth:`endpoints` + """ + # naive and slow + return self._real_mpfi_(RealIntervalField(prec(self))).lower(rnd) + + def endpoints(self, rnd=None): + """ + Return the endpoints of this ball, rounded outwards. + + INPUT: + + - ``rnd`` (string) -- rounding mode for the parent of the resulting + floating-point numbers (does not affect their values!), see + :meth:`sage.rings.real_mpfi.RealIntervalFieldElement.upper` + + OUTPUT: + + A pair of real numbers. + + EXAMPLES:: + + sage: RBF(-1/3).endpoints() + (-0.333333333333334, -0.333333333333333) + + .. SEEALSO:: :meth:`lower`, :meth:`upper` + """ + # naive and slow + return self._real_mpfi_(RealIntervalField(prec(self))).endpoints(rnd) + + def union(self, other): + r""" + Return a ball containing the convex hull of ``self`` and ``other``. + + EXAMPLES:: + + sage: RBF(0).union(1).endpoints() + (0.000000000000000, 1.00000000000000) + """ + cdef RealBall my_other = self._parent.coerce(other) + cdef RealBall res = self._new() + if _do_sig(prec(self)): sig_on() + arb_union(res.value, self.value, my_other.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + # Precision and accuracy def round(self): """ Return a copy of this ball with center rounded to the precision of the parent. - .. SEEALSO:: :meth:`trim` + EXAMPLES: - EXAMPLES:: + It is possible to create balls whose midpoint is more precise that + their parent's nominal precision (see :mod:`~sage.rings.real_arb` for + more information):: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(pi.n(100)) # optional - arb - sage: b.mid() # optional - arb + sage: b = RBF(pi.n(100)) + sage: b.mid() 3.141592653589793238462643383 - sage: b.round().mid() # optional - arb - 3.1415926535898 + + The ``round()`` method rounds such a ball to its parent's precision:: + + sage: b.round().mid() + 3.14159265358979 + + .. SEEALSO:: :meth:`trim` """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -1303,18 +1627,20 @@ cdef class RealBall(RingElement): Return the effective relative accuracy of this ball measured in bits. The accuracy is defined as the difference between the position of the - top bit in the midpoint and the top bit in the radius and , minus one. - The result is clamped between plus/minus ``ARF_PREC_EXACT``. + top bit in the midpoint and the top bit in the radius, minus one. + The result is clamped between plus/minus + :meth:`~RealBallField.maximal_accuracy`. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).accuracy() # optional - arb + sage: RBF(pi).accuracy() 51 - sage: RBF(1).accuracy() # optional - arb - 9223372036854775807 - sage: RBF(NaN).accuracy() # optional - arb - -9223372036854775807 + sage: RBF(1).accuracy() == RBF.maximal_accuracy() + True + sage: RBF(NaN).accuracy() == -RBF.maximal_accuracy() + True + + .. SEEALSO:: :meth:`~RealBallField.maximal_accuracy` """ return arb_rel_accuracy_bits(self.value) @@ -1327,16 +1653,15 @@ cdef class RealBall(RingElement): resulting ball is guaranteed to contain ``self``, but is more economical if ``self`` has less than full accuracy. - .. SEEALSO:: :meth:`round` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(RIF(3.1415,3.1416)) # optional - arb - sage: b.mid() # optional - arb - 3.14155000000000 - sage: b.trim().mid() # optional - arb - 3.14155000 + sage: b = RBF(0.11111111111111, rad=.001) + sage: b.mid() + 0.111111111111110 + sage: b.trim().mid() + 0.111111104488373 + + .. SEEALSO:: :meth:`round` """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -1344,6 +1669,42 @@ cdef class RealBall(RingElement): if _do_sig(prec(self)): sig_off() return res + def add_error(self, ampl): + """ + Increase the radius of this ball by (an upper bound on) ``ampl``. + + If ``ampl`` is negative, the radius is unchanged. + + INPUT: + + - ``ampl`` -- A real ball (or an object that can be coerced to a real + ball). + + OUTPUT: + + A new real ball. + + EXAMPLES:: + + sage: err = RBF(10^-16) + sage: RBF(1).add_error(err) + [1.000000000000000 +/- 1.01e-16] + + TESTS:: + + sage: RBF(1).add_error(-1) + 1.000000000000000 + sage: RBF(0).add_error(RBF(1, rad=2.)).endpoints() + (-3.00000000745059, 3.00000000745059) + """ + cdef RealBall res = self._new() + cdef RealBall my_ampl = self._parent(ampl) + if my_ampl < 0: + my_ampl = self._parent.zero() + arb_set(res.value, self.value) + arb_add_error(res.value, my_ampl.value) + return res + # Comparisons and predicates def is_zero(self): @@ -1352,72 +1713,80 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(0).is_zero() # optional - arb + sage: RBF = RealBallField() + sage: RBF(0).is_zero() True - sage: RBF(RIF(-0.5, 0.5)).is_zero() # optional - arb + sage: RBF(RIF(-0.5, 0.5)).is_zero() False + + .. SEEALSO:: :meth:`is_nonzero` """ return arb_is_zero(self.value) - def __nonzero__(self): + def is_nonzero(self): """ Return ``True`` iff zero is not contained in the interval represented by this ball. + .. NOTE:: + + This method is not the negation of :meth:`is_zero`: it only + returns ``True`` if zero is known not to be contained in the ball. + + Use ``bool(b)`` (or, equivalently, ``not b.is_zero()``) to check if + a ball ``b`` **may** represent a nonzero number (for instance, to + determine the “degree” of a polynomial with ball coefficients). + EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: bool(RBF(pi)) # optional - arb + sage: RBF = RealBallField() + sage: RBF(pi).is_nonzero() True - sage: bool(RBF(RIF(-0.5, 0.5))) # optional - arb + sage: RBF(RIF(-0.5, 0.5)).is_nonzero() False + + .. SEEALSO:: :meth:`is_zero` """ return arb_is_nonzero(self.value) - def is_exact(self): + def __nonzero__(self): """ - Return ``True`` iff the radius of this ball is zero. + Return ``True`` iff this ball is not the zero ball, i.e. if it its + midpoint and radius are not both zero. + + This is the preferred way, for instance, to determine the “degree” of a + polynomial with ball coefficients. + + .. WARNING:: + + A “nonzero” ball in the sense of this method may represent the + value zero. Use :meth:`is_nonzero` to check that a real number + represented by a ``RealBall`` object is known to be nonzero. EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(1).is_exact() # optional - arb - True - sage: RBF(RIF(0.1, 0.2)).is_exact() # optional - arb + sage: bool(RBF(0)) # indirect doctest False + sage: bool(RBF(1/3)) + True + sage: bool(RBF(RIF(-0.5, 0.5))) + True """ - return arb_is_exact(self.value) + return not arb_is_zero(self.value) - def __richcmp__(left, right, int op): + def is_exact(self): """ - Compare ``left`` and ``right``. - - For more information, see :mod:`sage.rings.real_arb`. + Return ``True`` iff the radius of this ball is zero. EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - sage: a = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb - False - sage: b = RBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb - False - sage: a == b # optional - arb - False + sage: RBF = RealBallField() + sage: RBF(1).is_exact() + True + sage: RBF(RIF(0.1, 0.2)).is_exact() + False """ - return (left)._richcmp(right, op) + return arb_is_exact(self.value) cpdef _richcmp_(left, Element right, int op): """ @@ -1427,164 +1796,171 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True + sage: RBF = RealBallField() + sage: a = RBF(1) + sage: b = RBF(1) + sage: a is b + False + sage: a == b + True + sage: a = RBF(1/3) + sage: a.is_exact() + False + sage: b = RBF(1/3) + sage: b.is_exact() + False + sage: a == b + False TESTS: - Balls whose intersection consists of one point:: - - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - False - sage: a > b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: a = RBF(RIF(2, 5)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: b = RBF(RIF(2, 3)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/2) # optional - arb - sage: a < b # optional - arb - True - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = RBF(2) # optional - arb - sage: b = RBF(2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False - - Special values:: - - sage: inf = RBF(+infinity) # optional - arb - sage: other_inf = RBF(+infinity, 42.r) # optional - arb - sage: neg_inf = RBF(-infinity) # optional - arb - sage: extended_line = 1/RBF(0) # optional - arb - sage: exact_nan = inf - inf # optional - arb - sage: exact_nan.mid(), exact_nan.rad() # optional - arb - (NaN, 0.00000000) - sage: other_exact_nan = inf - inf # optional - arb - - :: - - sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb - (False, False, False) - sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb - (False, False, False) - sage: from operator import eq, ne, le, lt, ge, gt # optional - arb - sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb - sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb - False - sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb - False - - :: - - sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb - True - sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb - True - sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb - True - sage: neg_inf < extended_line or extended_line < inf # optional - arb - False - sage: inf > extended_line or extended_line > neg_inf # optional - arb - False - - :: - - sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb - ....: for b in [inf, neg_inf, other_inf]) - True - sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb - ....: for b2 in [inf, neg_inf, a, extended_line] - ....: if not b1 is b2) - False - sage: all(b1 != b2 and not b1 == b2 # optional - arb - ....: for b1 in [inf, neg_inf, a] - ....: for b2 in [inf, neg_inf, a] - ....: if not b1 is b2) - True - sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb - True - sage: any(inf < b or b > inf # optional - arb - ....: for b in [inf, other_inf, a, extended_line]) - False - sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb - False + Balls whose intersection consists of one point:: + + sage: a = RBF(RIF(1, 2)) + sage: b = RBF(RIF(2, 4)) + sage: a < b + False + sage: a > b + False + sage: a <= b + False + sage: a >= b + False + sage: a == b + False + sage: a != b + False + + Balls with non-trivial intersection:: + + sage: a = RBF(RIF(1, 4)) + sage: a = RBF(RIF(2, 5)) + sage: a < b + False + sage: a <= b + False + sage: a > b + False + sage: a >= b + False + sage: a == b + False + sage: a != b + False + + One ball contained in another:: + + sage: a = RBF(RIF(1, 4)) + sage: b = RBF(RIF(2, 3)) + sage: a < b + False + sage: a <= b + False + sage: a > b + False + sage: a >= b + False + sage: a == b + False + sage: a != b + False + + Disjoint balls:: + + sage: a = RBF(1/3) + sage: b = RBF(1/2) + sage: a < b + True + sage: a <= b + True + sage: a > b + False + sage: a >= b + False + sage: a == b + False + sage: a != b + True + + Exact elements:: + + sage: a = RBF(2) + sage: b = RBF(2) + sage: a.is_exact() + True + sage: b.is_exact() + True + sage: a < b + False + sage: a <= b + True + sage: a > b + False + sage: a >= b + True + sage: a == b + True + sage: a != b + False + + Special values:: + + sage: inf = RBF(+infinity) + sage: other_inf = RBF(+infinity, 42.r) + sage: neg_inf = RBF(-infinity) + sage: extended_line = 1/RBF(0) + sage: exact_nan = inf - inf + sage: exact_nan.mid(), exact_nan.rad() + (NaN, 0.00000000) + sage: other_exact_nan = inf - inf + + :: + + sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan + (False, False, False) + sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan + (False, False, False) + sage: from operator import eq, ne, le, lt, ge, gt + sage: ops = [eq, ne, le, lt, ge, gt] + sage: any(op(exact_nan, other_exact_nan) for op in ops) + False + sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) + False + + :: + + sage: neg_inf < a < inf and inf > a > neg_inf + True + sage: neg_inf <= b <= inf and inf >= b >= neg_inf + True + sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf + True + sage: neg_inf < extended_line or extended_line < inf + False + sage: inf > extended_line or extended_line > neg_inf + False + + :: + + sage: all(b <= b == b >= b and not (b < b or b != b or b > b) + ....: for b in [inf, neg_inf, other_inf]) + True + sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] + ....: for b2 in [inf, neg_inf, a, extended_line] + ....: if not b1 is b2) + False + sage: all(b1 != b2 and not b1 == b2 + ....: for b1 in [inf, neg_inf, a] + ....: for b2 in [inf, neg_inf, a] + ....: if not b1 is b2) + True + sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf + True + sage: any(inf < b or b > inf + ....: for b in [inf, other_inf, a, extended_line]) + False + sage: any(inf <= b or b >= inf for b in [a, extended_line]) + False """ cdef RealBall lt, rt cdef arb_t difference @@ -1646,6 +2022,78 @@ cdef class RealBall(RingElement): return True assert False, "not reached" + def min(self, *others): + """ + Return a ball containing the minimum of this ball and the + remaining arguments. + + EXAMPLES:: + + sage: RBF(1, rad=.5).min(0) + 0 + + sage: RBF(0, rad=2.).min(RBF(0, rad=1.)).endpoints() + (-2.00000000651926, 1.00000000465662) + + sage: RBF(infinity).min(3, 1/3) + [0.3333333333333333 +/- 7.04e-17] + + Note that calls involving NaNs try to return a number when possible. + This is consistent with IEEE-754-2008 but may be surprising. :: + + sage: RBF('nan').min(0) + 0 + sage: RBF('nan').min(RBF('nan')) + nan + + .. SEEALSO:: :meth:`max` + + TESTS:: + + sage: RBF(0).min() + 0 + sage: RBF(infinity).min().rad() + 0.00000000 + """ + iv = self._real_mpfi_(RealIntervalField(prec(self))) + my_others = [self._parent.coerce(x) for x in others] + return self._parent(iv.min(*my_others)) + + def max(self, *others): + """ + Return a ball containing the maximum of this ball and the + remaining arguments. + + EXAMPLES:: + + sage: RBF(-1, rad=.5).max(0) + 0 + + sage: RBF(0, rad=2.).max(RBF(0, rad=1.)).endpoints() + (-1.00000000465662, 2.00000000651926) + + sage: RBF(-infinity).max(-3, 1/3) + [0.3333333333333333 +/- 7.04e-17] + + Note that calls involving NaNs try to return a number when possible. + This is consistent with IEEE-754-2008 but may be surprising. :: + + sage: RBF('nan').max(0) + 0 + sage: RBF('nan').max(RBF('nan')) + nan + + .. SEEALSO:: :meth:`min` + + TESTS:: + + sage: RBF(0).max() + 0 + """ + iv = self._real_mpfi_(RealIntervalField(prec(self))) + my_others = [self._parent.coerce(x) for x in others] + return self._parent(iv.max(*my_others)) + def is_finite(self): """ Return True iff the midpoint and radius of this ball are both @@ -1653,10 +2101,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (RBF(2)^(2^1000)).is_finite() # optional - arb + sage: (RBF(2)^(2^1000)).is_finite() True - sage: RBF(oo).is_finite() # optional - arb + sage: RBF(oo).is_finite() False """ return arb_is_finite(self.value) @@ -1675,12 +2122,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).identical(RBF(3)-RBF(2)) # optional - arb + sage: RBF(1).identical(RBF(3)-RBF(2)) True - sage: RBF(1, rad=0.25r).identical(RBF(1, rad=0.25r)) # optional - arb + sage: RBF(1, rad=0.25r).identical(RBF(1, rad=0.25r)) True - sage: RBF(1).identical(RBF(1, rad=0.25r)) # optional - arb + sage: RBF(1).identical(RBF(1, rad=0.25r)) False """ return arb_equal(self.value, other.value) @@ -1695,52 +2141,50 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).overlaps(RBF(pi) + 2**(-100)) # optional - arb + sage: RBF(pi).overlaps(RBF(pi) + 2**(-100)) True - sage: RBF(pi).overlaps(RBF(3)) # optional - arb + sage: RBF(pi).overlaps(RBF(3)) False """ return arb_overlaps(self.value, other.value) def contains_exact(self, other): """ - Returns nonzero *iff* the given number (or ball) ``other`` is contained + Return ``True`` *iff* the given number (or ball) ``other`` is contained in the interval represented by ``self``. - If ``self`` contains NaN, this function always returns nonzero (as + If ``self`` contains NaN, this function always returns ``True`` (as it could represent anything, and in particular could represent all the points included in ``other``). If ``other`` contains NaN and ``self`` - does not, it always returns zero. + does not, it always returns ``False``. Use ``other in self`` for a test that works for a wider range of inputs but may return false negatives. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(1) # optional - arb - sage: b.contains_exact(1) # optional - arb + sage: b = RBF(1) + sage: b.contains_exact(1) True - sage: b.contains_exact(QQ(1)) # optional - arb + sage: b.contains_exact(QQ(1)) True - sage: b.contains_exact(1.) # optional - arb + sage: b.contains_exact(1.) True - sage: b.contains_exact(b) # optional - arb + sage: b.contains_exact(b) True :: - sage: RBF(1/3).contains_exact(1/3) # optional - arb + sage: RBF(1/3).contains_exact(1/3) True - sage: RBF(sqrt(2)).contains_exact(sqrt(2)) # optional - arb + sage: RBF(sqrt(2)).contains_exact(sqrt(2)) Traceback (most recent call last): ... - TypeError + TypeError: unsupported type: TESTS:: - sage: b.contains_exact(1r) # optional - arb + sage: b.contains_exact(1r) True """ @@ -1752,20 +2196,20 @@ cdef class RealBall(RingElement): res = arb_contains(self.value, ( other).value) elif isinstance(other, int): res = arb_contains_si(self.value, PyInt_AS_LONG(other)) - elif isinstance(other, sage.rings.integer.Integer): + elif isinstance(other, Integer): fmpz_init(tmpz) - fmpz_set_mpz(tmpz, ( other).value) + fmpz_set_mpz(tmpz, ( other).value) res = arb_contains_fmpz(self.value, tmpz) fmpz_clear(tmpz) - elif isinstance(other, sage.rings.rational.Rational): + elif isinstance(other, Rational): fmpq_init(tmpq) - fmpq_set_mpq(tmpq, ( other).value) + fmpq_set_mpq(tmpq, ( other).value) res = arb_contains_fmpq(self.value, tmpq) fmpq_clear(tmpq) elif isinstance(other, RealNumber): res = arb_contains_mpfr(self.value, ( other).value) else: - raise TypeError + raise TypeError("unsupported type: " + str(type(other))) finally: if _do_sig(prec(self)): sig_off() return res @@ -1777,29 +2221,42 @@ cdef class RealBall(RingElement): The test is done using interval arithmetic with a precision determined by the parent of ``self`` and may return false negatives. - .. SEEALSO:: :meth:`contains_exact` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF, RealBallField # optional - arb - sage: sqrt(2) in RBF(sqrt(2)) # optional - arb + sage: sqrt(2) in RBF(sqrt(2)) True A false negative:: - sage: sqrt(2) in RBF(RealBallField(100)(sqrt(2))) # optional - arb + sage: sqrt(2) in RBF(RealBallField(100)(sqrt(2))) False + + .. SEEALSO:: :meth:`contains_exact` """ return self.contains_exact(self._parent(other)) + def contains_zero(self): + """ + Return ``True`` iff this ball contains zero. + + EXAMPLES:: + + sage: RBF(0).contains_zero() + True + sage: RBF(RIF(-1, 1)).contains_zero() + True + sage: RBF(1/3).contains_zero() + False + """ + return arb_contains_zero(self.value) + def is_negative_infinity(self): """ Return ``True`` if this ball is the point -∞. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-infinity).is_negative_infinity() # optional - arb + sage: RBF(-infinity).is_negative_infinity() True """ return (arf_is_neg_inf(arb_midref(self.value)) @@ -1811,8 +2268,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(infinity).is_positive_infinity() # optional - arb + sage: RBF(infinity).is_positive_infinity() True """ return (arf_is_pos_inf(arb_midref(self.value)) @@ -1835,16 +2291,15 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(infinity).is_infinity() # optional - arb + sage: RBF(infinity).is_infinity() True - sage: RBF(-infinity).is_infinity() # optional - arb + sage: RBF(-infinity).is_infinity() True - sage: RBF(NaN).is_infinity() # optional - arb + sage: RBF(NaN).is_infinity() True - sage: (~RBF(0)).is_infinity() # optional - arb + sage: (~RBF(0)).is_infinity() True - sage: RBF(42, rad=1.r).is_infinity() # optional - arb + sage: RBF(42, rad=1.r).is_infinity() False """ return not self.is_finite() @@ -1857,8 +2312,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: -RBF(1/3) # optional - arb + sage: -RBF(1/3) [-0.3333333333333333 +/- 7.04e-17] """ cdef RealBall res = self._new() @@ -1874,12 +2328,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: ~RBF(5) # optional - arb + sage: ~RBF(5) [0.2000000000000000 +/- 4.45e-17] - sage: ~RBF(0) # optional - arb + sage: ~RBF(0) [+/- inf] - sage: RBF(RIF(-0.1,0.1)) # optional - arb + sage: RBF(RIF(-0.1,0.1)) [+/- 0.101] """ @@ -1898,8 +2351,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1) + RBF(1/3) # optional - arb + sage: RBF(1) + RBF(1/3) [1.333333333333333 +/- 5.37e-16] """ cdef RealBall res = self._new() @@ -1918,8 +2370,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1) - RBF(1/3) # optional - arb + sage: RBF(1) - RBF(1/3) [0.666666666666667 +/- 5.37e-16] """ cdef RealBall res = self._new() @@ -1938,8 +2389,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-2) * RBF(1/3) # optional - arb + sage: RBF(-2) * RBF(1/3) [-0.666666666666667 +/- 4.82e-16] """ cdef RealBall res = self._new() @@ -1958,10 +2408,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi)/RBF(e) # optional - arb + sage: RBF(pi)/RBF(e) [1.155727349790922 +/- 8.43e-16] - sage: RBF(2)/RBF(0) # optional - arb + sage: RBF(2)/RBF(0) [+/- inf] """ cdef RealBall res = self._new() @@ -1974,30 +2423,29 @@ cdef class RealBall(RingElement): """ EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(e)^17 # optional - arb + sage: RBF(e)^17 [24154952.7535753 +/- 9.30e-8] - sage: RBF(e)^(-1) # optional - arb + sage: RBF(e)^(-1) [0.367879441171442 +/- 4.50e-16] - sage: RBF(e)^(1/2) # optional - arb + sage: RBF(e)^(1/2) [1.648721270700128 +/- 4.96e-16] - sage: RBF(e)^RBF(pi) # optional - arb + sage: RBF(e)^RBF(pi) [23.1406926327793 +/- 9.16e-14] :: - sage: RBF(-1)^(1/3) # optional - arb + sage: RBF(-1)^(1/3) nan - sage: RBF(0)^(-1) # optional - arb + sage: RBF(0)^(-1) [+/- inf] - sage: RBF(-e)**RBF(pi) # optional - arb + sage: RBF(-e)**RBF(pi) nan TESTS:: - sage: RBF(e)**(2r) # optional - arb + sage: RBF(e)**(2r) [7.38905609893065 +/- 4.68e-15] - sage: RBF(e)**(-1r) # optional - arb + sage: RBF(e)**(-1r) [0.367879441171442 +/- 4.50e-16] """ cdef fmpz_t tmpz @@ -2009,10 +2457,10 @@ cdef class RealBall(RingElement): if _do_sig(prec(self)): sig_on() arb_pow_ui(res.value, self.value, PyInt_AS_LONG(expo), prec(self)) if _do_sig(prec(self)): sig_off() - elif isinstance(expo, sage.rings.integer.Integer): + elif isinstance(expo, Integer): if _do_sig(prec(self)): sig_on() fmpz_init(tmpz) - fmpz_set_mpz(tmpz, ( expo).value) + fmpz_set_mpz(tmpz, ( expo).value) arb_pow_fmpz(res.value, self.value, tmpz, prec(self)) fmpz_clear(tmpz) if _do_sig(prec(self)): sig_off() @@ -2030,10 +2478,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).sqrt() # optional - arb + sage: RBF(2).sqrt() [1.414213562373095 +/- 2.99e-16] - sage: RBF(-1/3).sqrt() # optional - arb + sage: RBF(-1/3).sqrt() nan """ cdef RealBall res = self._new() @@ -2051,12 +2498,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).sqrtpos() # optional - arb + sage: RBF(2).sqrtpos() [1.414213562373095 +/- 2.99e-16] - sage: RBF(-1/3).sqrtpos() # optional - arb + sage: RBF(-1/3).sqrtpos() 0 - sage: RBF(0, rad=2.r).sqrtpos() # optional - arb + sage: RBF(0, rad=2.r).sqrtpos() [+/- 1.42] """ cdef RealBall res = self._new() @@ -2073,10 +2519,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).rsqrt() # optional - arb + sage: RBF(2).rsqrt() [0.707106781186547 +/- 5.73e-16] - sage: RBF(0).rsqrt() # optional - arb + sage: RBF(0).rsqrt() nan """ cdef RealBall res = self._new() @@ -2092,11 +2537,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(10^(-20)) # optional - arb - sage: (1 + eps).sqrt() - 1 # optional - arb + sage: eps = RBF(10^(-20)) + sage: (1 + eps).sqrt() - 1 [+/- 1.12e-16] - sage: eps.sqrt1pm1() # optional - arb + sage: eps.sqrt1pm1() [5.00000000000000e-21 +/- 2.54e-36] """ cdef RealBall res = self._new() @@ -2113,8 +2557,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1000+1/3, rad=1.r).floor() # optional - arb + sage: RBF(1000+1/3, rad=1.r).floor() [1.00e+3 +/- 1.01] """ cdef RealBall res = self._new() @@ -2129,8 +2572,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1000+1/3, rad=1.r).ceil() # optional - arb + sage: RBF(1000+1/3, rad=1.r).ceil() [1.00e+3 +/- 2.01] """ cdef RealBall res = self._new() @@ -2139,6 +2581,90 @@ cdef class RealBall(RingElement): if _do_sig(prec(self)): sig_off() return res + def __lshift__(val, shift): + r""" + If ``val`` is a ``RealBall`` and ``shift`` is an integer, return the + ball obtained by shifting the center and radius of ``val`` to the left + by ``shift`` bits. + + INPUT: + + - ``shift`` -- integer, may be negative. + + EXAMPLES:: + + sage: RBF(1/3) << 2 # indirect doctest + [1.333333333333333 +/- 4.82e-16] + sage: RBF(1) << -1 + 0.5000000000000000 + + TESTS:: + + sage: RBF(1) << (2^100) + [2.285367694229514e+381600854690147056244358827360 +/- 2.98e+381600854690147056244358827344] + sage: RBF(1) << (-2^100) + [4.375663498372584e-381600854690147056244358827361 +/- 9.57e-381600854690147056244358827378] + + sage: "a" << RBF(1/3) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for <<: 'str' and 'RealBall' + sage: RBF(1) << RBF(1/3) + Traceback (most recent call last): + ... + TypeError: shift should be an integer + """ + cdef fmpz_t tmpz + # the RealBall might be shift, not val + if not isinstance(val, RealBall): + raise TypeError("unsupported operand type(s) for <<: '{}' and '{}'" + .format(type(val).__name__, type(shift).__name__)) + cdef RealBall self = val + cdef RealBall res = self._new() + if isinstance(shift, int): + arb_mul_2exp_si(res.value, self.value, PyInt_AS_LONG(shift)) + elif isinstance(shift, Integer): + sig_on() + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( shift).value) + arb_mul_2exp_fmpz(res.value, self.value, tmpz) + fmpz_clear(tmpz) + sig_off() + else: + raise TypeError("shift should be an integer") + return res + + def __rshift__(val, shift): + r""" + If ``val`` is a ``RealBall`` and ``shift`` is an integer, return the + ball obtained by shifting the center and radius of ``val`` to the right + by ``shift`` bits. + + INPUT: + + - ``shift`` -- integer, may be negative. + + EXAMPLES:: + + sage: RBF(4) >> 2 + 1.000000000000000 + sage: RBF(1/3) >> -2 + [1.333333333333333 +/- 4.82e-16] + + TESTS:: + + sage: "a" >> RBF(1/3) + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for >>: 'str' and 'RealBall' + """ + # the RealBall might be shift, not val + if isinstance(val, RealBall): + return val << (-shift) + else: + raise TypeError("unsupported operand type(s) for >>: '{}' and '{}'" + .format(type(val).__name__, type(shift).__name__)) + # Elementary functions def log(self): @@ -2147,10 +2673,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(3).log() # optional - arb + sage: RBF(3).log() [1.098612288668110 +/- 6.63e-16] - sage: RBF(-1/3).log() # optional - arb + sage: RBF(-1/3).log() nan """ cdef RealBall res = self._new() @@ -2166,11 +2691,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(1e-30) # optional - arb - sage: (1 + eps).log() # optional - arb + sage: eps = RBF(1e-30) + sage: (1 + eps).log() [+/- 2.23e-16] - sage: eps.log1p() # optional - arb + sage: eps.log1p() [1.00000000000000e-30 +/- 2.68e-46] """ cdef RealBall res = self._new() @@ -2185,8 +2709,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).exp() # optional - arb + sage: RBF(1).exp() [2.718281828459045 +/- 5.41e-16] """ cdef RealBall res = self._new() @@ -2202,11 +2725,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(1e-30) # optional - arb - sage: exp(eps) - 1 # optional - arb + sage: eps = RBF(1e-30) + sage: exp(eps) - 1 [+/- 3.16e-30] - sage: eps.expm1() # optional - arb + sage: eps.expm1() [1.000000000000000e-30 +/- 8.34e-47] """ cdef RealBall res = self._new() @@ -2219,13 +2741,12 @@ cdef class RealBall(RingElement): """ Return the sine of this ball. - .. seealso:: :meth:`~sage.rings.real_arb.RealBallField.sinpi` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).sin() # abs tol 1e-16, optional - arb + sage: RBF(pi).sin() # abs tol 1e-16 [+/- 5.69e-16] + + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBallField.sinpi` """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -2237,13 +2758,12 @@ cdef class RealBall(RingElement): """ Return the cosine of this ball. - .. seealso:: :meth:`~sage.rings.real_arb.RealBallField.cospi` - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).cos() # abs tol 1e-16, optional - arb + sage: RBF(pi).cos() # abs tol 1e-16 [-1.00000000000000 +/- 6.69e-16] + + .. SEEALSO:: :meth:`~sage.rings.real_arb.RealBallField.cospi` """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -2257,10 +2777,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).tan() # optional - arb + sage: RBF(1).tan() [1.557407724654902 +/- 3.26e-16] - sage: RBF(pi/2).tan() # optional - arb + sage: RBF(pi/2).tan() [+/- inf] """ cdef RealBall res = self._new() @@ -2275,10 +2794,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).cot() # optional - arb + sage: RBF(1).cot() [0.642092615934331 +/- 4.79e-16] - sage: RBF(pi).cot() # optional - arb + sage: RBF(pi).cot() [+/- inf] """ cdef RealBall res = self._new() @@ -2293,10 +2811,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arcsin() # optional - arb + sage: RBF(1).arcsin() [1.570796326794897 +/- 6.65e-16] - sage: RBF(1, rad=.125r).arcsin() # optional - arb + sage: RBF(1, rad=.125r).arcsin() nan """ cdef RealBall res = self._new() @@ -2311,10 +2828,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arccos() # optional - arb + sage: RBF(1).arccos() 0 - sage: RBF(1, rad=.125r).arccos() # optional - arb + sage: RBF(1, rad=.125r).arccos() nan """ cdef RealBall res = self._new() @@ -2329,8 +2845,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arctan() # optional - arb + sage: RBF(1).arctan() [0.785398163397448 +/- 3.91e-16] """ cdef RealBall res = self._new() @@ -2345,8 +2860,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).sinh() # optional - arb + sage: RBF(1).sinh() [1.175201193643801 +/- 6.18e-16] """ cdef RealBall res = self._new() @@ -2361,8 +2875,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).cosh() # optional - arb + sage: RBF(1).cosh() [1.543080634815244 +/- 5.28e-16] """ cdef RealBall res = self._new() @@ -2377,8 +2890,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).tanh() # optional - arb + sage: RBF(1).tanh() [0.761594155955765 +/- 2.81e-16] """ cdef RealBall res = self._new() @@ -2393,10 +2905,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).coth() # optional - arb + sage: RBF(1).coth() [1.313035285499331 +/- 4.97e-16] - sage: RBF(0).coth() # optional - arb + sage: RBF(0).coth() [+/- inf] """ cdef RealBall res = self._new() @@ -2411,10 +2922,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arcsinh() # optional - arb + sage: RBF(1).arcsinh() [0.881373587019543 +/- 1.87e-16] - sage: RBF(0).arcsinh() # optional - arb + sage: RBF(0).arcsinh() 0 """ cdef RealBall res = self._new() @@ -2429,12 +2939,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).arccosh() # optional - arb + sage: RBF(2).arccosh() [1.316957896924817 +/- 6.61e-16] - sage: RBF(1).arccosh() # optional - arb + sage: RBF(1).arccosh() 0 - sage: RBF(0).arccosh() # optional - arb + sage: RBF(0).arccosh() nan """ cdef RealBall res = self._new() @@ -2449,12 +2958,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(0).arctanh() # optional - arb + sage: RBF(0).arctanh() 0 - sage: RBF(1/2).arctanh() # optional - arb + sage: RBF(1/2).arctanh() [0.549306144334055 +/- 3.32e-16] - sage: RBF(1).arctanh() # optional - arb + sage: RBF(1).arctanh() nan """ cdef RealBall res = self._new() @@ -2470,13 +2978,15 @@ cdef class RealBall(RingElement): Return the image of this ball by the Euler Gamma function. For integer and rational arguments, - :meth:`~sage.rings.real_arb.RealBall.gamma` may be faster. + :meth:`~sage.rings.real_arb.RealBallField.gamma` may be faster. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1/2).gamma() # optional - arb + sage: RBF(1/2).gamma() [1.772453850905516 +/- 3.41e-16] + + .. SEEALSO:: + :meth:`~sage.rings.real_arb.RealBallField.gamma` """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -2488,13 +2998,12 @@ cdef class RealBall(RingElement): """ Return the image of this ball by the logarithmic Gamma function. - The complex branch structure is assumed, so if ``self`` ≤ 0, the result + The complex branch structure is assumed, so if ``self`` <= 0, the result is an indeterminate interval. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1/2).log_gamma() # optional - arb + sage: RBF(1/2).log_gamma() [0.572364942924700 +/- 4.87e-16] """ cdef RealBall res = self._new() @@ -2510,10 +3019,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-1).rgamma() # optional - arb + sage: RBF(-1).rgamma() 0 - sage: RBF(3).rgamma() # optional - arb + sage: RBF(3).rgamma() 0.5000000000000000 """ cdef RealBall res = self._new() @@ -2528,8 +3036,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).psi() # optional - arb + sage: RBF(1).psi() [-0.577215664901533 +/- 3.85e-16] """ @@ -2550,12 +3057,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-1).zeta() # optional - arb + sage: RBF(-1).zeta() [-0.0833333333333333 +/- 4.36e-17] - sage: RBF(-1).zeta(1) # optional - arb + sage: RBF(-1).zeta(1) [-0.0833333333333333 +/- 6.81e-17] - sage: RBF(-1).zeta(2) # abs tol 1e-16, optional - arb + sage: RBF(-1).zeta(2) # abs tol 1e-16 [-1.083333333333333 +/- 4.09e-16] """ cdef RealBall a_ball @@ -2577,27 +3083,26 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: polylog(0, -1) # optional - arb + sage: polylog(0, -1) -1/2 - sage: RBF(-1).polylog(0) # optional - arb + sage: RBF(-1).polylog(0) [-0.50000000000000 +/- 1.29e-15] - sage: polylog(1, 1/2) # optional - arb + sage: polylog(1, 1/2) -log(1/2) - sage: RBF(1/2).polylog(1) # optional - arb + sage: RBF(1/2).polylog(1) [0.6931471805599 +/- 5.02e-14] - sage: RBF(1/3).polylog(1/2) # optional - arb + sage: RBF(1/3).polylog(1/2) [0.44210883528067 +/- 6.75e-15] - sage: RBF(1/3).polylog(RLF(pi)) # optional - arb + sage: RBF(1/3).polylog(RLF(pi)) [0.34728895057225 +/- 5.51e-15] TESTS:: - sage: RBF(1/3).polylog(2r) # optional - arb + sage: RBF(1/3).polylog(2r) [0.36621322997706 +/- 4.62e-15] """ cdef RealBall s_as_ball - cdef sage.rings.integer.Integer s_as_Integer + cdef Integer s_as_Integer cdef RealBall res = self._new() try: s_as_Integer = ZZ.coerce(s) @@ -2616,27 +3121,26 @@ cdef class RealBall(RingElement): def chebyshev_T(self, n): """ - Evaluates the Chebyshev polynomial of the first kind ``T_n`` at this + Evaluate the Chebyshev polynomial of the first kind ``T_n`` at this ball. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).chebyshev_T(0) # optional - arb + sage: RBF(pi).chebyshev_T(0) 1.000000000000000 - sage: RBF(pi).chebyshev_T(1) # abs tol 1e-16, optional - arb + sage: RBF(pi).chebyshev_T(1) # abs tol 1e-16 [3.141592653589793 +/- 5.62e-16] - sage: RBF(pi).chebyshev_T(10**20) # optional - arb + sage: RBF(pi).chebyshev_T(10**20) Traceback (most recent call last): ... ValueError: index too large - sage: RBF(pi).chebyshev_T(-1) # optional - arb + sage: RBF(pi).chebyshev_T(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index """ cdef RealBall res = self._new() - cdef sage.rings.integer.Integer n_as_Integer = ZZ.coerce(n) + cdef Integer n_as_Integer = ZZ.coerce(n) if mpz_fits_ulong_p(n_as_Integer.value): if _do_sig(prec(self)): sig_on() arb_chebyshev_t_ui(res.value, mpz_get_ui(n_as_Integer.value), self.value, prec(self)) @@ -2649,27 +3153,26 @@ cdef class RealBall(RingElement): def chebyshev_U(self, n): """ - Evaluates the Chebyshev polynomial of the second kind ``U_n`` at this + Evaluate the Chebyshev polynomial of the second kind ``U_n`` at this ball. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).chebyshev_U(0) # optional - arb + sage: RBF(pi).chebyshev_U(0) 1.000000000000000 - sage: RBF(pi).chebyshev_U(1) # optional - arb + sage: RBF(pi).chebyshev_U(1) [6.28318530717959 +/- 4.66e-15] - sage: RBF(pi).chebyshev_U(10**20) # optional - arb + sage: RBF(pi).chebyshev_U(10**20) Traceback (most recent call last): ... ValueError: index too large - sage: RBF(pi).chebyshev_U(-1) # optional - arb + sage: RBF(pi).chebyshev_U(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index """ cdef RealBall res = self._new() - cdef sage.rings.integer.Integer n_as_Integer = ZZ.coerce(n) + cdef Integer n_as_Integer = ZZ.coerce(n) if mpz_fits_ulong_p(n_as_Integer.value): if _do_sig(prec(self)): sig_on() arb_chebyshev_u_ui(res.value, mpz_get_ui(n_as_Integer.value), self.value, prec(self)) @@ -2686,10 +3189,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).agm(1) # optional - arb + sage: RBF(1).agm(1) 1.000000000000000 - sage: RBF(sqrt(2)).agm(1)^(-1) # optional - arb + sage: RBF(sqrt(2)).agm(1)^(-1) [0.83462684167407 +/- 4.31e-15] """ cdef RealBall other_as_ball @@ -2700,6 +3202,4 @@ cdef class RealBall(RingElement): if _do_sig(prec(self)): sig_off() return res - # TODO: once CBF is there, also wrap functions that only exist in acb* - RBF = RealBallField() diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index 09be7f11717..a0a22c4e7d3 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -45,7 +45,6 @@ from cpython.float cimport * include "sage/ext/python_debug.pxi" include 'sage/ext/cdefs.pxi' include 'sage/ext/stdsage.pxi' -include 'sage/ext/random.pxi' include 'sage/ext/interrupt.pxi' from sage.libs.gsl.all cimport * cimport libc.math @@ -66,6 +65,7 @@ from sage.rings.integer_ring import ZZ from sage.categories.morphism cimport Morphism from sage.structure.coerce cimport is_numpy_type +from sage.misc.randstate cimport randstate, current_randstate def is_RealDoubleField(x): @@ -140,7 +140,7 @@ cdef class RealDoubleField_class(Field): sage: TestSuite(R).run() """ from sage.categories.fields import Fields - Field.__init__(self, self, category = Fields()) + Field.__init__(self, self, category=Fields().Metric().Complete()) self._populate_coercion_lists_(element_constructor=RealDoubleElement, init_no_parent=True, convert_method_name='_real_double_') diff --git a/src/sage/rings/real_interval_absolute.pyx b/src/sage/rings/real_interval_absolute.pyx index f4754c2af2f..1fdcaf2ce0d 100644 --- a/src/sage/rings/real_interval_absolute.pyx +++ b/src/sage/rings/real_interval_absolute.pyx @@ -1,3 +1,7 @@ +""" +Real intervals with a fixed absolute precision +""" + from sage.ext.stdsage cimport PY_NEW from sage.libs.gmp.mpz cimport * diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index 508b8a3487e..fce4acfed0d 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -1,4 +1,6 @@ """ +Lazy real and complex numbers + These classes are very lazy, in the sense that it doesn't really do anything but simply sits between exact rings of characteristic 0 and the real numbers. The values are actually computed when they are cast into a field of fixed @@ -666,6 +668,26 @@ cdef class LazyFieldElement(FieldElement): True sage: RLF(3) == RLF(4) False + sage: RLF(3) < RLF(5/3) + False + + TESTS:: + + sage: from sage.rings.real_lazy import LazyBinop + sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) + False + sage: from sage.rings.real_lazy import LazyWrapper + sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) + False + sage: from sage.rings.real_lazy import LazyUnop + sage: RLF(3) < LazyUnop(RLF, 2, sqrt) + False + sage: from sage.rings.real_lazy import LazyNamedUnop + sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') + False + sage: from sage.rings.real_lazy import LazyConstant + sage: RLF(3) < LazyConstant(RLF, 'e') + False """ left = self try: @@ -677,17 +699,6 @@ cdef class LazyFieldElement(FieldElement): left, right = self.approx(), other.approx() return cmp(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: RLF(3) < RLF(5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -991,18 +1002,6 @@ cdef class LazyWrapper(LazyFieldElement): """ return not not self._value - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyWrapper - sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1174,18 +1173,6 @@ cdef class LazyBinop(LazyFieldElement): # We only do a call here because it is a python call. return self._op(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyBinop - sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1278,18 +1265,6 @@ cdef class LazyUnop(LazyFieldElement): return ~arg return self._op(self._arg.eval(R)) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyUnop - sage: RLF(3) < LazyUnop(RLF, 2, sqrt) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1409,18 +1384,6 @@ cdef class LazyNamedUnop(LazyUnop): interval_field = self._parent.interval_field() return self.eval(interval_field._middle_field()) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyNamedUnop - sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1547,18 +1510,6 @@ cdef class LazyConstant(LazyFieldElement): self._extra_args = args return self - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyConstant - sage: RLF(3) < LazyConstant(RLF, 'e') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. diff --git a/src/sage/rings/real_mpfi.pxd b/src/sage/rings/real_mpfi.pxd index 5996b03d2f5..865381d25a6 100644 --- a/src/sage/rings/real_mpfi.pxd +++ b/src/sage/rings/real_mpfi.pxd @@ -2,15 +2,13 @@ from sage.libs.mpfi cimport * cimport sage.rings.ring -cimport sage.structure.element from sage.structure.element cimport RingElement -from rational import Rational from rational cimport Rational cimport real_mpfr -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) # forward decl +cdef class RealIntervalFieldElement(RingElement) # forward decl cdef class RealIntervalField_class(sage.rings.ring.Field): cdef int __prec @@ -32,14 +30,17 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): cdef real_mpfr.RealField_class __lower_field cdef real_mpfr.RealField_class __middle_field cdef real_mpfr.RealField_class __upper_field - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with parent ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self) -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): cdef mpfi_t value - cdef char init - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with same parent as ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self._parent) cdef RealIntervalFieldElement abs(RealIntervalFieldElement self) cdef Rational _simplest_rational_helper(self) cpdef _str_question_style(self, int base, int error_digits, e, bint prefer_sci) diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index c5e7ee6aa0e..7f2c6d1c296 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -239,6 +239,7 @@ Comparisons with numpy types are right (see :trac:`17758` and :trac:`18076`):: import math # for log import sys +import operator include 'sage/ext/interrupt.pxi' include "sage/ext/cdefs.pxi" @@ -246,32 +247,21 @@ from cpython.mem cimport * from cpython.string cimport * cimport sage.rings.ring -import sage.rings.ring - cimport sage.structure.element from sage.structure.element cimport RingElement, Element, ModuleElement -import sage.structure.element cimport real_mpfr -from real_mpfr cimport RealField_class, RealNumber -from real_mpfr import RealField -import real_mpfr - -import operator +from real_mpfr cimport RealField_class, RealNumber, RealField +from sage.libs.mpfr cimport MPFR_RNDN, MPFR_RNDZ, MPFR_RNDU, MPFR_RNDD, MPFR_RNDA -from integer import Integer from integer cimport Integer - -from real_double import RealDoubleElement from real_double cimport RealDoubleElement import sage.rings.complex_field - import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) #***************************************************************************** # @@ -574,17 +564,6 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): else: return RealField(self.__prec, self.sci_not, "RNDZ") - cdef RealIntervalFieldElement _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self - mpfi_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -677,7 +656,7 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): sage: R('2', base=2) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert '2' to real interval sage: a = R('1.1001', base=2); a 1.5625000? sage: a.str(2) @@ -1115,34 +1094,43 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): return self(-1) raise ValueError, "No %sth root of unity in self"%n -R = RealIntervalField() #***************************************************************************** # # RealIntervalFieldElement -- element of Real Field # -# -# #***************************************************************************** -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): """ A real number interval. """ - cdef RealIntervalFieldElement _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real interval with same parent as ``self``. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfi import RealIntervalFieldElement + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, RIF) + [.. NaN ..] """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self._parent - mpfi_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealIntervalField_class p = parent + mpfi_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealIntervalField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ - Create a real interval element. Should be called by first creating - a :class:`RealIntervalField`, as illustrated in the examples. + Initialize a real interval element. Should be called by first + creating a :class:`RealIntervalField`, as illustrated in the + examples. EXAMPLES:: @@ -1153,7 +1141,17 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: R('1.2456').str(style='brackets') '[1.0 .. 1.3]' - EXAMPLES: + :: + + sage: RIF = RealIntervalField(53) + sage: RIF(RR.pi()) + 3.1415926535897932? + sage: RIF(RDF.pi()) + 3.1415926535897932? + sage: RIF(math.pi) + 3.1415926535897932? + sage: RIF.pi() + 3.141592653589794? Rounding:: @@ -1170,94 +1168,60 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): Type: ``RealIntervalField?`` for many more examples. """ - import sage.rings.qqbar - - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfi_init2(self.value, parent.__prec) - self.init = 1 - if x is None: return - cdef RealIntervalFieldElement _x, n, d - cdef RealNumber rn, rn1 - cdef Rational rat, rat1 - cdef Integer integ, integ1 - cdef RealDoubleElement dx, dx1 - cdef int ix, ix1 + if x is None: + return + + cdef RealNumber ra, rb + cdef RealIntervalFieldElement d + if isinstance(x, RealIntervalFieldElement): - _x = x # so we can get at x.value - mpfi_set(self.value, _x.value) + mpfi_set(self.value, (x).value) elif isinstance(x, RealNumber): - rn = x - mpfi_set_fr(self.value, rn.value) + mpfi_set_fr(self.value, (x).value) elif isinstance(x, Rational): - rat = x - mpfi_set_q(self.value, rat.value) + mpfi_set_q(self.value, (x).value) elif isinstance(x, Integer): - integ = x - mpfi_set_z(self.value, integ.value) + mpfi_set_z(self.value, (x).value) + elif isinstance(x, RealDoubleElement): + mpfi_set_d(self.value, (x)._value) elif isinstance(x, int): - ix = x - mpfi_set_si(self.value, ix) + mpfi_set_si(self.value, x) + elif isinstance(x, float): + mpfi_set_d(self.value, x) + elif hasattr(x, '_real_mpfi_'): + d = x._real_mpfi_(self._parent) + mpfi_set(self.value, d.value) elif isinstance(x, tuple): try: a, b = x except ValueError: raise TypeError("tuple defining an interval must have length 2") if isinstance(a, RealNumber) and isinstance(b, RealNumber): - rn = a - rn1 = b - mpfi_interv_fr(self.value, rn.value, rn1.value) + mpfi_interv_fr(self.value, (a).value, (b).value) elif isinstance(a, RealDoubleElement) and isinstance(b, RealDoubleElement): - dx = a - dx1 = b - mpfi_interv_d(self.value, dx._value, dx1._value) + mpfi_interv_d(self.value, (a)._value, (b)._value) elif isinstance(a, Rational) and isinstance(b, Rational): - rat = a - rat1 = b - mpfi_interv_q(self.value, rat.value, rat1.value) + mpfi_interv_q(self.value, (a).value, (b).value) elif isinstance(a, Integer) and isinstance(b, Integer): - integ = a - integ1 = b - mpfi_interv_z(self.value, integ.value, integ1.value) + mpfi_interv_z(self.value, (a).value, (b).value) elif isinstance(a, int) and isinstance(b, int): - ix = a - ix1 = b - mpfi_interv_si(self.value, ix, ix1) + mpfi_interv_si(self.value, a, b) else: # generic fallback - rn = self._parent(a).lower() - rn1 = self._parent(b).upper() - mpfi_interv_fr(self.value, rn.value, rn1.value) - - elif isinstance(x, sage.rings.qqbar.AlgebraicReal): - d = x.interval(self._parent) - mpfi_set(self.value, d.value) - - elif hasattr(x, '_real_mpfi_'): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - + ra = self._parent(a).lower() + rb = self._parent(b).upper() + mpfi_interv_fr(self.value, ra.value, rb.value) + elif isinstance(x, basestring): + s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') + if mpfi_set_str(self.value, s, base): + raise TypeError("unable to convert {!r} to real interval".format(x)) else: - from sage.symbolic.expression import Expression - - if isinstance(x, Expression): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - - elif isinstance(x, str): - # string - s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') - if mpfi_set_str(self.value, s, base): - raise TypeError("Unable to convert number to real interval.") - else: - # try coercing to real - try: - rn = self._parent._lower_field()(x) - rn1 = self._parent._upper_field()(x) - except TypeError: - raise TypeError("Unable to convert number to real interval.") - mpfi_interv_fr(self.value, rn.value, rn1.value) + # try coercing to real + try: + ra = self._parent._lower_field()(x) + rb = self._parent._upper_field()(x) + except TypeError: + raise TypeError("unable to convert {!r} to real interval".format(x)) + mpfi_interv_fr(self.value, ra.value, rb.value) def __reduce__(self): """ @@ -1305,7 +1269,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: R = RealIntervalField() sage: del R # indirect doctest """ - if self.init: + if self._parent is not None: mpfi_clear(self.value) def __repr__(self): @@ -2446,15 +2410,19 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ The largest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDU`` + EXAMPLES:: sage: RIF(-2, 1).magnitude() 2.00000000000000 sage: RIF(-1, 2).magnitude() 2.00000000000000 + sage: parent(RIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__upper_field._new() mpfi_mag(x.value, self.value) return x @@ -2462,6 +2430,8 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ The smallest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDD`` + EXAMPLES:: sage: RIF(-2, 1).mignitude() @@ -2470,9 +2440,11 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): 1.00000000000000 sage: RIF(3, 4).mignitude() 3.00000000000000 + sage: parent(RIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__lower_field._new() mpfi_mig(x.value, self.value) return x @@ -3073,33 +3045,59 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): # Conversions ########################################### -# def __float__(self): -# return mpfr_get_d(self.value, (self._parent).rnd) - -# def __int__(self): -# """ -# Returns integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return int(s[:i], 32) + def _mpfr_(self, RealField_class field): + """ + Convert to a real field, honoring the rounding mode of the + real field. -# def __long__(self): -# """ -# Returns long integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return long(s[:i], 32) + EXAMPLES:: -# def __complex__(self): -# return complex(float(self)) + sage: a = RealIntervalField(30)("1.2") + sage: RR(a) + 1.20000000018626 + sage: b = RIF(-1, 3) + sage: RR(b) + 1.00000000000000 -# def _complex_number_(self): -# return sage.rings.complex_field.ComplexField(self.prec())(self) + With different rounding modes:: -# def _pari_(self): -# return sage.libs.pari.all.pari.new_with_bits_prec(str(self), (self._parent).__prec) + sage: RealField(53, rnd="RNDU")(a) + 1.20000000111759 + sage: RealField(53, rnd="RNDD")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDZ")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDU")(b) + 3.00000000000000 + sage: RealField(53, rnd="RNDD")(b) + -1.00000000000000 + sage: RealField(53, rnd="RNDZ")(b) + 0.000000000000000 + """ + cdef RealNumber x = field._new() + if field.rnd == MPFR_RNDN: + mpfi_mid(x.value, self.value) + elif field.rnd == MPFR_RNDD: + mpfi_get_left(x.value, self.value) + elif field.rnd == MPFR_RNDU: + mpfi_get_right(x.value, self.value) + elif field.rnd == MPFR_RNDZ: + if mpfi_is_strictly_pos_default(self.value): # interval is > 0 + mpfi_get_left(x.value, self.value) + elif mpfi_is_strictly_neg_default(self.value): # interval is < 0 + mpfi_get_right(x.value, self.value) + else: + mpfr_set_zero(x.value, 1) # interval contains 0 + elif field.rnd == MPFR_RNDA: + # return the endpoint which is furthest from 0 + lo, hi = self.endpoints() + if hi.abs() >= lo.abs(): + mpfi_get_right(x.value, self.value) + else: + mpfi_get_left(x.value, self.value) + else: + raise AssertionError("%s has unknown rounding mode"%field) + return x def unique_sign(self): r""" @@ -3445,11 +3443,10 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return mpfi_nan_p(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ - Rich comparison between ``left`` and ``right``. - - For more information, see :mod:`sage.rings.real_mpfi`. + Implements comparisons between intervals. (See the file header + comment for more information on interval comparison.) EXAMPLES:: @@ -3469,13 +3466,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): False sage: RIF(0, 2) > RIF(2, 3) False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Implements comparisons between intervals. (See the file header - comment for more information on interval comparison.) EXAMPLES:: @@ -3666,7 +3656,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return not (mpfr_zero_p(&self.value.left) and mpfr_zero_p(&self.value.right)) - def __cmp__(left, right): + cpdef int _cmp_(left, Element right) except -2: """ Compare two intervals lexicographically. @@ -3694,12 +3684,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: cmp(RIF(0, 1), RIF(0, 1/2)) 1 """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element right) except -2: - """ - Implements the lexicographic total order on intervals. - """ cdef RealIntervalFieldElement lt, rt lt = left @@ -3907,6 +3891,16 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: min(RIF(-1, 1), RIF(-100, 100)).endpoints() (-1.00000000000000, 1.00000000000000) + Note that calls involving NaNs try to return a number when possible. + This is consistent with IEEE-754-2008 but may be surprising. :: + + sage: RIF('nan').min(2, 1) + 1 + sage: RIF(-1/3).min(RIF('nan')) + -0.3333333333333334? + sage: RIF('nan').min(RIF('nan')) + [.. NaN ..] + .. SEEALSO:: :meth:`~sage.rings.real_mpfi.RealIntervalFieldElement.max` @@ -3916,7 +3910,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: a.min('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result @@ -3932,7 +3926,11 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): else: other = self._parent(_other) - if mpfr_cmp(&result.value.right, &other.value.left) <= 0: + if result.is_NaN(): + result = other + elif other.is_NaN(): + pass + elif mpfr_cmp(&result.value.right, &other.value.left) <= 0: pass elif mpfr_cmp(&other.value.right, &result.value.left) <= 0: result = other @@ -3997,6 +3995,16 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: max(RIF(-1, 1), RIF(-100, 100)).endpoints() (-1.00000000000000, 1.00000000000000) + Note that calls involving NaNs try to return a number when possible. + This is consistent with IEEE-754-2008 but may be surprising. :: + + sage: RIF('nan').max(1, 2) + 2 + sage: RIF(-1/3).max(RIF('nan')) + -0.3333333333333334? + sage: RIF('nan').max(RIF('nan')) + [.. NaN ..] + .. SEEALSO:: :meth:`~sage.rings.real_mpfi.RealIntervalFieldElement.min` @@ -4006,7 +4014,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: a.max('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result @@ -4022,7 +4030,11 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): else: other = self._parent(_other) - if mpfr_cmp(&result.value.right, &other.value.left) <= 0: + if result.is_NaN(): + result = other + elif other.is_NaN(): + pass + elif mpfr_cmp(&result.value.right, &other.value.left) <= 0: result = other elif mpfr_cmp(&other.value.right, &result.value.left) <= 0: pass @@ -4199,10 +4211,10 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): if isinstance(exponent, (int, long, Integer)): q, r = divmod (exponent, 2) if r == 0: # x^(2q) = (x^q)^2 - xq = sage.rings.ring_element.RingElement.__pow__(self, q) + xq = RingElement.__pow__(self, q) return xq.abs().square() else: - return sage.rings.ring_element.RingElement.__pow__(self, exponent) + return RingElement.__pow__(self, exponent) return (self.log() * exponent).exp() @@ -4984,22 +4996,15 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): A :class:`RealIntervalFieldElement`. - This uses the optional `arb library `_. - You may have to install it via ``sage -i arb``. - EXAMPLES:: - sage: psi_1 = RIF(1).psi() # optional - arb - sage: psi_1 # optional - arb + sage: psi_1 = RIF(1).psi() + sage: psi_1 -0.577215664901533? - sage: psi_1.overlaps(-RIF.euler_constant()) # optional - arb + sage: psi_1.overlaps(-RIF.euler_constant()) True """ - try: - from sage.rings.real_arb import RealBallField - except ImportError: - raise TypeError("The optional arb package is not installed. " - "Consider installing it via 'sage -i arb'") + from sage.rings.real_arb import RealBallField return RealBallField(self.precision())(self).psi()._real_mpfi_(self._parent) # MPFI does not have: agm, erf, gamma, zeta diff --git a/src/sage/rings/real_mpfr.pxd b/src/sage/rings/real_mpfr.pxd index 4b2b1465cf8..cef3184333c 100644 --- a/src/sage/rings/real_mpfr.pxd +++ b/src/sage/rings/real_mpfr.pxd @@ -2,9 +2,7 @@ from sage.libs.mpfr cimport * cimport sage.rings.ring cimport sage.structure.element - -cdef extern from "pari/pari.h": - ctypedef long* GEN +from sage.libs.pari.types cimport GEN cdef class RealNumber(sage.structure.element.RingElement) # forward decl @@ -14,13 +12,15 @@ cdef class RealField_class(sage.rings.ring.Field): cdef bint sci_not cdef mpfr_rnd_t rnd cdef object rnd_str - cdef RealNumber _new(self) - + cdef inline RealNumber _new(self): + """Return a new real number with parent ``self``.""" + return (RealNumber.__new__(RealNumber, self)) cdef class RealNumber(sage.structure.element.RingElement): cdef mpfr_t value - cdef char init - cdef RealNumber _new(self) + cdef inline RealNumber _new(self): + """Return a new real number with same parent as ``self``.""" + return (RealNumber.__new__(RealNumber, self._parent)) cdef _set(self, x, int base) cdef _set_from_GEN_REAL(self, GEN g) cdef RealNumber abs(RealNumber self) diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 867d412d6d5..c41d68d6e81 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -100,20 +100,12 @@ Make sure we don't have a new field for every new literal:: """ #***************************************************************************** -# -# Sage: System for Algebra and Geometry Experimentation -# # Copyright (C) 2005-2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# 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/ #***************************************************************************** @@ -123,10 +115,10 @@ import re include 'sage/ext/interrupt.pxi' include "sage/ext/stdsage.pxi" -include "sage/ext/random.pxi" include 'sage/libs/pari/pari_err.pxi' from sage.libs.gmp.mpz cimport * +from sage.misc.randstate cimport randstate, current_randstate cimport sage.rings.ring cimport sage.structure.element @@ -162,8 +154,6 @@ import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealNumber(sage.structure.element.RingElement) - #***************************************************************************** # # Implementation @@ -445,7 +435,6 @@ cdef class RealField_class(sage.rings.ring.Field): Real Field with 17 bits of precision and rounding RNDD """ global MY_MPFR_PREC_MAX - cdef RealNumber rn if prec < MPFR_PREC_MIN or prec > MY_MPFR_PREC_MAX: raise ValueError, "prec (=%s) must be >= %s and <= %s"%( prec, MPFR_PREC_MIN, MY_MPFR_PREC_MAX) @@ -459,36 +448,20 @@ cdef class RealField_class(sage.rings.ring.Field): self.rnd = n self.rnd_str = rnd from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, tuple([]), False, category = Fields()) - - # hack, we cannot call the constructor here - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 0.0, self.rnd) + ParentWithGens.__init__(self, self, tuple([]), False, category=Fields().Metric().Complete()) + + # Initialize zero and one + cdef RealNumber rn + rn = self._new() + mpfr_set_zero(rn.value, 1) self._zero_element = rn - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 1.0, self.rnd) + rn = self._new() + mpfr_set_ui(rn.value, 1, MPFR_RNDZ) self._one_element = rn self._populate_coercion_lists_(convert_method_name='_mpfr_') - cdef RealNumber _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self - mpfr_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -977,7 +950,6 @@ cdef class RealField_class(sage.rings.ring.Field): else: return RealField(prec, self.sci_not, _rounding_modes[self.rnd]) - # int mpfr_const_pi (mpfr_t rop, mp_rnd_t rnd) def pi(self): r""" Return `\pi` to the precision of this field. @@ -1007,8 +979,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - - # int mpfr_const_euler (mpfr_t rop, mp_rnd_t rnd) def euler_constant(self): """ Returns Euler's gamma constant to the precision of this field. @@ -1025,7 +995,6 @@ cdef class RealField_class(sage.rings.ring.Field): sig_off() return x - # int mpfr_const_catalan (mpfr_t rop, mp_rnd_t rnd) def catalan_constant(self): """ Returns Catalan's constant to the precision of this field. @@ -1042,7 +1011,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - # int mpfr_const_log2 (mpfr_t rop, mp_rnd_t rnd) def log2(self): r""" Return `\log(2)` (i.e., the natural log of 2) to the precision @@ -1260,8 +1228,6 @@ cdef class RealField_class(sage.rings.ring.Field): # # RealNumber -- element of Real Field # -# -# #***************************************************************************** cdef class RealLiteral(RealNumber) @@ -1278,18 +1244,29 @@ cdef class RealNumber(sage.structure.element.RingElement): internal precision, in order to avoid confusing roundoff issues that occur because numbers are stored internally in binary. """ - cdef RealNumber _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real number with same parent as self. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfr import RealNumber + sage: RealNumber.__new__(RealNumber, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, RR) + NaN """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self._parent - mpfr_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealField_class p = parent + mpfr_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ Create a real number. Should be called by first creating a RealField, as illustrated in the examples. @@ -1334,16 +1311,8 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: TestSuite(R).run() """ - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfr_init2(self.value, parent.__prec) - self.init = 1 - if x is None: - return - - self._set(x, base) + if x is not None: + self._set(x, base) def _magma_init_(self, magma): r""" @@ -1501,7 +1470,7 @@ cdef class RealNumber(sage.structure.element.RingElement): return (__create__RealNumber_version0, (self._parent, s, 32)) def __dealloc__(self): - if self.init: + if self._parent is not None: mpfr_clear(self.value) def __repr__(self): @@ -3772,10 +3741,11 @@ cdef class RealNumber(sage.structure.element.RingElement): """ return mpfr_sgn(self.value) != 0 - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Return the Cython rich comparison operator (see the Cython - documentation for details). + Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` + if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` + are equal, and ``1`` if ``left`` is greater than ``right``. EXAMPLES:: @@ -3805,27 +3775,6 @@ cdef class RealNumber(sage.structure.element.RingElement): False sage: RR('1')>=RR('1') True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` - if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` - are equal, and ``1`` if ``left`` is greater than ``right``. - - EXAMPLES:: - - sage: RR('1')<=RR('1') - True - sage: RR('1')RR('1') - False - sage: RR('1')>=RR('1') - True - sage: RR('nan')==R('nan') - False sage: RR('inf')==RR('inf') True sage: RR('inf')==RR('-inf') @@ -4382,15 +4331,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - ########################################################## - # it would be nice to get zero back here: - # sage: R(-1).acos().sin() - # _57 = -0.50165576126683320234e-19 - # i think this could be "fixed" by using MPFI. (put on to-do list.) - # - # this seems to work ok: - # sage: R(-1).acos().cos() - # _58 = -0.10000000000000000000e1 def sin(self): """ Return the sine of ``self``. @@ -4445,9 +4385,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x,y - - # int mpfr_sin_cos (mpfr_t rop, mpfr_t op, mpfr_t, mp_rnd_t rnd) - def arccos(self): """ Return the inverse cosine of ``self``. @@ -4501,10 +4438,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - #int mpfr_acos _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_asin _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_atan _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - def cosh(self): """ Return the hyperbolic cosine of ``self``. @@ -5659,41 +5592,6 @@ def __create__RealNumber_version0(parent, x, base=10): return RealNumber(parent, x, base=base) -cdef inline RealNumber empty_RealNumber(RealField_class parent): - """ - Create and return an empty initialized real number. - - EXAMPLES: - - These are indirect tests of this function:: - - sage: from sage.rings.real_mpfr import RRtoRR - sage: R10 = RealField(10) - sage: R100 = RealField(100) - sage: f = RRtoRR(R100, R10) - sage: a = R100(1.2) - sage: f(a) - 1.2 - sage: g = f.section() - sage: g - Generic map: - From: Real Field with 10 bits of precision - To: Real Field with 100 bits of precision - sage: g(f(a)) # indirect doctest - 1.1992187500000000000000000000 - sage: b = R10(2).sqrt() - sage: f(g(b)) - 1.4 - sage: f(g(b)) == b - True - """ - - cdef RealNumber y = RealNumber.__new__(RealNumber) - y._parent = parent - mpfr_init2(y.value, parent.__prec) - y.init = 1 - return y - cdef class RRtoRR(Map): cpdef Element _call_(self, x): """ @@ -5720,7 +5618,7 @@ cdef class RRtoRR(Map): True """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() if type(x) is RealLiteral: mpfr_set_str(y.value, (x).literal, (x).base, parent.rnd) else: @@ -5753,7 +5651,7 @@ cdef class ZZtoRR(Map): 1.2346e8 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_z(y.value, (x).value, parent.rnd) return y @@ -5768,7 +5666,7 @@ cdef class QQtoRR(Map): -0.33333333333333333333333333333333333333333333333333333333333 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_q(y.value, (x).value, parent.rnd) return y @@ -5788,7 +5686,7 @@ cdef class double_toRR(Map): 3.1415926535897931159979634685441851615905761718750000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_d(y.value, x, parent.rnd) return y @@ -5816,6 +5714,6 @@ cdef class int_toRR(Map): 1.00000000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_si(y.value, x, parent.rnd) return y diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index c6b1e93b346..b62886b03c9 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -68,7 +68,7 @@ AUTHORS: from sage.misc.cachefunc import cached_method -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model from sage.structure.parent_gens cimport ParentWithGens from sage.structure.parent cimport Parent from sage.structure.category_object import check_default_category @@ -134,11 +134,14 @@ cdef class Ring(ParentWithGens): Test agaings another bug fixed in :trac:`9944`:: sage: QQ['x'].category() - Join of Category of euclidean domains and Category of commutative algebras over quotient fields + Join of Category of euclidean domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: QQ['x','y'].category() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: PolynomialRing(MatrixSpace(QQ,2),'x').category() - Category of algebras over algebras over quotient fields + Category of algebras over (algebras over + (quotient fields and metric spaces) and infinite sets) sage: PolynomialRing(SteenrodAlgebra(2),'x').category() Category of algebras over graded hopf algebras with basis over Finite Field of size 2 @@ -1308,7 +1311,7 @@ cdef class CommutativeRing(Ring): try: return self.fraction_field() except (NotImplementedError,TypeError): - return get_coercion_model().division_parent(self) + return coercion_model.division_parent(self) def __pow__(self, n, _): """ @@ -2253,11 +2256,11 @@ cdef class Field(PrincipalIdealDomain): ALGORITHM: This uses the extended Euclidean algorithm; see for example - [Cohen]_, Algorithm 3.2.2. + [Cohen1996]_, Algorithm 3.2.2. REFERENCES: - .. [Cohen] H. Cohen, A Course in Computational Algebraic + .. [Cohen1996] H. Cohen, A Course in Computational Algebraic Number Theory. Graduate Texts in Mathematics 138. Springer-Verlag, 1996. diff --git a/src/sage/rings/ring_element.py b/src/sage/rings/ring_element.py index 0e042c86d36..87deec5b7cc 100644 --- a/src/sage/rings/ring_element.py +++ b/src/sage/rings/ring_element.py @@ -17,4 +17,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.superseded import deprecation +deprecation(19167, "the module sage.rings.ring_element is deprecated, import from sage.structure.element instead") + from sage.structure.element import RingElement, is_RingElement diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index cc4d1104ab8..fa06154e1e6 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -134,10 +134,11 @@ cdef class TropicalSemiringElement(RingElement): return hash(self._val) # Comparisons - - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparisons. + Return ``-1`` if ``left`` is less than ``right``, ``0`` if + ``left`` and ``right`` are equal, and ``1`` if ``left`` is + greater than ``right``. EXAMPLES:: @@ -170,30 +171,6 @@ cdef class TropicalSemiringElement(RingElement): False sage: T.infinity() >= T.infinity() True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if ``left`` is less than ``right``, ``0`` if - ``left`` and ``right`` are equal, and ``1`` if ``left`` is - greater than ``right``. - - EXAMPLES:: - - sage: T = TropicalSemiring(QQ) - sage: T(2) == T(2) - True - sage: T(2) != T(4) - True - sage: T(2) < T(4) - True - sage: T(2) > T(4) - False - sage: T.infinity() == T.infinity() - True - sage: T(4) <= T.infinity() - True Using the `\max` definition:: diff --git a/src/sage/rings/sum_of_squares.pxd b/src/sage/rings/sum_of_squares.pxd new file mode 100644 index 00000000000..47d43124f8b --- /dev/null +++ b/src/sage/rings/sum_of_squares.pxd @@ -0,0 +1,2 @@ +from libc.stdint cimport uint_fast32_t, uint32_t +cdef int two_squares_c(uint_fast32_t n, uint_fast32_t res[2]) diff --git a/src/sage/rings/sum_of_squares.pyx b/src/sage/rings/sum_of_squares.pyx index 8dcacc2f6fe..dc5f2b9cb7f 100644 --- a/src/sage/rings/sum_of_squares.pyx +++ b/src/sage/rings/sum_of_squares.pyx @@ -18,7 +18,7 @@ AUTHORS: #***************************************************************************** from libc.math cimport sqrt -from libc.stdint cimport uint_fast32_t, uint32_t + include "sage/ext/interrupt.pxi" diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 3ca05b6ffbd..221f83c8054 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -154,15 +154,16 @@ def rings0(): called creates a random ring of a certain representative type described by desc. - RINGS:: - - ZZ - - QQ - - ZZ/nZZ - - GF(p) - - GF(q) - - quadratic number fields - - absolute number fields - - relative number fields (disabled in the automatic tests for now) + RINGS: + + - ZZ + - QQ + - ZZ/nZZ + - GF(p) + - GF(q) + - quadratic number fields + - absolute number fields + - relative number fields (disabled in the automatic tests for now) EXAMPLES:: @@ -187,14 +188,16 @@ def rings0(): def rings1(): """ Return an iterator over random rings. + Return a list of pairs (f, desc), where f is a function that outputs a random ring that takes a ring and possibly some other data as constructor. - RINGS:: - - polynomial ring in one variable over a rings0() ring. - - polynomial ring over a rings1() ring. - - multivariate polynomials + RINGS: + + - polynomial ring in one variable over a rings0() ring. + - polynomial ring over a rings1() ring. + - multivariate polynomials EXAMPLES:: @@ -238,12 +241,13 @@ def test_random_elements(level=MAX_LEVEL, trials=1): while True: test_random_elements(trials=100, print_seed=True) INPUT: - level -- (default: MAX_LEVEL); controls the types of rings to use - trials -- A positive integer (default 1); the number of trials - to run. - seed -- the random seed to use; if not specified, uses a truly - random seed. - print_seed -- If True (default False), prints the random seed chosen. + + - level -- (default: MAX_LEVEL); controls the types of rings to use + - trials -- A positive integer (default 1); the number of trials + to run. + - seed -- the random seed to use; if not specified, uses a truly + random seed. + - print_seed -- If True (default False), prints the random seed chosen. EXAMPLES:: @@ -283,12 +287,13 @@ def test_random_arith(level=MAX_LEVEL, trials=1): while True: test_random_arith(trials=100, print_seed=True) INPUT: - level -- (default: MAX_LEVEL); controls the types of rings to use - trials -- A positive integer (default 1); the number of trials - to run. - seed -- the random seed to use; if not specified, uses a truly - random seed. - print_seed -- If True (default False), prints the random seed chosen. + + - level -- (default: MAX_LEVEL); controls the types of rings to use + - trials -- A positive integer (default 1); the number of trials + to run. + - seed -- the random seed to use; if not specified, uses a truly + random seed. + - print_seed -- If True (default False), prints the random seed chosen. EXAMPLES:: diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index 05918a62801..6d2d2653696 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -260,7 +260,7 @@ sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0)) sage: show(p+t,axes_labels=['log(N)','log(D(N))']) - Working with sandpile divisors:: +Working with sandpile divisors:: sage: S = sandpiles.Complete(4) sage: D = SandpileDivisor(S, [0,0,0,5]) @@ -309,7 +309,6 @@ (0, 1, 2, 3) sage: D.weierstrass_rank_seq(0) (2, 1, 0, 0, 0, -1) - """ #***************************************************************************** @@ -491,14 +490,14 @@ def __init__(self, g, sink=None): INPUT: - - ``g`` -- dict for directed multigraph with edges weighted by - nonnegative integers (see NOTE), a Graph or DiGraph. + - ``g`` -- dict for directed multigraph with edges weighted by + nonnegative integers (see NOTE), a Graph or DiGraph. - - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the - designated sink are ignored for the purposes of stabilization. It is - assumed that every vertex has a directed path into the sink. If the - ``sink`` argument is omitted, the first vertex in the list of the - Sandpile's vertices is set as the sink. + - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the + designated sink are ignored for the purposes of stabilization. It is + assumed that every vertex has a directed path into the sink. If the + ``sink`` argument is omitted, the first vertex in the list of the + Sandpile's vertices is set as the sink. OUTPUT: @@ -518,41 +517,37 @@ def __init__(self, g, sink=None): sage: G = Sandpile(g,'d') Here is a square with unweighted edges. In this example, the graph is - also undirected. - - :: + also undirected. :: sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]} sage: G = Sandpile(g,3) In the following example, multiple edges and loops in the dictionary - become edge weights in the Sandpile. - - :: - - sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) - sage: s.laplacian() - [ 3 -1 -1 -1] - [-1 4 -3 0] - [-1 -2 3 0] - [ 0 0 0 0] - sage: s.dict() - {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} - - Sandpiles can be created from Graphs and DiGraphs. - - sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) - sage: s = Sandpile(g) - sage: s.dict() - {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} - sage: s.sink() - 0 - sage: s = sandpiles.Cycle(4) - sage: s.laplacian() - [ 2 -1 0 -1] - [-1 2 -1 0] - [ 0 -1 2 -1] - [-1 0 -1 2] + become edge weights in the Sandpile. :: + + sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) + sage: s.laplacian() + [ 3 -1 -1 -1] + [-1 4 -3 0] + [-1 -2 3 0] + [ 0 0 0 0] + sage: s.dict() + {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} + + Sandpiles can be created from Graphs and DiGraphs. :: + + sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) + sage: s = Sandpile(g) + sage: s.dict() + {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} + sage: s.sink() + 0 + sage: s = sandpiles.Cycle(4) + sage: s.laplacian() + [ 2 -1 0 -1] + [-1 2 -1 0] + [ 0 -1 2 -1] + [-1 0 -1 2] .. NOTE:: @@ -4847,6 +4842,7 @@ def dualize(self): SandpileDivisor EXAMPLES:: + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.dualize() @@ -6615,6 +6611,7 @@ def random_DAG(num_verts, p=0.5, weight_max=1): Digraph on 5 vertices Check that bad inputs are rejected:: + sage: g = random_DAG(5,1.1) Traceback (most recent call last): ... diff --git a/src/sage/sat/all.py b/src/sage/sat/all.py new file mode 100644 index 00000000000..d4beeb2a9a1 --- /dev/null +++ b/src/sage/sat/all.py @@ -0,0 +1,2 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.sat.solvers.satsolver', 'SAT') diff --git a/src/sage/sat/converters/__init__.py b/src/sage/sat/converters/__init__.py index c5bf9dc9325..bc3c2a6efb0 100644 --- a/src/sage/sat/converters/__init__.py +++ b/src/sage/sat/converters/__init__.py @@ -1,2 +1,2 @@ from anf2cnf import ANF2CNFConverter -from polybori import CNFEncoder as PolyBoRiCNFEncoder +from brial.cnf import CNFEncoder as PolyBoRiCNFEncoder diff --git a/src/sage/sat/converters/polybori.py b/src/sage/sat/converters/polybori.py index bbc3b64ab8c..11b5d295b14 100644 --- a/src/sage/sat/converters/polybori.py +++ b/src/sage/sat/converters/polybori.py @@ -154,7 +154,7 @@ def var(self, m=None, decision=None): INPUT: - ``m`` - something the new variables maps to, usually a monomial - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: diff --git a/src/sage/sat/solvers/cryptominisat/__init__.py b/src/sage/sat/solvers/cryptominisat/__init__.py index 28e9c2346cd..cd1cf895996 100644 --- a/src/sage/sat/solvers/cryptominisat/__init__.py +++ b/src/sage/sat/solvers/cryptominisat/__init__.py @@ -1,6 +1,2 @@ -try: - from cryptominisat import CryptoMiniSat -except ImportError: - raise ImportError("Failed to import 'sage.sat.solvers.cryptominisat.CryptoMiniSat'. Run \"install_package('cryptominisat')\" to install it.") - +from cryptominisat import CryptoMiniSat from solverconf import SolverConf diff --git a/src/sage/sat/solvers/sat_lp.py b/src/sage/sat/solvers/sat_lp.py new file mode 100644 index 00000000000..1a74206f162 --- /dev/null +++ b/src/sage/sat/solvers/sat_lp.py @@ -0,0 +1,145 @@ +r""" +Solve SAT problems Integer Linear Programming + +The class defined here is a :class:`~sage.sat.solvers.satsolver.SatSolver` that +solves its instance using :class:`MixedIntegerLinearProgram`. Its performance +can be expected to be slower than when using +:class:`~sage.sat.solvers.cryptominisat.cryptominisat.CryptoMiniSat`. +""" +from satsolver import SatSolver +from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException + +class SatLP(SatSolver): + def __init__(self, solver=None): + r""" + Initializes the instance + + INPUT: + + - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) + solver to be used. If set to ``None``, the default one is used. For + more information on LP solvers and which default solver is used, see + the method + :meth:`solve ` + of the class + :class:`MixedIntegerLinearProgram `. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + SatSolver.__init__(self) + self._LP = MixedIntegerLinearProgram() + self._vars = self._LP.new_variable(binary=True) + + def var(self): + """ + Return a *new* variable. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + """ + nvars = n = self._LP.number_of_variables() + while nvars==self._LP.number_of_variables(): + n += 1 + self._vars[n] # creates the variable if needed + return n + + def nvars(self): + """ + Return the number of variables. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + sage: S.var() + 2 + sage: S.nvars() + 2 + """ + return self._LP.number_of_variables() + + def add_clause(self, lits): + """ + Add a new clause to set of clauses. + + INPUT: + + - ``lits`` - a tuple of integers != 0 + + .. note:: + + If any element ``e`` in ``lits`` has ``abs(e)`` greater + than the number of variables generated so far, then new + variables are created automatically. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: for u,v in graphs.CycleGraph(6).edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + """ + if 0 in lits: + raise ValueError("0 should not appear in the clause: {}".format(lits)) + p = self._LP + p.add_constraint(p.sum(self._vars[x] if x>0 else 1-self._vars[-x] for x in lits) + >=1) + + def __call__(self): + """ + Solve this instance. + + OUTPUT: + + - If this instance is SAT: A tuple of length ``nvars()+1`` + where the ``i``-th entry holds an assignment for the + ``i``-th variables (the ``0``-th entry is always ``None``). + + - If this instance is UNSAT: ``False`` + + EXAMPLE:: + + sage: def is_bipartite_SAT(G): + ....: S=SAT(solver="LP"); S + ....: for u,v in G.edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + ....: return S + sage: S = is_bipartite_SAT(graphs.CycleGraph(6)) + sage: S() # random + [None, True, False, True, False, True, False] + sage: True in S() + True + sage: S = is_bipartite_SAT(graphs.CycleGraph(7)) + sage: S() + False + """ + try: + self._LP.solve() + except MIPSolverException: + return False + + b = self._LP.get_values(self._vars) + n = max(b) + return [None]+[bool(b.get(i,0)) for i in range(1,n+1)] + + def __repr__(self): + """ + TESTS:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + return "an ILP-based SAT Solver" diff --git a/src/sage/sat/solvers/satsolver.pyx b/src/sage/sat/solvers/satsolver.pyx index 102ce003d78..734393d4800 100644 --- a/src/sage/sat/solvers/satsolver.pyx +++ b/src/sage/sat/solvers/satsolver.pyx @@ -14,6 +14,7 @@ AUTHORS: - Martin Albrecht (2012): first version """ +from sage.misc.package import PackageNotFoundError cdef class SatSolver: def __cinit__(self, *args, **kwds): @@ -33,7 +34,7 @@ cdef class SatSolver: INPUT: - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: @@ -86,6 +87,55 @@ cdef class SatSolver: """ raise NotImplementedError + def read(self, filename): + r""" + Reads DIMAC files. + + Reads in DIMAC formatted lines (lazily) from a + file or file object and adds the corresponding + clauses into this solver instance. Note that the + DIMACS format is not well specified, see + http://people.sc.fsu.edu/~jburkardt/data/cnf/cnf.html, + http://www.satcompetition.org/2009/format-benchmarks2009.html, + and http://elis.dvo.ru/~lab_11/glpk-doc/cnfsat.pdf. + The differences were summarized in the discussion on + the ticket :trac:`16924`. This method assumes the following + DIMACS format + + - Any line starting with "c" is a comment + - Any line starting with "p" is a header + - Any variable 1-n can be used + - Every line containing a clause must end with a "0" + + INPUT: + + - ``filename`` - The name of a file as a string or a file object + + EXAMPLE:: + + sage: from six import StringIO # for python 2/3 support + sage: file_object = StringIO("c A sample .cnf file.\np cnf 3 2\n1 -3 0\n2 3 -1 0 ") + sage: from sage.sat.solvers.dimacs import DIMACS + sage: solver = DIMACS() + sage: solver.read(file_object) + sage: solver.clauses() + [((1, -3), False, None), ((2, 3, -1), False, None)] + """ + if isinstance(filename,str): + file_object = open(filename,"r") + else: + file_object = filename + for line in file_object: + if line.startswith("c"): + continue # comment + if line.startswith("p"): + continue # header + line = line.split(" ") + clause = map(int,[e for e in line if e]) + clause = clause[:-1] # strip trailing zero + self.add_clause(clause) + + def __call__(self, assumptions=None): """ Solve this instance. @@ -226,3 +276,60 @@ cdef class SatSolver: """ return ["gens"] +def SAT(solver=None): + r""" + Return a :class:`SatSolver` instance. + + Through this class, one can define and solve `SAT + `__ problems. + + INPUT: + + - ``solver`` (string) -- select a solver. Admissible values are: + + - ``"cryptominisat"`` -- note that the cryptominisat package must be + installed. + + - ``"LP"`` -- use :class:`~sage.sat.solvers.sat_lp.SatLP` to solve the + SAT instance. + + - ``None`` (default) -- use CryptoMiniSat if available, and a LP solver + otherwise. + + EXAMPLE:: + + sage: SAT(solver="LP") + an ILP-based SAT Solver + + TESTS:: + + sage: SAT(solver="Wouhouuuuuu") + Traceback (most recent call last): + ... + ValueError: Solver 'Wouhouuuuuu' is not available + + Forcing CryptoMiniSat:: + + sage: SAT(solver="cryptominisat") # optional - cryptominisat + CryptoMiniSat + #vars: 0, #lits: 0, #clauses: 0, #learnt: 0, #assigns: 0 + + """ + if solver is None: + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + solver = "cryptominisat" + except ImportError: + solver = "LP" + + if solver == 'cryptominisat': + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + except ImportError: + raise PackageNotFoundError("cryptominisat") + return CryptoMiniSat() + elif solver == "LP": + from sat_lp import SatLP + return SatLP() + else: + raise ValueError("Solver '{}' is not available".format(solver)) diff --git a/src/sage/schemes/affine/affine_point.py b/src/sage/schemes/affine/affine_point.py index 2955de86861..9b24f911222 100644 --- a/src/sage/schemes/affine/affine_point.py +++ b/src/sage/schemes/affine/affine_point.py @@ -252,16 +252,16 @@ def homogenize(self,n): OUTPUT: - A point in the projectivization of the codomain of ``self`` - + EXAMPLES:: - + sage: A. = AffineSpace(ZZ,2) sage: Q = A(2,3) sage: Q.homogenize(2).dehomogenize(2) == Q True - + :: - + sage: A. = AffineSpace(QQ,2) sage: Q = A(2,3) sage: P = A(0,1) @@ -432,4 +432,3 @@ def orbit_structure(self,f): index+=1 I=Orbit.index(P) return([I,index-I-1]) - diff --git a/src/sage/schemes/affine/affine_space.py b/src/sage/schemes/affine/affine_space.py index a10c451dfb4..93ac8d42fd5 100644 --- a/src/sage/schemes/affine/affine_space.py +++ b/src/sage/schemes/affine/affine_space.py @@ -759,6 +759,7 @@ def points_of_bounded_height(self,bound): - an iterator of points in self EXAMPLES:: + sage: A. = AffineSpace(QQ,2) sage: list(A.points_of_bounded_height(3)) [(0, 0), (1, 0), (-1, 0), (1/2, 0), (-1/2, 0), (2, 0), (-2, 0), (0, 1), diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 3d48f28f623..4f58129adb0 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -33,7 +33,6 @@ from sage.rings.number_field.number_field import is_NumberField from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial from sage.rings.ring import is_Ring -from sage.rings.ring_element import is_RingElement from sage.categories.fields import Fields _Fields = Fields() diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index b187046ed60..9867d3e3144 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -109,6 +109,8 @@ import gal_reps_number_field +from six import reraise as raise_ + class EllipticCurve_number_field(EllipticCurve_field): r""" Elliptic curve over a number field. @@ -893,7 +895,7 @@ def _reduce_model(self): (a1, a2, a3, a4, a6) = [ZK(a) for a in self.a_invariants()] except TypeError: import sys - raise TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2] + raise_(TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2]) # N.B. Must define s, r, t in the right order. if ZK.absolute_degree() == 1: @@ -2641,12 +2643,23 @@ class :class:`EllipticCurveIsogeny` allowed composition. In sage: isogs = C.isogenies() sage: [((i,j),isogs[i][j].degree()) for i in range(4) for j in range(4) if isogs[i][j]!=0] - [((0, 1), 3), ((2, 1), 2), ((3, 0), 2), ((3, 2), 3)] + [((0, 1), 3), + ((0, 3), 2), + ((1, 0), 3), + ((1, 2), 2), + ((2, 1), 2), + ((2, 3), 3), + ((3, 0), 2), + ((3, 2), 3)] sage: [((i,j),isogs[i][j].x_rational_map()) for i in range(4) for j in range(4) if isogs[i][j]!=0] [((0, 1), (1/9*x^3 - 12)/x^2), - ((2, 1), (-1/2*i*x^2 + x)/(x + 3/2*i)), - ((3, 0), (-1/2*i*x^2 - x - 4*i)/(x - 5/2*i)), - ((3, 2), (1/9*x^3 - 4/3*i*x^2 - 34/3*x + 226/9*i)/(x^2 - 8*i*x - 16))] + ((0, 3), (-1/2*i*x^2 + i*x - 12*i)/(x - 3)), + ((1, 0), (x^3 + 4)/x^2), + ((1, 2), (-1/2*i*x^2 - i*x - 2*i)/(x + 1)), + ((2, 1), (1/2*i*x^2 - x)/(x + 3/2*i)), + ((2, 3), (x^3 + 4*i*x^2 - 10*x - 10*i)/(x^2 + 4*i*x - 4)), + ((3, 0), (1/2*i*x^2 + x + 4*i)/(x - 5/2*i)), + ((3, 2), (1/9*x^3 - 4/3*i*x^2 - 34/3*x + 226/9*i)/(x^2 - 8*i*x - 16))] The isogeny class may be visualized by obtaining its graph and plotting it:: @@ -2697,7 +2710,7 @@ class :class:`EllipticCurveIsogeny` allowed composition. In [(0, 0, 0, -25762110*c^2 - 67447215, -154360009760*c^2 - 404119737340), (0, 0, 0, 130763490*c^2 + 342343485, 1391590873420*c^3 + 3643232206680*c)] sage: C.isogenies()[0][1] - Isogeny of degree 2 from Elliptic Curve defined by y^2 = x^3 + (-25762110*c^2-67447215)*x + (-154360009760*c^2-404119737340) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 to Elliptic Curve defined by y^2 = x^3 + (130763490*c^2+342343485)*x + (-1391590873420*c^3-3643232206680*c) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 + Isogeny of degree 2 from Elliptic Curve defined by y^2 = x^3 + (-25762110*c^2-67447215)*x + (-154360009760*c^2-404119737340) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 to Elliptic Curve defined by y^2 = x^3 + (130763490*c^2+342343485)*x + (1391590873420*c^3+3643232206680*c) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 An example with CM by `\sqrt{-23}` (class number `3`):: diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 3d51ebe7645..6cdf83e5d33 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -776,7 +776,7 @@ def xy(self): sage: Q.xy() Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero """ if self[2] == 1: return self[0], self[1] diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index d82a74e5ea4..d605573bf3a 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -2555,8 +2555,10 @@ def CPS_height_bound(self): the naive logarithmic height of `P` and `\hat{h}(P)` is the canonical height. - SEE ALSO: silverman_height_bound for a bound that also works for - points over number fields. + .. SEEALSO:: + + :meth:`silverman_height_bound` for a bound that also works for + points over number fields. EXAMPLES:: @@ -5112,6 +5114,7 @@ def _shortest_paths(self): {0: 0, 1: 5, 2: 25}) """ from sage.graphs.graph import Graph + from sage.rings.real_mpfr import RR isocls = self.isogeny_class() M = isocls.matrix(fill=True).change_ring(rings.RR) # see trac #4889 for nebulous M.list() --> M.entries() change... @@ -5119,7 +5122,7 @@ def _shortest_paths(self): M = M.parent()([a.log() if a else 0 for a in M.list()]) G = Graph(M, format='weighted_adjacency_matrix') G.set_vertices(dict([(v,isocls[v]) for v in G.vertices()])) - v = G.shortest_path_lengths(0, by_weight=True, weight_sums=True) + v = G.shortest_path_lengths(0, by_weight=True) # Now exponentiate and round to get degrees of isogenies v = dict([(i, j.exp().round() if j else 0) for i,j in v.iteritems()]) return isocls.curves, v diff --git a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py index 59bc0720cf4..e27283a4d99 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py +++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py @@ -802,7 +802,7 @@ def _semistable_reducible_primes(E): # Next, we turn K into relative number field over F. - K = K.relativize(F.embeddings(K)[0], 'b') + K = K.relativize(F.embeddings(K)[0], K.variable_name()+'0') E = E.change_ring(K.structure()[1]) ## We try to find a nontrivial divisibility condition. ## diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py index a60242c7f1f..a15ae57d175 100644 --- a/src/sage/schemes/elliptic_curves/heegner.py +++ b/src/sage/schemes/elliptic_curves/heegner.py @@ -3526,6 +3526,7 @@ def _numerical_approx_xy_poly(self, prec=53): - 2-tuple of polynomials with floating point coefficients EXAMPLES:: + sage: E = EllipticCurve('37a') sage: y = E.heegner_point(-7,3); y Heegner point of discriminant -7 and conductor 3 on elliptic curve of conductor 37 diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py index 90fbff5629d..02da62312eb 100644 --- a/src/sage/schemes/elliptic_curves/height.py +++ b/src/sage/schemes/elliptic_curves/height.py @@ -1203,7 +1203,7 @@ def S(self, xi1, xi2, v): OUTPUT: The union of intervals `S^{(v)}(\xi_1,\xi_2)` defined in [TT]_ - sectoin 6.1. + section 6.1. EXAMPLES: diff --git a/src/sage/schemes/elliptic_curves/isogeny_class.py b/src/sage/schemes/elliptic_curves/isogeny_class.py index fdf9a44985b..c087a324353 100644 --- a/src/sage/schemes/elliptic_curves/isogeny_class.py +++ b/src/sage/schemes/elliptic_curves/isogeny_class.py @@ -620,12 +620,23 @@ class :class:`EllipticCurveIsogeny` allowed composition. In sage: isogs = C.isogenies() sage: [((i,j),isogs[i][j].degree()) for i in range(4) for j in range(4) if isogs[i][j]!=0] - [((0, 1), 3), ((2, 1), 2), ((3, 0), 2), ((3, 2), 3)] + [((0, 1), 3), + ((0, 3), 2), + ((1, 0), 3), + ((1, 2), 2), + ((2, 1), 2), + ((2, 3), 3), + ((3, 0), 2), + ((3, 2), 3)] sage: [((i,j),isogs[i][j].x_rational_map()) for i in range(4) for j in range(4) if isogs[i][j]!=0] [((0, 1), (1/9*x^3 - 12)/x^2), - ((2, 1), (-1/2*i*x^2 + x)/(x + 3/2*i)), - ((3, 0), (-1/2*i*x^2 - x - 4*i)/(x - 5/2*i)), - ((3, 2), (1/9*x^3 - 4/3*i*x^2 - 34/3*x + 226/9*i)/(x^2 - 8*i*x - 16))] + ((0, 3), (-1/2*i*x^2 + i*x - 12*i)/(x - 3)), + ((1, 0), (x^3 + 4)/x^2), + ((1, 2), (-1/2*i*x^2 - i*x - 2*i)/(x + 1)), + ((2, 1), (1/2*i*x^2 - x)/(x + 3/2*i)), + ((2, 3), (x^3 + 4*i*x^2 - 10*x - 10*i)/(x^2 + 4*i*x - 4)), + ((3, 0), (1/2*i*x^2 + x + 4*i)/(x - 5/2*i)), + ((3, 2), (1/9*x^3 - 4/3*i*x^2 - 34/3*x + 226/9*i)/(x^2 - 8*i*x - 16))] sage: K. = QuadraticField(-1) sage: E = EllipticCurve([1+i, -i, i, 1, 0]) @@ -670,7 +681,7 @@ class :class:`EllipticCurveIsogeny` allowed composition. In [(0, 0, 0, -25762110*c^2 - 67447215, -154360009760*c^2 - 404119737340), (0, 0, 0, 130763490*c^2 + 342343485, 1391590873420*c^3 + 3643232206680*c)] sage: C.isogenies()[0][1] - Isogeny of degree 2 from Elliptic Curve defined by y^2 = x^3 + (-25762110*c^2-67447215)*x + (-154360009760*c^2-404119737340) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 to Elliptic Curve defined by y^2 = x^3 + (130763490*c^2+342343485)*x + (-1391590873420*c^3-3643232206680*c) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 + Isogeny of degree 2 from Elliptic Curve defined by y^2 = x^3 + (-25762110*c^2-67447215)*x + (-154360009760*c^2-404119737340) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 to Elliptic Curve defined by y^2 = x^3 + (130763490*c^2+342343485)*x + (1391590873420*c^3+3643232206680*c) over Number Field in c with defining polynomial x^4 + 3*x^2 + 1 TESTS:: @@ -729,6 +740,19 @@ def _compute(self, verbose=False): [3 1 2 6] [6 2 1 3] [2 6 3 1] + + TESTS: + + Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong):: + + sage: K. = NumberField(x^2+1) + sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146]) + sage: C = E.isogeny_class() + sage: curves = C.curves + sage: isos = C.isogenies() + sage: isos[0][3].codomain() == curves[3] + True + """ from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix from sage.matrix.all import MatrixSpace @@ -750,7 +774,7 @@ def _compute(self, verbose=False): curves = [E] ncurves = 1 degs = [] - # tuples (i,j,l,phi) where curve i is l-isogenous to curve j + # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi tuples = [] def add_tup(t): @@ -791,8 +815,13 @@ def add_tup(t): E2 = phi.codomain() d = phi.degree() js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)] - if js: # seen codomain already - add_tup([i,js[0],d,phi]) + if js: # seen codomain already -- up to isomorphism + j = js[0] + if phi.codomain()!=curves[j]: + iso = E2.isomorphism_to(curves[j]) + phi.set_post_isomorphism(iso) + assert phi.domain()==curves[i] and phi.codomain()==curves[j] + add_tup([i,j,d,phi]) else: curves.append(E2) if verbose: @@ -819,8 +848,9 @@ def add_tup(t): mat = MatrixSpace(ZZ,ncurves)(0) self._maps = [[0]*ncurves for i in range(ncurves)] for i,j,l,phi in tuples: - mat[perm[i],perm[j]] = l - self._maps[perm[i]][perm[j]] = phi + if phi!=0: + mat[perm[i],perm[j]] = l + self._maps[perm[i]][perm[j]] = phi self._mat = fill_isogeny_matrix(mat) if verbose: print "Matrix = %s" % self._mat diff --git a/src/sage/schemes/elliptic_curves/modular_parametrization.py b/src/sage/schemes/elliptic_curves/modular_parametrization.py index a5ea06027d6..23f6cee7787 100644 --- a/src/sage/schemes/elliptic_curves/modular_parametrization.py +++ b/src/sage/schemes/elliptic_curves/modular_parametrization.py @@ -68,7 +68,7 @@ class ModularParameterization: def __init__(self, E): r""" EXAMPLES:: - s + sage: from sage.schemes.elliptic_curves.ell_rational_field import ModularParameterization sage: phi = ModularParameterization(EllipticCurve('389a')) sage: phi(CC.0/5) @@ -76,7 +76,6 @@ def __init__(self, E): sage: phi == loads(dumps(phi)) True - """ self._E = E diff --git a/src/sage/schemes/elliptic_curves/padic_lseries.py b/src/sage/schemes/elliptic_curves/padic_lseries.py index deabd080e3d..1edef217bd7 100644 --- a/src/sage/schemes/elliptic_curves/padic_lseries.py +++ b/src/sage/schemes/elliptic_curves/padic_lseries.py @@ -223,6 +223,7 @@ def __cmp__(self,other): Compare self and other. TESTS:: + sage: lp1 = EllipticCurve('11a1').padic_lseries(5) sage: lp2 = EllipticCurve('11a1').padic_lseries(7) sage: lp3 = EllipticCurve('11a2').padic_lseries(5) @@ -232,7 +233,6 @@ def __cmp__(self,other): False sage: lp1 == lp3 False - """ c = cmp(type(self), type(other)) if c: @@ -742,7 +742,7 @@ def _quotient_of_periods_to_twist(self,D): """ from sage.functions.all import sqrt - # This funciton does not depend on p and could be moved out of this file but it is needed only here + # This function does not depend on p and could be moved out of this file but it is needed only here # Note that the number of real components does not change by twisting. if D == 1: diff --git a/src/sage/schemes/elliptic_curves/padics.py b/src/sage/schemes/elliptic_curves/padics.py index 97886593a7b..0fe08ab0beb 100644 --- a/src/sage/schemes/elliptic_curves/padics.py +++ b/src/sage/schemes/elliptic_curves/padics.py @@ -46,6 +46,7 @@ def __check_padic_hypotheses(self, p): is an odd prime of good ordinary reduction. EXAMPLES:: + sage: E = EllipticCurve('11a1') sage: from sage.schemes.elliptic_curves.padics import __check_padic_hypotheses sage: __check_padic_hypotheses(E,5) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py index 98a295d1797..02acc245a61 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py @@ -55,13 +55,21 @@ class HyperellipticCurve_finite_field(hyperelliptic_generic.HyperellipticCurve_generic): def _frobenius_coefficient_bound_charpoly(self): - """ - Computes bound on number of ``p``-adic digits needed to recover + r""" + Computes bound on number of `p`-adic digits needed to recover frobenius polynomial computing the characteristic polynomial - of the frobenius matrix, i.e. returns ``B`` so that knowledge of - ``a_1``, ..., ``a_g`` modulo ``p^B`` determine frobenius polynomial + of the frobenius matrix, i.e. returns `B` so that knowledge of + `a_1`, ..., `a_g` modulo `p^B` determine frobenius polynomial uniquely. + The bound used here stems from the expression of the coefficients + of the characteristic polynomial of the Frobenius as sums + of products of its eigenvalues: + + .. MATH:: + + \| a_i \| \leq \binom{2g}{i} \sqrt{q}^i + EXAMPLES:: sage: R. = PolynomialRing(GF(37)) @@ -101,12 +109,19 @@ def _frobenius_coefficient_bound_charpoly(self): return B def _frobenius_coefficient_bound_traces(self, n=1): - """ - Computes bound on number of ``p``-adic digits needed to recover - the number of rational points on ``n`` extensions computing - traces of the frobenius matrix powers, i.e. returns ``B`` so that - knowledge of ``\tr(M^1)``, ..., ``\tr(M^n)`` modulo ``p^B`` determine - ``N_1``, ..., ``N_n`` uniquely. + r""" + Computes bound on number of `p`-adic digits needed to recover + the number of rational points on `n` extensions computing + traces of the frobenius matrix powers, i.e. returns `B` so that + knowledge of `\tr(M^1)`, ..., `\tr(M^n)` modulo `p^B` determine + `N_1`, ..., `N_n` uniquely. + + The formula stems from the expression of the trace of the Frobenius + as a sum of `i`-th powers of its eigenvalues. + + .. MATH:: + + \| \tr(M^i) \| \leq 2 g \sqrt{q}^i EXAMPLES:: @@ -114,9 +129,9 @@ def _frobenius_coefficient_bound_traces(self, n=1): sage: HyperellipticCurve(t^3 + t + 1)._frobenius_coefficient_bound_traces() 1 sage: HyperellipticCurve(t^5 + t + 1)._frobenius_coefficient_bound_traces() - 1 + 2 sage: HyperellipticCurve(t^7 + t + 1)._frobenius_coefficient_bound_traces() - 1 + 2 sage: R. = PolynomialRing(GF(next_prime(10^9))) sage: HyperellipticCurve(t^3 + t + 1)._frobenius_coefficient_bound_traces() @@ -131,21 +146,33 @@ def _frobenius_coefficient_bound_traces(self, n=1): 2 sage: HyperellipticCurve(t^13 + t + 1)._frobenius_coefficient_bound_traces(n=5) 3 + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurve(t^5 - t + 1) + sage: H._frobenius_coefficient_bound_traces() + 2 """ - g = self.genus() p = self.base_ring().characteristic() - return (ZZ(4*g).exact_log(p) + n//2).floor() + 1 + q = self.base_ring().order() + sqrtq = RR(q).sqrt() + g = self.genus() + + M = 4 * g * sqrtq**n + B = ZZ(M.ceil()).exact_log(p) + if p**B < M: + B += 1 + return B def frobenius_matrix_hypellfrob(self, N=None): - """ - Compute ``p``-adic frobenius matrix to precision ``p^N``. - If ``N`` not supplied, a default value is selected, which is the + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the minimum needed to recover the charpoly unambiguously. .. note:: - Currently only implemented using hypellfrob, which means only works - over the prime field ``GF(p)``, and must have ``p > (2g+1)(2N-1)``. + Implemented using ``hypellfrob``, which means it only works + over the prime field `GF(p)`, and requires `p > (2g+1)(2N-1)`. EXAMPLES:: @@ -157,7 +184,7 @@ def frobenius_matrix_hypellfrob(self, N=None): [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] - The `hypellfrob` program doesn't support non-prime fields:: + The ``hypellfrob`` program doesn't support non-prime fields:: sage: K. = GF(37**3) sage: R. = PolynomialRing(K) @@ -211,15 +238,16 @@ def frobenius_matrix_hypellfrob(self, N=None): return matrix_of_frobenius def frobenius_matrix(self, N=None, algorithm='hypellfrob'): - """ - Compute ``p``-adic frobenius matrix to precision ``p^N``. - If ``N`` not supplied, a default value is selected, which is the + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the minimum needed to recover the charpoly unambiguously. .. note:: - Currently only implemented using hypellfrob, which means only works - over the prime field ``GF(p)``, and must have ``p > (2g+1)(2N-1)``. + Currently only implemented using ``hypellfrob``, + which means it only works over the prime field `GF(p)`, + and requires `p > (2g+1)(2N-1)`. EXAMPLES:: @@ -231,7 +259,7 @@ def frobenius_matrix(self, N=None, algorithm='hypellfrob'): [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] - The `hypellfrob` program doesn't support non-prime fields:: + The ``hypellfrob`` program doesn't support non-prime fields:: sage: K. = GF(37**3) sage: R. = PolynomialRing(K) @@ -262,10 +290,10 @@ def frobenius_matrix(self, N=None, algorithm='hypellfrob'): return self.frobenius_matrix_hypellfrob(N=N) def frobenius_polynomial_cardinalities(self, a=None): - """ - Compute the charpoly of frobenius, as an element of ``\ZZ[x]``, - by computing the number of points on the curve over ``g`` extensions - of the base field where ``g`` is the genus of the curve. + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by computing the number of points on the curve over `g` extensions + of the base field where `g` is the genus of the curve. .. WARNING:: @@ -328,12 +356,12 @@ def frobenius_polynomial_cardinalities(self, a=None): return ZZ['x'](coeffs).reverse() def frobenius_polynomial_matrix(self, M=None, algorithm='hypellfrob'): - """ - Compute the charpoly of frobenius, as an element of ``\ZZ[x]``, + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, by computing the charpoly of the frobenius matrix. This is currently only supported when the base field is prime - and large enough using the `hypellfrob` library. + and large enough using the ``hypellfrob`` library. EXAMPLES:: @@ -359,7 +387,7 @@ def frobenius_polynomial_matrix(self, M=None, algorithm='hypellfrob'): sage: H.frobenius_polynomial_matrix() # long time, 8s on a Corei7 x^14 - 76*x^13 + 220846*x^12 - 12984372*x^11 + 24374326657*x^10 - 1203243210304*x^9 + 1770558798515792*x^8 - 74401511415210496*x^7 + 88526169366991084208*x^6 - 3007987702642212810304*x^5 + 3046608028331197124223343*x^4 - 81145833008762983138584372*x^3 + 69007473838551978905211279154*x^2 - 1187357507124810002849977200076*x + 781140631562281254374947500349999 - This `hypellfrob` program doesn't support non-prime fields:: + This ``hypellfrob`` program doesn't support non-prime fields:: sage: K. = GF(37**3) sage: R. = PolynomialRing(K) @@ -391,8 +419,8 @@ def frobenius_polynomial_matrix(self, M=None, algorithm='hypellfrob'): @cached_method def frobenius_polynomial(self): - """ - Compute the charpoly of frobenius, as an element of ZZ[x]. + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`. EXAMPLES:: @@ -416,7 +444,7 @@ def frobenius_polynomial(self): x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 Curves defined over a non-prime field are supported as well, - but a naive algorithm is used; especially when ``g = 1``, + but a naive algorithm is used; especially when `g = 1`, fast point counting on elliptic curves should be used:: sage: K. = GF(23**3) @@ -431,7 +459,7 @@ def frobenius_polynomial(self): sage: H.frobenius_polynomial() x^4 - 3*x^3 + 10*x^2 - 81*x + 729 - Over prime fields of odd characteristic, when ``h`` is non-zero, + Over prime fields of odd characteristic, when `h` is non-zero, this naive algorithm is currently used as well, whereas we should rather use another defining equation:: @@ -686,7 +714,8 @@ def points(self): sage: len(C.points()) 122 - Conics are allowed (the issue reported at #11800 has been resolved):: + Conics are allowed (the issue reported at :trac:`11800` + has been resolved):: sage: R. = GF(7)[] sage: H = HyperellipticCurve(3*x^2 + 5*x + 1) @@ -735,11 +764,11 @@ def points(self): def count_points_matrix_traces(self, n=1, M=None, N=None): r""" - Count the number of points on the curve over the first ``n`` extensions + Count the number of points on the curve over the first `n` extensions of the base field by computing traces of powers of the frobenius matrix. - This requires less ``p``-adic precision than computing the charpoly - of the matrix when ``n < g`` where ``g`` is the genus of the curve. + This requires less `p`-adic precision than computing the charpoly + of the matrix when `n < g` where `g` is the genus of the curve. EXAMPLES:: @@ -748,6 +777,17 @@ def count_points_matrix_traces(self, n=1, M=None, N=None): sage: H = HyperellipticCurve(t^19 + t + 1) sage: H.count_points_matrix_traces(3) [49491, 2500024375, 124992509154249] + + TESTS: + + Check that :trac:`18831` is fixed:: + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurve(t^5 - t + 1) + sage: H.count_points_matrix_traces() + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 15 """ if N is None: N = self._frobenius_coefficient_bound_traces(n=n) @@ -773,7 +813,7 @@ def count_points_matrix_traces(self, n=1, M=None, N=None): def count_points_frobenius_polynomial(self, n=1, f=None): r""" - Count the number of points on the curve over the first n extensions + Count the number of points on the curve over the first `n` extensions of the base field by computing the frobenius polynomial. EXAMPLES:: @@ -826,11 +866,11 @@ def count_points_frobenius_polynomial(self, n=1, f=None): def count_points_exhaustive(self, n=1, naive=False): r""" - Count the number of points on the curve over the first ``n`` extensions - of the base field by exhaustive search if ``n`` if smaller than ``g``, + Count the number of points on the curve over the first `n` extensions + of the base field by exhaustive search if `n` if smaller than `g`, the genus of the curve, and by computing the frobenius polynomial - after performing exhaustive search on the first ``g`` extensions if - ``n > g`` (unless `naive == True`). + after performing exhaustive search on the first `g` extensions if + `n > g` (unless ``naive == True``). EXAMPLES:: @@ -840,10 +880,10 @@ def count_points_exhaustive(self, n=1, naive=False): sage: H.count_points_exhaustive(n=5) [9, 27, 108, 675, 3069] - When ``n > g``, the frobenius polynomial is computed from the numbers - of points of the curve over the first ``g`` extension, so that computing - the number of points on extensions of degree ``n > g`` is not much more - expensive than for ``n == g``:: + When `n > g`, the frobenius polynomial is computed from the numbers + of points of the curve over the first `g` extension, so that computing + the number of points on extensions of degree `n > g` is not much more + expensive than for `n == g`:: sage: H.count_points_exhaustive(n=15) [9, @@ -862,7 +902,7 @@ def count_points_exhaustive(self, n=1, naive=False): 6103414827, 30517927308] - This behavior can be disabled by passing `naive=True`:: + This behavior can be disabled by passing ``naive=True``:: sage: H.count_points_exhaustive(n=6, naive=True) # long time, 7s on a Corei7 [9, 27, 108, 675, 3069, 16302] @@ -885,8 +925,8 @@ def count_points_exhaustive(self, n=1, naive=False): def count_points_hypellfrob(self, n=1, N=None, algorithm=None): r""" - Count the number of points on the curve over the first ``n``` extensions - of the base field using the `hypellfrob` program. + Count the number of points on the curve over the first `n` extensions + of the base field using the ``hypellfrob`` program. This only supports prime fields of large enough characteristic. @@ -1048,7 +1088,7 @@ def count_points(self, n=1): return self.count_points_exhaustive(n) def cardinality_exhaustive(self, extension_degree=1, algorithm=None): - """ + r""" Count points on a single extension of the base field by enumerating over x and solving the resulting quadratic equation for y. @@ -1152,7 +1192,7 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): r""" Count points on a single extension of the base field - using the hypellfrob prgoram. + using the ``hypellfrob`` prgoram. EXAMPLES:: @@ -1223,7 +1263,7 @@ def cardinality(self, extension_degree=1): def zeta_function(self): r""" - Gives the zeta function of the hyperelliptic curve. + Compute the zeta function of the hyperelliptic curve. EXAMPLES:: diff --git a/src/sage/schemes/plane_curves/projective_curve.py b/src/sage/schemes/plane_curves/projective_curve.py index f70ea35f73b..f29625978cc 100644 --- a/src/sage/schemes/plane_curves/projective_curve.py +++ b/src/sage/schemes/plane_curves/projective_curve.py @@ -600,7 +600,9 @@ def riemann_roch_basis(self, D): .. NOTE:: - Currently this only works over prime field and divisors supported on rational points. + + Currently this only works over prime field and divisors + supported on rational points. """ f = self.defining_polynomial()._singular_() singular = f.parent() diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 00a144e32a4..7a6f98cac15 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -1428,7 +1428,7 @@ def __init__(self, X, v, check=True): See :class:`SchemeMorphism_point_projective_ring` for details. This function still normalized points so that the rightmost non-zero coordinate is 1. The is to maintain current functionality with current - implementations of curves in projectives space (plane, connic, elliptic, etc). The class:`SchemeMorphism_point_projective_ring` is for general use. + implementations of curves in projectives space (plane, conic, elliptic, etc). The :class:`SchemeMorphism_point_projective_ring` is for general use. EXAMPLES:: diff --git a/src/sage/schemes/toric/divisor.py b/src/sage/schemes/toric/divisor.py index 1eed28a1390..92bfa8ba654 100644 --- a/src/sage/schemes/toric/divisor.py +++ b/src/sage/schemes/toric/divisor.py @@ -1168,16 +1168,16 @@ def is_ample(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] @@ -1246,16 +1246,16 @@ def is_nef(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] """ diff --git a/src/sage/schemes/toric/fano_variety.py b/src/sage/schemes/toric/fano_variety.py index 3f965f64750..ea2114c018c 100644 --- a/src/sage/schemes/toric/fano_variety.py +++ b/src/sage/schemes/toric/fano_variety.py @@ -308,7 +308,7 @@ def CPRFanoToricVariety(Delta=None, We start with the product of two projective lines:: sage: diamond = lattice_polytope.cross_polytope(2) - sage: diamond.vertices_pc() + sage: diamond.vertices() M( 1, 0), M( 0, 1), M(-1, 0), @@ -332,13 +332,13 @@ def CPRFanoToricVariety(Delta=None, square:: sage: square = diamond.polar() - sage: square.vertices_pc() + sage: square.vertices() N(-1, 1), N( 1, 1), N(-1, -1), N( 1, -1) in 2-d lattice N - sage: square.points_pc() + sage: square.points() N(-1, 1), N( 1, 1), N(-1, -1), @@ -608,8 +608,8 @@ def CPRFanoToricVariety(Delta=None, # single facet of Delta_polar, otherwise they do not form a # subdivision of the face fan of Delta_polar if check: - facet_sets = [frozenset(facet.points()) - for facet in Delta_polar.facets()] + facet_sets = [frozenset(facet.ambient_point_indices()) + for facet in Delta_polar.facets_lp()] for chart in charts: is_bad = True for fset in facet_sets: @@ -1237,7 +1237,7 @@ def cartesian_product(self, other, fan = self.fan().cartesian_product(other.fan()) Delta_polar = LatticePolytope(fan.rays()) - points = Delta_polar.points_pc() + points = Delta_polar.points() point_to_ray = dict() coordinate_points = [] for ray_index, ray in enumerate(fan.rays()): diff --git a/src/sage/schemes/toric/library.py b/src/sage/schemes/toric/library.py index 3b51ef2e6d8..10d38dbd736 100644 --- a/src/sage/schemes/toric/library.py +++ b/src/sage/schemes/toric/library.py @@ -260,7 +260,7 @@ def _make_CPRFanoToricVariety(self, name, coordinate_names, base_ring): dict_key = (name, base_ring) + tuple(coordinate_names) if dict_key not in self.__dict__: polytope = LatticePolytope(rays, lattice=ToricLattice(len(rays[0]))) - points = [tuple(_) for _ in polytope.points_pc()] + points = [tuple(_) for _ in polytope.points()] ray2point = [points.index(r) for r in rays] charts = [ [ray2point[i] for i in c] for c in cones ] self.__dict__[dict_key] = \ diff --git a/src/sage/schemes/toric/morphism.py b/src/sage/schemes/toric/morphism.py index df28bfc7df0..93bad1725bc 100644 --- a/src/sage/schemes/toric/morphism.py +++ b/src/sage/schemes/toric/morphism.py @@ -584,7 +584,7 @@ class SchemeMorphism_orbit_closure_toric_variety(SchemeMorphism, Morphism): cone of Rational polyhedral fan in 2-d lattice N. TESTS:: - + sage: V.embedding_morphism()._reverse_ray_map() {N(-1): 3, N(1): 2} sage: V.embedding_morphism()._defining_cone @@ -1583,7 +1583,7 @@ def fiber_graph(self, codomain_cone): irreducible components do not have to be of the same dimension. .. seealso:: - + :meth:`~SchemeMorphism_fan_toric_variety_dominant.fiber_component`. EXAMPLES:: diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index ca902bf97eb..aedb22ac2db 100644 --- a/src/sage/schemes/toric/points.py +++ b/src/sage/schemes/toric/points.py @@ -35,10 +35,9 @@ #***************************************************************************** - +import itertools from copy import copy -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import powerset, prod from sage.misc.cachefunc import cached_method from sage.rings.arith import gcd @@ -220,7 +219,7 @@ def _Chow_group_free(self): units = self.units() result = [] ker = self.rays().matrix().integer_kernel().matrix() - for phases in CartesianProduct(*([units] * ker.nrows())): + for phases in itertools.product(units, repeat=ker.nrows()): phases = tuple(prod(mu**exponent for mu, exponent in zip(phases, column)) for column in ker.columns()) result.append(phases) @@ -288,9 +287,10 @@ def rescalings(self): if len(tors) == 0: # optimization for smooth fans return free result = set(free) - for f, t in CartesianProduct(free, tors): - phases = tuple(x*y for x, y in zip(f, t)) - result.add(phases) + for f in free: + for t in tors: + phases = tuple(x*y for x, y in zip(f, t)) + result.add(phases) return tuple(sorted(result)) def orbit(self, point): @@ -390,7 +390,7 @@ def coordinate_iter(self): patch = copy(big_torus) for i in cone.ambient_ray_indices(): patch[i] = [zero] - for p in CartesianProduct(*patch): + for p in itertools.product(*patch): yield tuple(p) def __iter__(self): @@ -869,7 +869,7 @@ def solutions_serial(self, inhomogeneous_equations, log_range): OUTPUT: All solutions (as tuple of log inhomogeneous coordinates) in - the Cartesian product of the ranges. + the cartesian product of the ranges. EXAMPLES:: @@ -881,10 +881,10 @@ def solutions_serial(self, inhomogeneous_equations, log_range): sage: ffe.solutions_serial([s^2-1, s^6-s^2], [range(6)]) sage: list(_) - [[0], [3]] + [(0,), (3,)] """ - from sage.combinat.cartesian_product import CartesianProduct - for log_t in CartesianProduct(*log_range): + from itertools import product + for log_t in product(*log_range): t = self.ambient.exp(log_t) if all(poly(t) == 0 for poly in inhomogeneous_equations): yield log_t @@ -909,14 +909,14 @@ def solutions(self, inhomogeneous_equations, log_range): sage: ffe.solutions([s^2-1, s^6-s^2], [range(6)]) sage: sorted(_) - [[0], [3]] + [(0,), (3,)] """ # Do simple cases in one process (this includes most doctests) if len(log_range) <= 2: for log_t in self.solutions_serial(inhomogeneous_equations, log_range): yield log_t raise StopIteration - # Parallelize the outermost loop of the Cartesian product + # Parallelize the outermost loop of the cartesian product work = [([[r]] + log_range[1:],) for r in log_range[0]] from sage.parallel.decorate import Parallel parallel = Parallel() diff --git a/src/sage/schemes/toric/variety.py b/src/sage/schemes/toric/variety.py index 900bdaa218b..38b8f1e93a7 100644 --- a/src/sage/schemes/toric/variety.py +++ b/src/sage/schemes/toric/variety.py @@ -91,7 +91,7 @@ :func:`face fan ` of the "diamond":: sage: diamond = lattice_polytope.cross_polytope(2) - sage: diamond.vertices_pc() + sage: diamond.vertices() M( 1, 0), M( 0, 1), M(-1, 0), diff --git a/src/sage/schemes/toric/weierstrass_higher.py b/src/sage/schemes/toric/weierstrass_higher.py index f8da402e596..5cdce4d64a4 100644 --- a/src/sage/schemes/toric/weierstrass_higher.py +++ b/src/sage/schemes/toric/weierstrass_higher.py @@ -48,7 +48,7 @@ def WeierstrassForm2(polynomial, variables=None, transformation=False): See :func:`~sage.schemes.toric.weierstrass.WeierstrassForm` TESTS:: - + sage: from sage.schemes.toric.weierstrass_higher import WeierstrassForm2 sage: R. = QQ[] sage: quadratic1 = w^2+x^2+y^2 diff --git a/src/sage/server/trac/trac.py b/src/sage/server/trac/trac.py index b1296aa2620..0ff24a6bbb7 100644 --- a/src/sage/server/trac/trac.py +++ b/src/sage/server/trac/trac.py @@ -39,12 +39,7 @@ def trac_create_instance(directory = 'sage_trac', easy_setup = False): from sage.misc.sage_ostools import have_program if not have_program('trac-admin'): - print "You must install the optional trac package." - print "Try something like install_package('trac-0.11.5')," - print "but note that the package name may have a different" - print "version. Use optional_packages() to get a list" - print "of current package names." - return + raise RuntimeError("trac is not installed") if easy_setup: cmd = 'trac-admin "%s" initenv "Sage" "sqlite:db/trac.db" "" ""' % directory @@ -103,6 +98,9 @@ def trac(directory = 'sage_trac', port = 10000, address = 'localhost', - ``options`` - a string (default: ''); command-line options to pass directly to tracd """ + from sage.misc.superseded import deprecation + deprecation(16759, "This Sage Trac Server interface is deprecated, you can just run the Trac server outside of Sage") + if not os.path.exists(directory): trac_create_instance(directory, easy_setup = easy_setup) diff --git a/src/sage/sets/cartesian_product.py b/src/sage/sets/cartesian_product.py index a208f257250..0afc8910e2d 100644 --- a/src/sage/sets/cartesian_product.py +++ b/src/sage/sets/cartesian_product.py @@ -4,7 +4,6 @@ AUTHORS: - Nicolas Thiery (2010-03): initial version - """ #***************************************************************************** # Copyright (C) 2008 Nicolas Thiery , @@ -62,6 +61,8 @@ def __init__(self, sets, category, flatten=False): ``flatten`` is current ignored, and reserved for future use. + No other keyword arguments (``kwargs``) are accepted. + TESTS:: sage: from sage.sets.cartesian_product import CartesianProduct @@ -71,6 +72,10 @@ def __init__(self, sets, category, flatten=False): sage: C.an_element() (1/2, 1, 1) sage: TestSuite(C).run() + sage: cartesian_product([ZZ, ZZ], blub=None) + Traceback (most recent call last): + ... + TypeError: __init__() got an unexpected keyword argument 'blub' """ self._sets = tuple(sets) Parent.__init__(self, category=category) @@ -221,6 +226,45 @@ def _cartesian_product_of_elements(self, elements): assert len(elements) == len(self._sets) return self.element_class(self, elements) + def construction(self): + r""" + Return the construction functor and its arguments for this + cartesian product. + + OUTPUT: + + A pair whose first entry is a cartesian product functor and + its second entry is a list of the cartesian factors. + + EXAMPLES:: + + sage: cartesian_product([ZZ, QQ]).construction() + (The cartesian_product functorial construction, + (Integer Ring, Rational Field)) + """ + from sage.categories.cartesian_product import cartesian_product + return cartesian_product, self.cartesian_factors() + + def _coerce_map_from_(self, S): + r""" + Return ``True`` if ``S`` coerces into this cartesian product. + + TESTS:: + + sage: Z = cartesian_product([ZZ]) + sage: Q = cartesian_product([QQ]) + sage: Z.has_coerce_map_from(Q) # indirect doctest + False + sage: Q.has_coerce_map_from(Z) # indirect doctest + True + """ + if isinstance(S, CartesianProduct): + S_factors = S.cartesian_factors() + R_factors = self.cartesian_factors() + if len(S_factors) == len(R_factors): + if all(r.has_coerce_map_from(s) for r, s in zip(R_factors, S_factors)): + return True + an_element = Sets.CartesianProducts.ParentMethods.an_element class Element(ElementWrapper): @@ -276,3 +320,17 @@ def __iter__(self): 1 """ return iter(self.value) + + def cartesian_factors(self): + r""" + Return the tuple of elements that compose this element. + + EXAMPLES:: + + sage: A = cartesian_product([ZZ, RR]) + sage: A((1, 1.23)).cartesian_factors() + (1, 1.23000000000000) + sage: type(_) + + """ + return self.value diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index 90f3a4f437b..b76b81f2ddd 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -1009,7 +1009,7 @@ def cardinality(self): Check that :trac:`15195` is fixed:: - sage: C = CartesianProduct(PositiveIntegers(), [1,2,3]) + sage: C = cartesian_product([PositiveIntegers(), [1,2,3]]) sage: C.cardinality() +Infinity sage: F = Family(C, lambda x: x) diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index daec1e077fd..a79e5e14cdb 100644 --- a/src/sage/sets/finite_set_maps.py +++ b/src/sage/sets/finite_set_maps.py @@ -18,6 +18,8 @@ #***************************************************************************** +import itertools + from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.structure.unique_representation import UniqueRepresentation @@ -25,7 +27,6 @@ from sage.categories.monoids import Monoids from sage.categories.enumerated_sets import EnumeratedSets from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct from sage.sets.integer_range import IntegerRange from sage.sets.finite_set_map_cy import ( FiniteSetMap_MN, FiniteSetMap_Set, @@ -338,7 +339,7 @@ def __iter__(self): sage: FiniteSetMaps(1,1).list() [[0]] """ - for v in CartesianProduct(*([range(self._n)]*self._m)): + for v in itertools.product(range(self._n), repeat=self._m): yield self._from_list_(v) def _from_list_(self, v): diff --git a/src/sage/sets/primes.py b/src/sage/sets/primes.py index a89ea96a914..19a78f22cf9 100644 --- a/src/sage/sets/primes.py +++ b/src/sage/sets/primes.py @@ -26,6 +26,7 @@ from sage.rings.arith import nth_prime from sage.structure.unique_representation import UniqueRepresentation + class Primes(Set_generic, UniqueRepresentation): """ The set of prime numbers. @@ -125,7 +126,7 @@ def _repr_(self): def __contains__(self, x): """ - Checks whether an object is a prime number. + Check whether an object is a prime number. EXAMPLES:: @@ -148,7 +149,7 @@ def __contains__(self, x): def _an_element_(self): """ - Returns a typical prime number. + Return a typical prime number. EXAMPLES:: @@ -160,7 +161,7 @@ def _an_element_(self): def first(self): """ - Returns the first prime number. + Return the first prime number. EXAMPLES:: @@ -172,7 +173,7 @@ def first(self): def next(self, pr): """ - Returns the next prime number. + Return the next prime number. EXAMPLES:: @@ -185,9 +186,10 @@ def next(self, pr): def unrank(self, n): """ - Returns the n-th prime number. + Return the n-th prime number. EXAMPLES:: + sage: P = Primes() sage: P.unrank(0) 2 diff --git a/src/sage/sets/real_set.py b/src/sage/sets/real_set.py index feb37bd95ea..52960fe1a03 100644 --- a/src/sage/sets/real_set.py +++ b/src/sage/sets/real_set.py @@ -364,7 +364,7 @@ def closure(self): The closure as a new :class:`RealInterval` EXAMPLES:: - + sage: RealSet.open(0,1)[0].closure() [0, 1] sage: RealSet.open(-oo,1)[0].closure() @@ -385,7 +385,7 @@ def interior(self): The interior as a new :class:`RealInterval` EXAMPLES:: - + sage: RealSet.closed(0, 1)[0].interior() (0, 1) sage: RealSet.open_closed(-oo, 1)[0].interior() @@ -405,7 +405,7 @@ def is_connected(self, other): has a single connected component. EXAMPLES:: - + sage: I1 = RealSet.open(0, 1)[0]; I1 (0, 1) sage: I2 = RealSet.closed(1, 2)[0]; I2 @@ -577,7 +577,7 @@ def contains(self, x): Boolean. EXAMPLES:: - + sage: i = RealSet.open_closed(0,2)[0]; i (0, 2] sage: i.contains(0) @@ -784,7 +784,7 @@ def get_interval(self, i): The $i$-th connected component as a :class:`RealInterval`. EXAMPLES:: - + sage: s = RealSet(RealSet.open_closed(0,1), RealSet.closed_open(2,3)) sage: s.get_interval(0) (0, 1] @@ -1325,7 +1325,7 @@ def contains(self, x): Boolean. EXAMPLES:: - + sage: s = RealSet(0,2) + RealSet.unbounded_above_closed(10); s (0, 2) + [10, +oo) sage: s.contains(1) @@ -1357,7 +1357,7 @@ def is_included_in(self, *other): Boolean. EXAMPLES:: - + sage: I = RealSet((1,2)) sage: J = RealSet((1,3)) sage: K = RealSet((2,3)) @@ -1433,7 +1433,7 @@ def are_pairwise_disjoint(*real_set_collection): Boolean. EXAMPLES:: - + sage: s1 = RealSet((0, 1), (2, 3)) sage: s2 = RealSet((1, 2)) sage: s3 = RealSet.point(3) diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 83d8bdfd5e0..12ef7a8667e 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -36,14 +36,19 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.element import Element -from sage.structure.parent import Parent, Set_generic + from sage.misc.latex import latex -import sage.rings.infinity +from sage.misc.prandom import choice from sage.misc.misc import is_iterator + +from sage.structure.element import Element +from sage.structure.parent import Parent, Set_generic + from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets +import sage.rings.infinity + def Set(X=frozenset()): r""" Create the underlying set of ``X``. @@ -224,7 +229,12 @@ def __init__(self, X): if isinstance(X, (int,long)) or is_Integer(X): # The coercion model will try to call Set_object(0) raise ValueError('underlying object cannot be an integer') - Parent.__init__(self, category=Sets()) + + category = Sets() + if X in Sets().Finite() or isinstance(X, (tuple,list,set,frozenset)): + category = Sets().Finite() + + Parent.__init__(self, category=category) self.__object = X def __hash__(self): @@ -685,6 +695,21 @@ def __init__(self, X): """ Set_object.__init__(self, X) + def random_element(self): + r""" + Return a random element in this set. + + EXAMPLES:: + + sage: Set([1,2,3]).random_element() # random + 2 + """ + try: + return self.object().random_element() + except AttributeError: + # TODO: this very slow! + return choice(self.list()) + def is_finite(self): r""" Return ``True`` as this is a finite set. diff --git a/src/sage/sets/set_from_iterator.py b/src/sage/sets/set_from_iterator.py index 3ab4303cf7f..3546b11f6a3 100644 --- a/src/sage/sets/set_from_iterator.py +++ b/src/sage/sets/set_from_iterator.py @@ -180,6 +180,24 @@ def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=Fals *getattr(self, '_args', ()), **getattr(self, '_kwds', {})))) + def __hash__(self): + r""" + A simple hash using the first elements of the set. + + EXAMPLES:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: E = EnumeratedSetFromIterator(xsrange, (1,200)) + sage: hash(E) + 4600916458883504074 # 64-bit + -2063607862 # 32-bit + """ + try: + return hash(self._cache[:13]) + except AttributeError: + from itertools import islice + return hash(tuple(islice(self, 13))) + def __reduce__(self): r""" Support for pickle. diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index be78efcf49a..405ff0d9d4d 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -235,7 +235,8 @@ cdef class CategoryObject(sage_object.SageObject): sage: ZZ.categories() [Join of Category of euclidean domains - and Category of infinite enumerated sets, + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index 97743825804..338c12dd192 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -81,6 +81,8 @@ import operator cdef dict operator_dict = operator.__dict__ from operator import add, sub, mul, div, truediv, iadd, isub, imul, idiv +from cpython.weakref cimport PyWeakref_GET_OBJECT, PyWeakref_NewRef + from sage_object cimport SageObject from sage.categories.map cimport Map import sage.categories.morphism @@ -409,15 +411,35 @@ cdef class CoercionModel_cache_maps(CoercionModel): sage: cm = sage.structure.element.get_coercion_model() sage: maps, actions = cm.get_cache() - Now lets see what happens when we do a binary operations with + Now let us see what happens when we do a binary operations with an integer and a rational:: - sage: left_morphism, right_morphism = maps[ZZ, QQ] - sage: print copy(left_morphism) + sage: left_morphism_ref, right_morphism_ref = maps[ZZ, QQ] + + Note that by :trac:`14058` the coercion model only stores a weak + reference to the coercion maps in this case:: + + sage: left_morphism_ref + + + Moreover, the weakly referenced coercion map uses only a weak + reference to the codomain:: + + sage: left_morphism_ref() + (map internal to coercion system -- copy before use) + Natural morphism: + From: Integer Ring + To: Rational Field + + To get an actual valid map, we simply copy the weakly referenced + coercion map:: + + sage: print copy(left_morphism_ref()) Natural morphism: From: Integer Ring To: Rational Field - sage: print right_morphism + sage: print right_morphism_ref None We can see that it coerces the left operand from an integer to a @@ -490,7 +512,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): The function _test_exception_stack is executing the following code:: try: - raise TypeError, "just a test" + raise TypeError("just a test") except TypeError: cm._record_exception() """ @@ -518,7 +540,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): ['Traceback (most recent call last):\n File "sage/structure/coerce.pyx", line ...TypeError: just a test'] """ try: - raise TypeError, "just a test" + raise TypeError("just a test") except TypeError: self._record_exception() @@ -758,7 +780,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): all.append("Coercion on right operand via") all.append(y_mor) if res is not None and res is not y_mor.codomain(): - raise RuntimeError, ("BUG in coercion model: codomains not equal!", x_mor, y_mor) + raise RuntimeError("BUG in coercion model: codomains not equal!", x_mor, y_mor) res = y_mor.codomain() all.append("Arithmetic performed after coercions.") if op is div and isinstance(res, Parent): @@ -1045,7 +1067,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): # We should really include the underlying error. # This causes so much headache. - raise TypeError, arith_error_message(x,y,op) + raise TypeError(arith_error_message(x,y,op)) cpdef canonical_coercion(self, x, y): r""" @@ -1265,33 +1287,76 @@ cdef class CoercionModel_cache_maps(CoercionModel): True sage: parent(w+v) is W True + + TESTS: + + We check that with :trac:`14058`, parents are still eligible for + garbage collection after being involved in binary operations:: + + sage: import gc + sage: gc.collect() #random + 852 + sage: T=type(GF(2)) + sage: N0=len(list(o for o in gc.get_objects() if type(o) is T)) + sage: L=[ZZ(1)+GF(p)(1) for p in prime_range(2,50)] + sage: N1=len(list(o for o in gc.get_objects() if type(o) is T)) + sage: print N1 > N0 + True + sage: del L + sage: gc.collect() #random + 3939 + sage: N2=len(list(o for o in gc.get_objects() if type(o) is T)) + sage: print N2-N0 + 0 + """ try: - return self._coercion_maps.get(R, S, None) - except KeyError: - homs = self.discover_coercion(R, S) - if 0: - # This breaks too many things that are going to change - # in the new coercion model anyways. - # COERCE TODO: Enable it then. - homs = self.verify_coercion_maps(R, S, homs) + refs = self._coercion_maps.get(R, S, None) + if refs is None: + return None + R_map_ref, S_map_ref = refs + if R_map_ref is None: + S_map = PyWeakref_GET_OBJECT(S_map_ref) + if S_map is not None: + return None, S_map + elif S_map_ref is None: + R_map = PyWeakref_GET_OBJECT(R_map_ref) + if R_map is not None: + return R_map, None else: - if homs is not None: - x_map, y_map = homs - if x_map is not None and not isinstance(x_map, Map): - raise RuntimeError, "BUG in coercion model: coerce_map_from must return a Map" - if y_map is not None and not isinstance(y_map, Map): - raise RuntimeError, "BUG in coercion model: coerce_map_from must return a Map" - if homs is None: - swap = None + R_map = PyWeakref_GET_OBJECT(R_map_ref) + S_map = PyWeakref_GET_OBJECT(S_map_ref) + if R_map is not None and S_map is not None: + return R_map, S_map + except KeyError: + pass + homs = self.discover_coercion(R, S) + if 0: + # This breaks too many things that are going to change + # in the new coercion model anyways. + # COERCE TODO: Enable it then. + homs = self.verify_coercion_maps(R, S, homs) + else: + if homs is not None: + x_map, y_map = homs + if x_map is not None and not isinstance(x_map, Map): + raise RuntimeError("BUG in coercion model: coerce_map_from must return a Map") + if y_map is not None and not isinstance(y_map, Map): + raise RuntimeError("BUG in coercion model: coerce_map_from must return a Map") + if homs is None: + refs = None + swap = None + else: + R_map, S_map = homs + R_map_ref = None if R_map is None else PyWeakref_NewRef(R_map, None) + S_map_ref = None if S_map is None else PyWeakref_NewRef(S_map, None) + refs = R_map_ref, S_map_ref + if R_map is None and isinstance(S, Parent) and (S).has_coerce_map_from(R): + swap = None, PyWeakref_NewRef((S).coerce_map_from(R), None) else: - R_map, S_map = homs - if R_map is None and isinstance(S, Parent) and (S).has_coerce_map_from(R): - swap = None, (S)._internal_coerce_map_from(R) - else: - swap = S_map, R_map - self._coercion_maps.set(R, S, None, homs) - self._coercion_maps.set(S, R, None, swap) + swap = S_map_ref, R_map_ref + self._coercion_maps.set(R, S, None, refs) + self._coercion_maps.set(S, R, None, swap) return homs cpdef verify_coercion_maps(self, R, S, homs, bint fix=False): @@ -1334,14 +1399,14 @@ cdef class CoercionModel_cache_maps(CoercionModel): if connecting is not None: R_map = R_map * connecting if R_map.domain() is not R: - raise RuntimeError, ("BUG in coercion model, left domain must be original parent", R, R_map) + raise RuntimeError("BUG in coercion model, left domain must be original parent", R, R_map) if S_map is not None and S_map.domain() is not S: if fix: connecting = S_map.domain()._internal_coerce_map_from(S) if connecting is not None: S_map = S_map * connecting if S_map.domain() is not S: - raise RuntimeError, ("BUG in coercion model, right domain must be original parent", S, S_map) + raise RuntimeError("BUG in coercion model, right domain must be original parent", S, S_map) # Make sure the codomains are correct if R_map.codomain() is not S_map.codomain(): if fix: @@ -1353,7 +1418,7 @@ cdef class CoercionModel_cache_maps(CoercionModel): if connecting is not None: R_map = connecting * R_map if R_map.codomain() is not S_map.codomain(): - raise RuntimeError, ("BUG in coercion model, codomains must be identical", R_map, S_map) + raise RuntimeError("BUG in coercion model, codomains must be identical", R_map, S_map) if isinstance(R_map, IdentityMorphism): R_map = None elif isinstance(S_map, IdentityMorphism): @@ -1431,9 +1496,9 @@ cdef class CoercionModel_cache_maps(CoercionModel): coerce_R = Z._internal_coerce_map_from(R) coerce_S = Z._internal_coerce_map_from(S) if coerce_R is None: - raise TypeError, "No coercion from %s to pushout %s" % (R, Z) + raise TypeError("No coercion from %s to pushout %s" % (R, Z)) if coerce_S is None: - raise TypeError, "No coercion from %s to pushout %s" % (S, Z) + raise TypeError("No coercion from %s to pushout %s" % (S, Z)) return coerce_R, coerce_S except Exception: self._record_exception() @@ -1716,5 +1781,3 @@ Original elements %r (parent %s) and %r (parent %s) and maps %s %r"""%( x_elt, y_elt, parent_c(x_elt), parent_c(y_elt), x, parent_c(x), y, parent_c(y), type(x_map), x_map, type(y_map), y_map) - - diff --git a/src/sage/structure/coerce_actions.pyx b/src/sage/structure/coerce_actions.pyx index 30da0d9a117..18f19a13d95 100644 --- a/src/sage/structure/coerce_actions.pyx +++ b/src/sage/structure/coerce_actions.pyx @@ -18,14 +18,13 @@ import operator include "sage/ext/interrupt.pxi" from cpython.int cimport * from cpython.number cimport * -from sage.structure.element cimport parent_c +from sage.structure.element cimport parent_c, coercion_model from sage.categories.action import InverseAction, PrecomposedAction from coerce_exceptions import CoercionException cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef inline an_element(R): if isinstance(R, Parent): diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index c2317e745ae..66e6047cb52 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -148,5 +148,6 @@ cdef class CoercionModel: cpdef canonical_coercion(self, x, y) cpdef bin_op(self, x, y, op) +cdef CoercionModel coercion_model cdef generic_power_c(a, nn, one) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index ede01579cb8..a76e61926ec 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -128,6 +128,8 @@ underscores). # by any element. Derived class must call __init__ ################################################################## +from libc.limits cimport LONG_MAX, LONG_MIN + include "sage/ext/python.pxi" from sage.ext.stdsage cimport * @@ -524,9 +526,6 @@ cdef class Element(SageObject): pass return res - def __hash__(self): - return hash(str(self)) - def _im_gens_(self, codomain, im_gens): """ Return the image of ``self`` in codomain under the map that sends @@ -962,6 +961,19 @@ cdef class Element(SageObject): return 1 raise + def _cache_key(self): + """ + Provide a hashable key for an element if it is not hashable + + EXAMPLES:: + + sage: a=sage.structure.element.Element(ZZ) + sage: a._cache_key() + (Integer Ring, 'Generic element of a structure') + """ + + return(self.parent(),str(self)) + cdef _richcmp(self, other, int op): """ Compare ``self`` and ``other`` using the coercion framework, @@ -1043,10 +1055,10 @@ cdef class Element(SageObject): return rich_to_bool(op, -1) #################################################################### - # For a Cython class, you must define either _cmp_ (if your subclass - # is totally ordered), _richcmp_ (if your subclass is partially - # ordered), or both (if your class has both a total order and a - # partial order, or if implementing both gives better performance). + # In a Cython or a Python class, you must define either _cmp_ + # (if your subclass is totally ordered), _richcmp_ (if your subclass + # is partially ordered), or both (if your class has both a total order + # and a partial order, or if that gives better performance). # # Rich comparisons (like a < b) will default to using _richcmp_, # three-way comparisons (like cmp(a,b)) will default to using @@ -2138,7 +2150,7 @@ def is_CommutativeRingElement(x): TESTS:: - sage: from sage.rings.commutative_ring_element import is_CommutativeRingElement + sage: from sage.structure.element import is_CommutativeRingElement sage: is_CommutativeRingElement(oo) False @@ -3279,10 +3291,26 @@ cdef class InfinityElement(RingElement): return ZZ(0) cdef class PlusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(+infinity) + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + """ + return LONG_MAX cdef class MinusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(-infinity) + -9223372036854775808 # 64-bit + -2147483648 # 32-bit + """ + return LONG_MIN ################################################################################# diff --git a/src/sage/structure/formal_sum.py b/src/sage/structure/formal_sum.py index 5933c47bd5c..d8c0a73156e 100644 --- a/src/sage/structure/formal_sum.py +++ b/src/sage/structure/formal_sum.py @@ -351,6 +351,7 @@ def __classcall__(cls, base_ring = ZZ): def _repr_(self): """ EXAMPLES:: + sage: FormalSums(GF(7)) Abelian Group of all Formal Finite Sums over Finite Field of size 7 sage: FormalSums(GF(7))._repr_() diff --git a/src/sage/structure/indexed_generators.py b/src/sage/structure/indexed_generators.py index 2f445492466..5a079f3a7e9 100644 --- a/src/sage/structure/indexed_generators.py +++ b/src/sage/structure/indexed_generators.py @@ -74,6 +74,9 @@ class IndexedGenerators(object): - ``generator_cmp`` -- a comparison function (default: ``cmp``), to use for sorting elements in the output of elements + - ``string_quotes`` -- bool (default: ``True``), if ``True`` then + display string indices with quotes + .. NOTE:: These print options may also be accessed and modified using the @@ -130,7 +133,8 @@ def __init__(self, indices, prefix="x", **kwds): 'scalar_mult': "*", 'latex_scalar_mult': None, 'tensor_symbol': None, - 'generator_cmp': cmp} + 'generator_cmp': cmp, + 'string_quotes': True} # 'bracket': its default value here is None, meaning that # the value of self._repr_option_bracket is used; the default # value of that attribute is True -- see immediately before @@ -186,8 +190,9 @@ def print_options(self, **kwds): - ``latex_scalar_mult`` - ``tensor_symbol`` - ``generator_cmp`` + - ``string_quotes`` - See the documentation for :class:`CombinatorialFreeModule` for + See the documentation for :class:`IndexedGenerators` for descriptions of the effects of setting each of these options. OUTPUT: if the user provides any input, set the appropriate @@ -209,7 +214,8 @@ def print_options(self, **kwds): [('bracket', '('), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: F.print_options(bracket='[') # reset """ # don't just use kwds.get(...) because I want to distinguish @@ -220,7 +226,7 @@ def print_options(self, **kwds): # TODO: make this into a set and put it in a global variable? if option in ['prefix', 'latex_prefix', 'bracket', 'latex_bracket', 'scalar_mult', 'latex_scalar_mult', 'tensor_symbol', - 'generator_cmp' + 'generator_cmp', 'string_quotes' ]: self._print_options[option] = kwds[option] else: @@ -262,6 +268,9 @@ def _repr_generator(self, m): sage: e = F.basis() sage: e['a'] + 2*e['b'] # indirect doctest F['a'] + 2*F['b'] + sage: F.print_options(string_quotes=False) + sage: e['a'] + 2*e['b'] + F[a] + 2*F[b] sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="") sage: original_print_options = QS3.print_options() @@ -309,6 +318,9 @@ def _repr_generator(self, m): else: left = bracket right = bracket + quotes = self._print_options.get('string_quotes', True) + if not quotes and isinstance(m, str): + return self.prefix() + left + m + right return self.prefix() + left + repr(m) + right # mind the (m), to accept a tuple for m def _ascii_art_generator(self, m): diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 8271964688b..90ff244043c 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -95,7 +95,7 @@ This came up in some subtle bug once:: """ from types import MethodType -from element cimport parent_c +from .element cimport parent_c, coercion_model cimport sage.categories.morphism as morphism cimport sage.categories.map as map from sage.structure.debug_options import debug @@ -118,8 +118,7 @@ dummy_attribute_error = AttributeError(dummy_error_message) cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef object _Integer cdef bint is_Integer(x): @@ -805,6 +804,7 @@ cdef class Parent(category_object.CategoryObject): running ._test_eq() . . . pass running ._test_euclidean_degree() . . . pass running ._test_gcd_vs_xgcd() . . . pass + running ._test_metric() . . . pass running ._test_not_implemented_methods() . . . pass running ._test_one() . . . pass running ._test_pickling() . . . pass @@ -875,6 +875,7 @@ cdef class Parent(category_object.CategoryObject): _test_eq _test_euclidean_degree _test_gcd_vs_xgcd + _test_metric _test_not_implemented_methods _test_one _test_pickling @@ -1117,7 +1118,7 @@ cdef class Parent(category_object.CategoryObject): it is a ring, from the point of view of categories:: sage: MS.category() - Category of algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) sage: MS in Rings() True @@ -1808,7 +1809,6 @@ cdef class Parent(category_object.CategoryObject): if embedding is not None: self.register_embedding(embedding) - def _unset_coercions_used(self): r""" Pretend that this parent has never been interrogated by the coercion @@ -1820,8 +1820,7 @@ cdef class Parent(category_object.CategoryObject): For internal use only! """ self._coercions_used = False - import sage.structure.element - sage.structure.element.get_coercion_model().reset_cache() + coercion_model.reset_cache() def _unset_embedding(self): r""" @@ -2082,10 +2081,10 @@ cdef class Parent(category_object.CategoryObject): [ 0 1] [-272118 0] - sage: a.matrix() * b + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] - sage: a * b.matrix() + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] """ diff --git a/src/sage/structure/parent_old.pxd b/src/sage/structure/parent_old.pxd index f57e21a0665..4c254ea50dd 100644 --- a/src/sage/structure/parent_old.pxd +++ b/src/sage/structure/parent_old.pxd @@ -29,7 +29,6 @@ cdef class Parent(parent.Parent): cdef has_coerce_map_from_c_impl(self, S) cpdef _coerce_c(self, x) cdef _coerce_c_impl(self, x) - cdef _coerce_self_c(self, x) cdef _an_element_c_impl(self) cpdef _an_element_c(self) diff --git a/src/sage/structure/parent_old.pyx b/src/sage/structure/parent_old.pyx index 8f615a1235e..bbe2a11dedc 100644 --- a/src/sage/structure/parent_old.pyx +++ b/src/sage/structure/parent_old.pyx @@ -109,9 +109,6 @@ cdef class Parent(parent.Parent): # New Coercion support functionality ################################################################################# -# def coerce_map_from(self, S): -# return self.coerce_map_from_c(S) - cpdef coerce_map_from_c(self, S): """ EXAMPLES: @@ -151,10 +148,7 @@ cdef class Parent(parent.Parent): except KeyError: pass - if HAS_DICTIONARY(self): - mor = self.coerce_map_from_impl(S) - else: - mor = self.coerce_map_from_c_impl(S) + mor = self.coerce_map_from_c_impl(S) import sage.categories.morphism import sage.categories.map if mor is True: @@ -162,7 +156,7 @@ cdef class Parent(parent.Parent): elif mor is False: mor = None elif mor is not None and not isinstance(mor, sage.categories.map.Map): - raise TypeError, "coerce_map_from_impl must return a boolean, None, or an explicit Map" + raise TypeError("coerce_map_from_c_impl must return a boolean, None, or an explicit Map") if mor is None and isinstance(S, type): #Convert Python types to native Sage types @@ -179,10 +173,6 @@ cdef class Parent(parent.Parent): return mor - def coerce_map_from_impl(self, S): - check_old_coerce(self) - return self.coerce_map_from_c_impl(S) - cdef coerce_map_from_c_impl(self, S): check_old_coerce(self) import sage.categories.morphism @@ -213,9 +203,6 @@ cdef class Parent(parent.Parent): else: return None -# def get_action(self, S, op=operator.mul, self_on_left=True): -# return self.get_action_c(S, op, self_on_left) - cpdef get_action_c(self, S, op, bint self_on_left): check_old_coerce(self) try: @@ -307,30 +294,6 @@ cdef class Parent(parent.Parent): pass raise TypeError, "no canonical coercion of element into self" - def _coerce_self(self, x): - check_old_coerce(self) - return self._coerce_self_c(x) - - cdef _coerce_self_c(self, x): - """ - Try to canonically coerce x into self. - Return result on success or raise TypeError on failure. - """ - check_old_coerce(self) - # todo -- optimize? - try: - P = x.parent() - if P is self: - return x - elif P == self: - return self(x) - except AttributeError: - pass - raise TypeError, "no canonical coercion to self defined" - -# def has_coerce_map_from(self, S): -# return self.has_coerce_map_from_c(S) - cpdef has_coerce_map_from_c(self, S): """ Return True if there is a natural map from S to self. @@ -346,17 +309,10 @@ cdef class Parent(parent.Parent): return self._has_coerce_map_from.get(S) except KeyError: pass - if HAS_DICTIONARY(self): - x = self.has_coerce_map_from_impl(S) - else: - x = self.has_coerce_map_from_c_impl(S) + x = self.has_coerce_map_from_c_impl(S) self._has_coerce_map_from.set(S, x) return x - def has_coerce_map_from_impl(self, S): - check_old_coerce(self) - return self.has_coerce_map_from_c_impl(S) - cdef has_coerce_map_from_c_impl(self, S): check_old_coerce(self) if not isinstance(S, parent.Parent): @@ -365,8 +321,6 @@ cdef class Parent(parent.Parent): self._coerce_c((S).an_element()) except TypeError: return False - except NotImplementedError as msg: - raise NotImplementedError, "%s\nAlso, please make sure you have implemented has_coerce_map_from_impl or has_coerce_map_from_c_impl (or better _an_element_c_impl or _an_element_impl if possible) for %s"%(msg,self) return True def _an_element_impl(self): # override this in Python diff --git a/src/sage/structure/proof/proof.py b/src/sage/structure/proof/proof.py index 156b0da0815..46e3951de25 100644 --- a/src/sage/structure/proof/proof.py +++ b/src/sage/structure/proof/proof.py @@ -204,7 +204,7 @@ class WithProof: ... Traceback (most recent call last): ... - ZeroDivisionError: Rational division by zero + ZeroDivisionError: rational division by zero sage: proof.arithmetic() True """ diff --git a/src/sage/structure/sage_object.pxd b/src/sage/structure/sage_object.pxd index ac613d0dda4..48a5b086aee 100644 --- a/src/sage/structure/sage_object.pxd +++ b/src/sage/structure/sage_object.pxd @@ -1,11 +1,9 @@ from libc.stdint cimport uint32_t from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE - cdef class SageObject: pass - cdef inline bint rich_to_bool(int op, int c): """ Return the corresponding ``True`` or ``False`` value for a rich @@ -69,3 +67,6 @@ cdef inline bint rich_to_bool_sgn(int op, int c): This is in particular needed for ``mpz_cmp()``. """ return rich_to_bool(op, (c > 0) - (c < 0)) + + + diff --git a/src/sage/structure/sage_object.pyx b/src/sage/structure/sage_object.pyx index 86b21316341..0a146b5c83c 100644 --- a/src/sage/structure/sage_object.pyx +++ b/src/sage/structure/sage_object.pyx @@ -19,6 +19,43 @@ have_same_parent = LazyImport('sage.structure.element', 'have_same_parent', depr import zlib; comp = zlib import bz2; comp_other = bz2 +op_LT = Py_LT # operator < +op_LE = Py_LE # operator <= +op_EQ = Py_EQ # operator == +op_NE = Py_NE # operator != +op_GT = Py_GT # operator > +op_GE = Py_GE # operator >= + +def py_rich_to_bool(op, c): + r""" + Return ``True`` or ``False`` for a rich comparison, given the result of an + ordinary comparison. + + Do not use this function from Cython. Instead, call ``rich_to_bool`` or + ``rich_to_bool_sgn`` defined in ``sage_object.pxd``. + + INPUT: + + - ``op`` -- a rich comparison operation (e.g. ``op_EQ``) + + - ``c`` -- the result of an ordinary comparison, i.e. an integer. + + EXAMPLES:: + + sage: from sage.structure.sage_object import (py_rich_to_bool, + ....: op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE) + sage: for op in (op_LT, op_LE, op_EQ, op_NE, op_GT, op_GE): + ....: for c in (-1,0,1): + ....: print py_rich_to_bool(op, c), + ....: print + True False False + True True False + False True False + True False True + False False True + False True True + """ + return rich_to_bool_sgn(op, c) cdef process(s): if s[-5:] != '.sobj': @@ -296,7 +333,17 @@ cdef class SageObject: return UnicodeArt(lines) def __hash__(self): - return hash(self.__repr__()) + r""" + Not implemented: mutable objects inherit from this class + + EXAMPLES:: + + sage: hash(SageObject()) + Traceback (most recent call last): + ... + TypeError: is not hashable + """ + raise TypeError("{} is not hashable".format(type(self))) def _cache_key(self): r""" @@ -484,9 +531,8 @@ cdef class SageObject: Let us now write a broken :meth:`.category` method:: sage: class CCls(SageObject): - ... def category(self): - ... return 3 - ... + ....: def category(self): + ....: return 3 sage: CC = CCls() sage: CC._test_category() Traceback (most recent call last): @@ -566,17 +612,14 @@ cdef class SageObject: TESTS:: sage: class Abstract(SageObject): - ... @abstract_method - ... def bla(self): - ... "returns bla" - ... + ....: @abstract_method + ....: def bla(self): + ....: "returns bla" sage: class Concrete(Abstract): - ... def bla(self): - ... return 1 - ... + ....: def bla(self): + ....: return 1 sage: class IncompleteConcrete(Abstract): - ... pass - ... + ....: pass sage: Concrete()._test_not_implemented_methods() sage: IncompleteConcrete()._test_not_implemented_methods() Traceback (most recent call last): @@ -602,7 +645,9 @@ cdef class SageObject: sage: ZZ._test_pickling() - SEE ALSO: :func:`dumps` :func:`loads` + .. SEEALSO:: + + :func:`dumps`, :func:`loads` TESTS:: @@ -1103,6 +1148,24 @@ def register_unpickle_override(module, name, callable, call_name=None): you can specify the module name and class name, for the benefit of :func:`~sage.misc.explain_pickle.explain_pickle` when called with ``in_current_sage=True``).) + EXAMPLES: + + Imagine that there used to be an ``old_integer`` module and old + pickles essentially trying to do the following:: + + sage: unpickle_global('sage.rings.old_integer', 'OldInteger') + Traceback (most recent call last): + ... + ImportError: cannot import OldInteger from sage.rings.old_integer, call register_unpickle_override('sage.rings.old_integer', 'OldInteger', ...) to fix this + + After following the advice from the error message, unpickling + works:: + + sage: from sage.structure.sage_object import register_unpickle_override + sage: register_unpickle_override('sage.rings.old_integer', 'OldInteger', Integer) + sage: unpickle_global('sage.rings.old_integer', 'OldInteger') + + In many cases, unpickling problems for old pickles can be resolved with a simple call to ``register_unpickle_override``, as in the example above and in many of the ``sage`` source files. However, if the underlying data @@ -1158,14 +1221,13 @@ def register_unpickle_override(module, name, callable, call_name=None): defining a new :meth:`__setstate__` method:: sage: class SweeterPickle(CombinatorialObject,Element): - ... def __setstate__(self, state): - ... if isinstance(state, dict): # a pickle from CombinatorialObject is just its instance dictionary - ... self._set_parent(Tableaux()) # this is a fudge: we need an appropriate parent here - ... self.__dict__ = state - ... else: - ... self._set_parent(state[0]) - ... self.__dict__ = state[1] - ... + ....: def __setstate__(self, state): + ....: if isinstance(state, dict): # a pickle from CombinatorialObject is just its instance dictionary + ....: self._set_parent(Tableaux()) # this is a fudge: we need an appropriate parent here + ....: self.__dict__ = state + ....: else: + ....: self._set_parent(state[0]) + ....: self.__dict__ = state[1] sage: __main__.SweeterPickle = SweeterPickle sage: register_unpickle_override('__main__','SourPickle',SweeterPickle) sage: loads( gherkin ) @@ -1183,20 +1245,20 @@ def register_unpickle_override(module, name, callable, call_name=None): :: sage: class A(object): - ... def __init__(self,value): - ... self.original_attribute = value - ... def __repr__(self): - ... return 'A(%s)'%self.original_attribute + ....: def __init__(self,value): + ....: self.original_attribute = value + ....: def __repr__(self): + ....: return 'A(%s)'%self.original_attribute sage: class B(object): - ... def __init__(self,value): - ... self.new_attribute = value - ... def __setstate__(self,state): - ... try: - ... self.new_attribute = state['new_attribute'] - ... except KeyError: # an old pickle - ... self.new_attribute = state['original_attribute'] - ... def __repr__(self): - ... return 'B(%s)'%self.new_attribute + ....: def __init__(self,value): + ....: self.new_attribute = value + ....: def __setstate__(self,state): + ....: try: + ....: self.new_attribute = state['new_attribute'] + ....: except KeyError: # an old pickle + ....: self.new_attribute = state['original_attribute'] + ....: def __repr__(self): + ....: return 'B(%s)'%self.new_attribute sage: import __main__ sage: __main__.A=A; __main__.B=B # a hack to allow us to pickle command line classes sage: A(10) @@ -1287,7 +1349,12 @@ def unpickle_global(module, name): mod = sys_modules.get(module) if mod is not None: return getattr(mod, name) - __import__(module) + try: + __import__(module) + except ImportError: + raise ImportError("cannot import {1} from {0}, " + "call register_unpickle_override({0!r}, {1!r}, ...) to fix this".format( + module, name)) mod = sys_modules[module] return getattr(mod, name) @@ -1379,12 +1446,13 @@ def picklejar(obj, dir=None): sage: import os sage: os.chmod(dir, 0o000) sage: try: - ... uid = os.getuid() - ... except AttributeError: - ... uid = -1 + ....: uid = os.getuid() + ....: except AttributeError: + ....: uid = -1 sage: if uid==0: - ... raise OSError('You must not run the doctests as root, geez!') - ... else: sage.structure.sage_object.picklejar(1, dir + '/noaccess') + ....: raise OSError('You must not run the doctests as root, geez!') + ....: else: + ....: sage.structure.sage_object.picklejar(1, dir + '/noaccess') Traceback (most recent call last): ... OSError: ... @@ -1468,7 +1536,7 @@ def unpickle_all(dir = None, debug=False, run_test_suite=False): sage: from sage.structure.sage_object import register_unpickle_override, unpickle_all, unpickle_global sage: class A(CombinatorialObject,sage.structure.element.Element): - ... pass # to break a pickle + ....: pass # to break a pickle sage: tableau_unpickler=unpickle_global('sage.combinat.tableau','Tableau_class') sage: register_unpickle_override('sage.combinat.tableau','Tableau_class',A) # breaking the pickle sage: unpickle_all() # todo: not tested diff --git a/src/sage/structure/sequence.py b/src/sage/structure/sequence.py index e7ab4784ef9..3c88dc04734 100644 --- a/src/sage/structure/sequence.py +++ b/src/sage/structure/sequence.py @@ -541,6 +541,7 @@ def __setslice__(self, i, j, value): def append(self, x): """ EXAMPLES:: + sage: v = Sequence([1,2,3,4], immutable=True) sage: v.append(34) Traceback (most recent call last): diff --git a/src/sage/structure/set_factories.py b/src/sage/structure/set_factories.py new file mode 100644 index 00000000000..e37f1049304 --- /dev/null +++ b/src/sage/structure/set_factories.py @@ -0,0 +1,1180 @@ +r""" +Set factories +============= + + +A *set factory* `F` is a device for constructing some :class:`Parent` +`P` that models subsets of a big set `S`. Typically, each such parent +is constructed as the subset of `S` of all elements satisfying a +certain collection of constraints `cons`. In such a hierarchy of +subsets, one needs an easy and flexible control on how elements are +constructed. For example, one may want to construct the elements of +`P` in some subclass of the class of the elements of `S`. On other +occasions, one also often needs `P` to be a facade parent, whose +elements are represented as elements of `S` (see +:class:`~sage.categories.facade_sets.FacadeSets`). + +The role of a set factory is twofold: + +- *Manage a database* of constructors for the different parents `P = F(cons)` + depending on the various kinds of constraints `cons`. Note: currently there + is no real support for that. We are gathering use cases before fixing the + interface. + +- Ensure that the elements `e = P(...)` created by the different parents + follows a consistent policy concerning their *class and parent*. + +.. RUBRIC:: Basic usage: constructing parents through a factory + +The file :mod:`sage.structure.set_factories_example` shows an example of a +:class:`SetFactory` together with typical implementation. Note that the +written code is intentionally kept minimal, many things and in particular +several iterators could be written in a more efficient way. + +Consider the set `S` of couples `(x,y)` with `x` and `y` in `I:=\{0,1,2,3,4\}`. +We represent an element of `S` as a 2-elements tuple, wrapped in a class +:class:`~.set_factories_example.XYPair` deriving from :class:`ElementWrapper`. +You can create a :class:`~.set_factories_example.XYPair` with any +:class:`Parent`:: + + sage: from sage.structure.set_factories import * + sage: from sage.structure.set_factories_example import * + sage: p = XYPair(Parent(), (0,1)); p + (0, 1) + +Now, given `(a, b)\in S` we want to consider the following subsets of +`S` + +.. MATH:: + + S_a := \{(x,y) \in S \mid x = a\}, + + S^b := \{(x,y) \in S \mid y = b\}, + + S_a^b := \{(x,y) \in S \mid x = a, y = b\}. + +The constraints considered here are admittedly trivial. In a realistic +example, there would be much more of them. And for some sets of constraints +no good enumeration algorithms would be known. + +In Sage, those sets are constructed by passing the constraints to the +factory. We first create the set with no constraints at all:: + + sage: XYPairs + Factory for XY pairs + sage: S = XYPairs(); S.list() + [(0, 0), (1, 0), ..., (4, 0), (0, 1), (1, 1), ..., (3, 4), (4, 4)] + sage: S.cardinality() + 25 + +Let us construct `S_2`, `S^3` and `S_2^3`:: + + sage: Sx2 = XYPairs(x=2); Sx2.list() + [(2, 0), (2, 1), (2, 2), (2, 3), (2, 4)] + sage: Sy3 = XYPairs(y=3); Sy3.list() + [(0, 3), (1, 3), (2, 3), (3, 3), (4, 3)] + sage: S23 = XYPairs(x=2, y=3); S23.list() + [(2, 3)] + +Set factories provide an alternative way to build subsets of an +already constructed set: each set constructed by a factory has a +method :meth:`~ParentWithSetFactory.subset` which accept new +constraints. Sets constructed by the factory or the +:meth:`~ParentWithSetFactory.subset` methods are identical:: + + sage: Sx2s = S.subset(x=2); Sx2 is Sx2s + True + sage: Sx2.subset(y=3) is S23 + True + +It is not possible to change an already given constraint:: + + sage: S23.subset(y=5) + Traceback (most recent call last): + ... + ValueError: Duplicate value for constraints 'y': was 3 now 5 + +.. RUBRIC:: Constructing custom elements: policies + +We now come to the point of factories: constructing custom elements. The +writer of :func:`~.set_factories_example.XYPairs` decided that, by default, +the parents ``Sx2``, ``Sy3`` and ``S23`` are facade for parent ``S``. This +means that each element constructed by those subsets behaves as if they where +directly constructed by ``S`` itself:: + + sage: Sx2.an_element().parent() + AllPairs + sage: el = Sx2.an_element() + sage: el.parent() is S + True + sage: type(el) is S.element_class + True + +This is not always desirable. The device which decides how to construct an +element is called a *policy* (see :class:`SetFactoryPolicy`). Each factory +should have a default policy. Here is the policy of +:func:`~.set_factories_example.XYPairs`:: + + sage: XYPairs._default_policy + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + +This means that with the current policy, the parent builds elements with class +``XYPair`` and parent ``AllPairs`` which is itself constructed by calling the +factory :func:`~.set_factories_example.XYPairs` with constraints ``()``. There +is a lot of flexibility to change that. We now illustrate how to make a few +different choices. + +1 - In a first use case, we want to add some methods to the constructed +elements. As illustration, we add here a new method ``sum`` which returns +`x+y`. We therefore create a new class for the elements which inherits from +:class:`~.set_factories_example.XYPair`:: + + sage: class NewXYPair(XYPair): + ....: def sum(self): + ....: return sum(self.value) + +Here is an instance of this class (with a dummy parent):: + + sage: el = NewXYPair(Parent(), (2,3)) + sage: el.sum() + 5 + +We now want to have subsets generating those new elements while still having a +single real parent (the one with no constraint) for each element. The +corresponding policy is called :class:`TopMostParentPolicy`. It takes three +parameters: + +- the factory; +- the parameters for void constraint; +- the class used for elements. + +Calling the factory with this policy returns a new set which builds its +elements with the new policy:: + + sage: new_policy = TopMostParentPolicy(XYPairs, (), NewXYPair) + sage: NewS = XYPairs(policy=new_policy) + sage: el = NewS.an_element(); el + (0, 0) + sage: el.sum() + 0 + sage: el.parent() is NewS + True + sage: isinstance(el, NewXYPair) + True + +Newly constructed subsets inherit the policy:: + + sage: NewS2 = NewS.subset(x=2) + sage: el2 = NewS2.an_element(); el2 + (2, 0) + sage: el2.sum() + 2 + sage: el2.parent() is NewS + True + +2 - In a second use case, we want the elements to remember which +parent created them. The corresponding policy is called +:class:`SelfParentPolicy`. It takes only two parameters: + +- the factory; +- the class used for elements. + +Here is an example:: + + sage: selfpolicy = SelfParentPolicy(XYPairs, NewXYPair) + sage: SelfS = XYPairs(policy=selfpolicy) + sage: el = SelfS.an_element(); + sage: el.parent() is SelfS + True + +Now all subsets are the parent of the elements that they create:: + + sage: SelfS2 = SelfS.subset(x=2) + sage: el2 = SelfS2.an_element() + sage: el2.parent() is NewS + False + sage: el2.parent() is SelfS2 + True + +3 - Finaly, a common use case is to construct simple python object which are +not Sage :class:`sage.structure.Element`. As an example, we show how to build +a parent ``TupleS`` which construct pairs as tuple. The corresponding policy +is called :class:`BareFunctionPolicy`. It takes two parameters: + +- the factory; +- the function called to construct the elements. + +Here is how to do it:: + + sage: cons = lambda t, check: tuple(t) # ignore the check parameter + sage: tuplepolicy = BareFunctionPolicy(XYPairs, cons) + sage: P = XYPairs(x=2, policy=tuplepolicy) + sage: P.list() + [(2, 0), (2, 1), (2, 2), (2, 3), (2, 4)] + sage: el = P.an_element() + sage: type(el) + + +Here are the currently implemented policies: + +- :class:`FacadeParentPolicy`: reuse an existing parent together with + its element_class + +- :class:`TopMostParentPolicy`: use a parent created by the factory + itself and provide a class ``Element`` for it. In this case, we need + to specify the set of constraints which build this parent taking the + ownership of all elements and the class which will be used for the + ``Element``. + +- :class:`SelfParentPolicy`: provide systematically ``Element`` and + element_class and ensure that the parent is ``self``. + +- :class:`BareFunctionPolicy`: instead of calling a class constructor element + are passed to a function provided to the policy. + +.. TODO:: + + Generalize :class:`TopMostParentPolicy` to be able to have several + topmost parents. + +.. RUBRIC:: Technicalities: how policies inform parents + +Parents built from factories should inherit from +:class:`ParentWithSetFactory`. This class provide a few methods +related to factories and policies. The ``__init__`` method of +:class:`ParentWithSetFactory` must be provided with the set of +constraints and the policy. A parent built from a factory must create +elements through a call to the method ``_element_constructor_``. The +current way in which policies inform parents how to builds their elements is +set by a few attributes. So the class must accept attribute +adding. The precise details of which attributes are set may be subject +to change in the future. + +.. RUBRIC:: How to write a set factory + +.. SEEALSO:: :mod:`.set_factories_example` for an example of a factory. + +Here are the specifications: + +A parent built from a factory should + +- *inherit* from :class:`ParentWithSetFactory`. It should accept a + ``policy`` argument and pass it verbatim to the ``__init__`` method + of :class:`ParentWithSetFactory` together with the set of + constraints; + +- *create its elements* through calls to the method + ``_element_constructor_``; + +- *define a method* :class:`ParentWithSetFactory.check_element` which + checks if a built element indeed belongs to it. The method should + accept an extra keyword parameter called ``check`` specifying which + level of check should be performed. It will only be called when + ``bool(check)`` evaluates to ``True``. + +The constructor of the elements of a parent from a factory should: + +- receive the parent as first mandatory argument; + +- accept an extra optional keyword parameter called ``check`` which is meant + to tell if the input must be checked or not. The precise meaning of + ``check`` is intentionally left vague. The only intent is that if + ``bool(check)`` evaluates to ``False``, no check is performed at all. + +A factory should + +- *define a method* ``__call__`` which is responsible for calling the + appropriate parent constructor given the constraints; + +- *define a method* overloading :meth:`SetFactory.add_constraints` which is + responsible of computing the union of two sets of constraints; + +- *optionally* define a method or an attribute ``_default_policy`` passed to + the :class:`ParentWithSetFactory` if no policy is given to the factory. + + +.. TODO:: + + There is currently no support for dealing with sets of + constraints. The set factory and the parents must cooperate to + consistently handle them. More support, together with a generic mechanism + to select the appropriate parent class from the constraints, will be added + as soon as we have gathered sufficiently enough use-cases. + +AUTHORS: + +- Florent Hivert (2011-2012): initial revision +""" +#***************************************************************************** +# Copyright (C) 2012 Florent Hivert +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.structure.sage_object import SageObject +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.sets_cat import Sets +from sage.misc.abstract_method import abstract_method + +#################################################### +# Factories # +#################################################### + + +class SetFactory(UniqueRepresentation, SageObject): + r""" + This class is currently just a stub that we will be using to add + more structures on factories. + + TESTS:: + + sage: from sage.structure.set_factories import SetFactory + sage: S = SetFactory() + sage: S.__call__("foo") + Traceback (most recent call last): + ... + NotImplementedError: + sage: S.add_constraints("foo") + Traceback (most recent call last): + ... + NotImplementedError: + """ + @abstract_method + def __call__(self, *constraints, **consdict): + r""" + Construct the parent associated with the constraints in + argument. This should return a :class:`Parent`. + + .. NOTE:: + + Currently there is no specification on how constraints are + passed as arguments. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs() + AllPairs + sage: XYPairs(3) + {(3, b) | b in range(5)} + + sage: XYPairs(x=3) + {(3, b) | b in range(5)} + + sage: XYPairs(y=2) + {(a, 2) | a in range(5)} + + TESTS:: + + sage: from sage.structure.set_factories import SetFactory + sage: F = SetFactory() + sage: F() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + @abstract_method + def add_constraints(self, cons, *args, **opts): + r""" + Add constraints to the set of constraints `cons`. + + Should return a set of constraints. + + .. NOTE:: + + Currently there is no specification on how constraints are + passed as arguments. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs.add_constraints((3,),((None, 2), {})) + (3, 2) + + sage: XYPairs.add_constraints((3,),((None, None), {'y': 2})) + (3, 2) + + TESTS:: + + sage: from sage.structure.set_factories import SetFactory + sage: F = SetFactory() + sage: F.add_constraints(()) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + # TODO : default policy ? + +#################################################### +# Policies # +#################################################### + + +class SetFactoryPolicy(UniqueRepresentation, SageObject): + r""" + Abstract base class for policies. + + A policy is a device which is passed to a parent inheriting from + :class:`ParentWithSetFactory` in order to set-up the element + construction framework. + + INPUT: + + - ``factory`` -- a :class:`SetFactory` + + .. WARNING:: + + This class is a base class for policies, one should not try + to create instances. + """ + def __init__(self, factory): + r""" + TEST:: + + sage: from sage.structure.set_factories import SetFactoryPolicy + sage: from sage.structure.set_factories_example import XYPairs + sage: S = SetFactoryPolicy(XYPairs); S + + """ + assert isinstance(factory, SetFactory) + self._factory = factory + + def factory(self): + r""" + Return the factory for ``self``. + + EXAMPLES:: + + sage: from sage.structure.set_factories import SetFactoryPolicy, SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: XYPairs._default_policy.factory() + Factory for XY pairs + sage: XYPairs._default_policy.factory() is XYPairs + True + + TESTS:: + + sage: policy = SetFactoryPolicy(XYPairs) + sage: policy.factory() + Factory for XY pairs + sage: SelfParentPolicy(XYPairs, XYPair).factory() + Factory for XY pairs + """ + return self._factory + + def self_element_constructor_attributes(self, Element): + r""" + Element Constructor Attributes for non facade parent. + + The list of attributes which must be set during the init of a + non facade parent with factory. + + INPUT: + + - ``Element`` -- the class used for the elements + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = XYPairs._default_policy + sage: pol.self_element_constructor_attributes(XYPair) + {'Element': , + '_parent_for': 'self'} + """ + return {'_parent_for': "self", 'Element': Element} + + def facade_element_constructor_attributes(self, parent): + r""" + Element Constructor Attributes for facade parent. + + The list of attributes which must be set during the init of a + facade parent with factory. + + INPUT: + + - ``parent`` -- the actual parent for the elements + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = XYPairs._default_policy + sage: pol.facade_element_constructor_attributes(XYPairs()) + {'_facade_for': AllPairs, + '_parent_for': AllPairs, + 'element_class': } + """ + return {'_parent_for': parent, + '_facade_for': parent, + 'element_class': parent.element_class} + + @abstract_method + def element_constructor_attributes(self, constraints): + r""" + Element constructor attributes. + + INPUT: + + - ``constraints`` -- a bunch of constraints + + Should return the attributes that are prerequisite for element + construction. This is coordinated with + :meth:`ParentWithSetFactory._element_constructor_`. Currently two standard + attributes are provided in + :meth:`facade_element_constructor_attributes` and + :meth:`self_element_constructor_attributes`. You should return the + one needed depending on the given constraints. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = XYPairs._default_policy + sage: pol.element_constructor_attributes(()) + {'Element': , + '_parent_for': 'self'} + sage: pol.element_constructor_attributes((1)) + {'_facade_for': AllPairs, + '_parent_for': AllPairs, + 'element_class': } + """ + + +class SelfParentPolicy(SetFactoryPolicy): + r""" + Policy where each parent is a standard parent. + + INPUT: + + - ``factory`` -- an instance of :class:`SetFactory` + - ``Element`` -- a subclass of :class:`~.element.Element` + + Given a factory ``F`` and a class ``E``, returns a policy for + parent ``P`` creating elements in class ``E`` and parent ``P`` + itself. + + EXAMPLES:: + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair, Pairs_Y + sage: pol = SelfParentPolicy(XYPairs, XYPair) + sage: S = Pairs_Y(3, pol) + sage: el = S.an_element() + sage: el.parent() is S + True + + sage: class Foo(XYPair): pass + sage: pol = SelfParentPolicy(XYPairs, Foo) + sage: S = Pairs_Y(3, pol) + sage: el = S.an_element() + sage: isinstance(el, Foo) + True + """ + def __init__(self, factory, Element): + r""" + TEST:: + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: S = SelfParentPolicy(XYPairs, XYPair); S + Set factory policy for with parent ``self`` + sage: TestSuite(S).run(skip='_test_category') + """ + self._Element = Element + SetFactoryPolicy.__init__(self, factory) + + def element_constructor_attributes(self, constraints): + r""" + Return the element constructor attributes as per + :meth:`SetFactoryPolicy.element_constructor_attributes` + + INPUT: + + - ``constraints`` -- a bunch of constraints + + TESTS:: + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = SelfParentPolicy(XYPairs, XYPair) + sage: pol.element_constructor_attributes(()) + {'Element': , + '_parent_for': 'self'} + """ + return self.self_element_constructor_attributes(self._Element) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: SelfParentPolicy(XYPairs, XYPair) # indirect doctest + Set factory policy for with parent ``self`` + """ + return "Set factory policy for {} with parent ``self``".format(self._Element) + + +class TopMostParentPolicy(SetFactoryPolicy): + r""" + Policy where the parent of the elements is the topmost parent. + + INPUT: + + - ``factory`` -- an instance of :class:`SetFactory` + - ``top_constraints`` -- the empty set of constraints. + - ``Element`` -- a subclass of :class:`~.element.Element` + + Given a factory ``F`` and a class ``E``, returns a policy for + parent ``P`` creating element in class ``E`` and parent + ``factory(*top_constraints, policy)``. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: P = XYPairs(); P.policy() + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + """ + def __init__(self, factory, top_constraints, Element): + """ + TEST:: + + sage: from sage.structure.set_factories import TopMostParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: T = TopMostParentPolicy(XYPairs, (), XYPair); T + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + sage: TestSuite(T).run(skip='_test_category') + """ + # assert(isinstance(top_constraints, tuple)) + self._top_constraints = top_constraints + self._Element = Element + SetFactoryPolicy.__init__(self, factory) + + def element_constructor_attributes(self, constraints): + r""" + Return the element constructor attributes as per + :meth:`SetFactoryPolicy.element_constructor_attributes`. + + INPUT: + + - ``constraints`` -- a bunch of constraints + + TESTS:: + + sage: from sage.structure.set_factories import TopMostParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = TopMostParentPolicy(XYPairs, (), XYPair) + sage: pol.element_constructor_attributes(()) + {'Element': , + '_parent_for': 'self'} + sage: pol.element_constructor_attributes((1)) + {'_facade_for': AllPairs, + '_parent_for': AllPairs, + 'element_class': } + """ + factory = self._factory + if constraints == self._top_constraints: + return self.self_element_constructor_attributes(self._Element) + else: + return self.facade_element_constructor_attributes( + factory(*self._top_constraints, policy=self)) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories import TopMostParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: TopMostParentPolicy(XYPairs, (), XYPair) # indirect doctest + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + """ + return "Set factory policy for {} with parent {}[={}({})]".format( + self._Element, self._factory(*self._top_constraints, policy=self), + self._factory, self._top_constraints) + + +class FacadeParentPolicy(SetFactoryPolicy): + r""" + Policy for facade parent. + + INPUT: + + - ``factory`` -- an instance of :class:`SetFactory` + - ``parent`` -- an instance of :class:`Parent` + + Given a factory ``F`` and a class ``E``, returns a policy for + parent ``P`` creating elements as if they were created by + ``parent``. + + EXAMPLES:: + + sage: from sage.structure.set_factories import SelfParentPolicy, FacadeParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + + We create a custom standard parent ``P``:: + + sage: selfpolicy = SelfParentPolicy(XYPairs, XYPair) + sage: P = XYPairs(x=2, policy=selfpolicy) + sage: pol = FacadeParentPolicy(XYPairs, P) + sage: P2 = XYPairs(x=2, y=3, policy=pol) + sage: el = P2.an_element() + sage: el.parent() is P + True + sage: type(el) is P.element_class + True + + If ``parent`` is itself a facade parent, then transitivity is + correctly applied:: + + sage: P = XYPairs() + sage: P2 = XYPairs(x=2) + sage: P2.category() + Category of facade finite enumerated sets + sage: pol = FacadeParentPolicy(XYPairs, P) + sage: P23 = XYPairs(x=2, y=3, policy=pol) + sage: el = P2.an_element() + sage: el.parent() is P + True + sage: type(el) is P.element_class + True + """ + def __init__(self, factory, parent): + r""" + TEST:: + + sage: from sage.structure.set_factories import FacadeParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: F = FacadeParentPolicy(XYPairs, XYPairs()); F + Set factory policy for facade parent AllPairs + sage: TestSuite(F).run(skip='_test_category') + """ + self._parent_for = parent + SetFactoryPolicy.__init__(self, factory) + + def element_constructor_attributes(self, constraints): + r""" + Return the element constructor attributes as per + :meth:`SetFactoryPolicy.element_constructor_attributes`. + + INPUT: + + - ``constraints`` -- a bunch of constraints + + TESTS:: + + sage: from sage.structure.set_factories import FacadeParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: pol = FacadeParentPolicy(XYPairs, XYPairs()) + sage: pol.element_constructor_attributes(()) + {'_facade_for': AllPairs, + '_parent_for': AllPairs, + 'element_class': } + sage: pol.element_constructor_attributes((1)) + {'_facade_for': AllPairs, + '_parent_for': AllPairs, + 'element_class': } + """ + return self.facade_element_constructor_attributes( + self._parent_for._parent_for) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories import FacadeParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: FacadeParentPolicy(XYPairs, XYPairs()) # indirect doctest + Set factory policy for facade parent AllPairs + """ + return "Set factory policy for facade parent {}".format( + self._parent_for) + + + + +class BareFunctionPolicy(SetFactoryPolicy): + r""" + Policy where element are contructed using a bare function. + + INPUT: + + - ``factory`` -- an instance of :class:`SetFactory` + - ``contructor`` -- a function + + Given a factory ``F`` and a function ``c``, returns a policy for + parent ``P`` creating element using the function ``f``. + + EXAMPLES:: + + sage: from sage.structure.set_factories import BareFunctionPolicy + sage: from sage.structure.set_factories_example import XYPairs + sage: cons = lambda t, check: tuple(t) # ignore the check parameter + sage: tuplepolicy = BareFunctionPolicy(XYPairs, cons) + sage: P = XYPairs(x=2, policy=tuplepolicy) + sage: el = P.an_element() + sage: type(el) + + """ + def __init__(self, factory, constructor): + """ + TEST:: + + sage: from sage.structure.set_factories import BareFunctionPolicy + sage: from sage.structure.set_factories_example import XYPairs + sage: pol = BareFunctionPolicy(XYPairs, tuple) + sage: TestSuite(pol).run(skip='_test_category') + """ + # assert(isinstance(top_constraints, tuple)) + self._constructor = constructor + SetFactoryPolicy.__init__(self, factory) + + def element_constructor_attributes(self, constraints): + r""" + Return the element constructor attributes as per + :meth:`SetFactoryPolicy.element_constructor_attributes`. + + INPUT: + + - ``constraints`` -- a bunch of constraints + + TESTS:: + + sage: from sage.structure.set_factories import BareFunctionPolicy + sage: from sage.structure.set_factories_example import XYPairs + sage: pol = BareFunctionPolicy(XYPairs, tuple) + sage: pol.element_constructor_attributes(()) + {'_element_constructor_': , '_parent_for': None} + """ + return {'_element_constructor_' : self._constructor, '_parent_for' : None} + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories import BareFunctionPolicy + sage: from sage.structure.set_factories_example import XYPairs + sage: BareFunctionPolicy(XYPairs, tuple) + Set factory policy for bare function + """ + return "Set factory policy for bare function {}".format(self._constructor) + + +#################################################### +# Parent # +#################################################### + + +class ParentWithSetFactory(Parent): + r""" + Abstract class for parent belonging to a set factory. + + INPUT: + + - ``constraints`` -- a set of constraints + - ``policy`` -- the policy for element construction + - ``category`` -- the category of the parent (default to ``None``) + + Depending on the constraints and the policy, initialize the parent + in a proper category to set up element construction. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs, PairsX_ + sage: P = PairsX_(3, XYPairs._default_policy) + sage: P is XYPairs(3) + True + sage: P.category() + Category of facade finite enumerated sets + """ + def __init__(self, constraints, policy, category=None): + r""" + TESTS:: + + sage: from sage.structure.set_factories import ParentWithSetFactory + sage: from sage.structure.set_factories_example import XYPairs + sage: isinstance(XYPairs(3), ParentWithSetFactory) # indirect doctest + True + """ + self._constraints = constraints + assert isinstance(policy, SetFactoryPolicy) + self._policy = policy + policy_attributes = policy.element_constructor_attributes(constraints) + for attrname, attr in policy_attributes.items(): + if attr == "self": + setattr(self, attrname, self) + else: + setattr(self, attrname, attr) + assert self._parent_for is None or isinstance(self._parent_for, Parent) + Parent.__init__(self, + category=category, + facade=policy_attributes.get('_facade_for', None)) + + def constraints(self): + r""" + Return the constraints defining ``self``. + + .. NOTE:: + + Currently there is no specification on how constraints are + passed as arguments. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs().constraints() + () + sage: XYPairs(x=3).constraints() + (3, None) + sage: XYPairs(y=2).constraints() + (None, 2) + """ + return self._constraints + + def policy(self): + r""" + Return the policy used when ``self`` was created. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs().policy() + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + sage: XYPairs(x=3).policy() + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + """ + return self._policy + + def facade_policy(self): + r""" + Return the policy for parent facade for ``self``. + + EXAMPLES:: + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: from sage.structure.set_factories_example import XYPairs, XYPair + + We create a custom standard parent ``P``:: + + sage: selfpolicy = SelfParentPolicy(XYPairs, XYPair) + sage: P = XYPairs(x=2, policy=selfpolicy) + sage: P.facade_policy() + Set factory policy for facade parent {(2, b) | b in range(5)} + + Now passing ``P.facade_policy()`` creates parent which are facade for + ``P``:: + + sage: P3 = XYPairs(x=2, y=3, policy=P.facade_policy()) + sage: P3.facade_for() == (P,) + True + sage: el = P3.an_element() + sage: el.parent() is P + True + """ + return FacadeParentPolicy(self.factory(), self) + + def factory(self): + r""" + Return the factory which built ``self``. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs().factory() is XYPairs + True + sage: XYPairs(x=3).factory() is XYPairs + True + """ + return self._policy.factory() + + def subset(self, *args, **options): + r""" + Return a subset of ``self`` by adding more constraints. + + .. NOTE:: + + Currently there is no specification on how constraints are + passed as arguments. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: S = XYPairs() + sage: S3 = S.subset(x=3) + sage: S3.list() + [(3, 0), (3, 1), (3, 2), (3, 3), (3, 4)] + + TESTS:: + + sage: S3 is XYPairs(3) + True + sage: S3 is XYPairs(x=3) + True + """ + factory = self.factory() + constr = factory.add_constraints(self._constraints, + (args, options)) + return factory(*constr, policy=self._policy) + + def _test_subset(self, **options): + r""" + Tests that subsets with no extra parameters returns + ``self``. + + Currently, only the test that one gets the same parent when no + more constraints are given, is performed. + + .. TODO:: + + Straighten the test when handling of constraints will be + specified. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: S = XYPairs() + sage: S._test_subset() + """ + tester = self._tester(**options) + tester.assertTrue(self.subset() is self) + + @abstract_method + def check_element(self, x, check): + r""" + Check that ``x`` verifies the constraints of ``self``. + + INPUT: + + - ``x`` -- an instance of ``self.element_class``. + + - ``check`` -- the level of checking to be performed (usually a + boolean). + + This method may assume that ``x`` was properly constructed by + ``self`` or a possible super-set of ``self`` for which + ``self`` is a facade. It should return nothing if ``x`` + verifies the constraints and raise a + :exc:`~exceptions.ValueError` explaining which constraints + ``x`` fails otherwise. + + The method should accept an extra parameter check specifying + which level of check should be performed. It will only be + called when ``bool(check)`` evaluates to ``True``. + + .. TODO:: + + Should we always call check element and let it decide + which check has to be performed ? + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: S = XYPairs() + sage: el = S((2,3)) + sage: S.check_element(el, True) + sage: XYPairs(x=2).check_element(el, True) + sage: XYPairs(x=3).check_element(el, True) + Traceback (most recent call last): + ... + ValueError: Wrong first coordinate + sage: XYPairs(y=4).check_element(el, True) + Traceback (most recent call last): + ... + ValueError: Wrong second coordinate + """ + + def __contains__(self, x): + r""" + Default implementation for ``__contains__``. + + INPUT: + + - ``x`` -- any object + + Check for class, parent and calls ``self.check_element(x)``. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: S = XYPairs() + sage: el = S((2,3)) + sage: el in S + True + sage: el in XYPairs(x=2) + True + sage: el in XYPairs(x=3) + False + sage: el in XYPairs(y=4) + False + """ + if (isinstance(x, self.element_class) and + x.parent() == self._parent_for): # TODO: is_parent_of ??? + try: + self.check_element(x, True) + except ValueError: + return False + else: + return True + return False + + def __call__(self, *args, **keywords): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: S = XYPairs() + sage: el = S((2,3)); el + (2, 3) + sage: S(el) is el + True + + sage: XYPairs(x=3)((2,3)) + Traceback (most recent call last): + ... + ValueError: Wrong first coordinate + + sage: XYPairs(x=3)(el) + Traceback (most recent call last): + ... + ValueError: Wrong first coordinate + """ + # Ensure idempotence of element construction + if (len(args) == 1 and + isinstance(args[0], self.element_class) and + args[0].parent() == self._parent_for): + check = keywords.get("check", True) + if check: + self.check_element(args[0], check) + return args[0] + else: + return Parent.__call__(self, *args, **keywords) + + # QUESTION: Should we call: + # self._parent_for._element_constructor_ + # currently we do not call it directly because: + # - it may do some extra check we dont want to perform ? + # - calling directly element_class should be faster + def _element_constructor_(self, *args, **keywords): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs()((2,3)) # indirect doctest + (2, 3) + sage: XYPairs(x=3)((3,3)) # indirect doctest + (3, 3) + sage: XYPairs(x=3)((2,3)) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Wrong first coordinate + + sage: XYPairs(x=3)((2,3), check=False) # Don't do this at home, kids + (2, 3) + """ + check = keywords.get("check", True) + res = self.element_class(self._parent_for, *args, **keywords) + if check: + self.check_element(res, check) + return res diff --git a/src/sage/structure/set_factories_example.py b/src/sage/structure/set_factories_example.py new file mode 100644 index 00000000000..74bced6f88e --- /dev/null +++ b/src/sage/structure/set_factories_example.py @@ -0,0 +1,527 @@ +r""" +An example of set factory +========================= + +The goal of this module is to exemplify the use of set factories. Note +that the code is intentionally kept minimal; many things and in +particular several iterators could be written in a more efficient way. + +.. SEEALSO:: + + :mod:`.set_factories` for an introduction to set + factories, their specifications, and examples of their use and + implementation based on this module. + +We describe here a factory used to construct the set `S` of couples `(x,y)` +with `x` and `y` in `I:=\{0,1,2,3,4\}`, together with the following subsets, +where `(a, b)\in S` + +.. MATH:: + + S_a := \{(x,y) \in S \mid x = a\}, + + S^b := \{(x,y) \in S \mid y = b\}, + + S_a^b := \{(x,y) \in S \mid x = a, y = b\}. + + +""" +#***************************************************************************** +# Copyright (C) 2012 Florent Hivert +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.structure.set_factories import ( + SetFactory, ParentWithSetFactory, TopMostParentPolicy) +from sage.sets.all import DisjointUnionEnumeratedSets +from sage.sets.family import LazyFamily +from sage.categories.enumerated_sets import EnumeratedSets +from sage.rings.integer import Integer +from sage.misc.lazy_attribute import lazy_attribute + +MAX = 5 + + +class XYPairsFactory(SetFactory): + r""" + An example of set factory, for sets of pairs of integers. + + .. SEEALSO:: + + :mod:`.set_factories` for an introduction to set factories. + """ + def __call__(self, x=None, y=None, policy=None): + r""" + Construct the subset from constraints. + + Consider the set `S` of couples `(x,y)` with `x` and `y` in + `I:=\{0,1,2,3,4\}`. Returns the subsets of element of `S` satisfying + some constraints. + + INPUT: + + - ``x=a`` -- where ``a`` is an integer (default to ``None``). + - ``y=b`` -- where ``b`` is an integer (default to ``None``). + - ``policy`` -- the policy passed to the created set. + + .. SEEALSO:: + + :class:`.set_factories.SetFactoryPolicy` + + EXAMPLES: + + Let us first create the set factory:: + + sage: from sage.structure.set_factories_example import XYPairsFactory + sage: XYPairs = XYPairsFactory() + + One can then use the set factory to construct a set:: + + sage: P = XYPairs(); P.list() + [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (0, 4), (1, 4), (2, 4), (3, 4), (4, 4)] + + .. NOTE:: + + This function is actually the ``__call__`` method of + :class:`XYPairsFactory`. + + TESTS:: + + sage: TestSuite(P).run() + + """ + if policy is None: + policy = self._default_policy + + if isinstance(x, (Integer, int)): + if isinstance(y, (Integer, int)): + return SingletonPair(x, y, policy) + return PairsX_(x, policy) + elif isinstance(y, (Integer, int)): + return Pairs_Y(y, policy) + return AllPairs(policy) + + def add_constraints(self, cons, (args, opts)): + r""" + Add constraints to the set ``cons`` as per + :meth:`SetFactory.add_constraints<.set_factories.SetFactory.add_constraints>`. + + This is a crude implementation for the sake of the demonstration which + should not be taken as an example. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs.add_constraints((3,None), ((2,), {})) + Traceback (most recent call last): + ... + ValueError: Duplicate value for constraints 'x': was 3 now 2 + sage: XYPairs.add_constraints((), ((2,), {})) + (2, None) + sage: XYPairs.add_constraints((), ((2,), {'y':3})) + (2, 3) + """ + res = list(cons) + res += [None] * (2 - len(res)) + + def set_args(argss): + for i, v in enumerate(argss): + if res[i] is not None and v is not None: + raise ValueError("Duplicate value for constraints '{}': " + "was {} now {}".format(['x', 'y'][i], + res[i], v)) + if v is not None: + res[i] = v + set_args(args) + + def parse_args(x=None, y=None): + set_args((x, y)) + parse_args(**opts) + if res == (None, None): + return () + return tuple(res) + + @lazy_attribute + def _default_policy(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairsFactory + sage: XYPairs = XYPairsFactory() + sage: XYPairs._default_policy + Set factory policy for with parent AllPairs[=Factory for XY pairs(())] + """ + return TopMostParentPolicy(self, (), XYPair) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs # indirect doctest + Factory for XY pairs + """ + return "Factory for XY pairs" + +XYPairs = XYPairsFactory() +XYPairs.__doc__ = XYPairsFactory.__call__.__doc__ + + +class XYPair(ElementWrapper): + r""" + A class for Elements `(x,y)` with `x` and `y` in `\{0,1,2,3,4\}`. + + EXAMPLES:: + + sage: from sage.structure.set_factories_example import XYPair + sage: p = XYPair(Parent(), (0,1)); p + (0, 1) + sage: p = XYPair(Parent(), (0,8)) + Traceback (most recent call last): + ... + ValueError: numbers must be in range(5) + """ + def __init__(self, parent, value, check=True): + """ + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(); p = P.list()[0] + sage: TestSuite(p).run() + """ + if check: + if not isinstance(value, tuple): + raise ValueError("Value {} must be a tuple".format(value)) + if len(value) != 2: + raise ValueError("Value must be of length 2") + if not all(int(x) in range(MAX) for x in value): + raise ValueError("numbers must be in range({})".format(MAX)) + ElementWrapper.__init__(self, parent, value) + + +class AllPairs(ParentWithSetFactory, DisjointUnionEnumeratedSets): + r""" + This parent shows how one can use set factories together with + :class:`DisjointUnionEnumeratedSets`. + + It is constructed as the disjoint union + (:class:`DisjointUnionEnumeratedSets`) of :class:`Pairs_Y` parents: + + .. math:: + + S := \bigcup_{i = 0,1,..., 4} S^y + + .. WARNING:: + + When writing a parent ``P`` as a disjoint union of a family of parents + ``P_i``, the parents ``P_i`` must be constructed as facade parents for + ``P``. As a consequence, it should be passed ``P.facade_policy()`` as + policy argument. See the source code of :meth:`pairs_y` for an + example. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(); P.list() + [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (0, 4), (1, 4), (2, 4), (3, 4), (4, 4)] + """ + def __init__(self, policy): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: TestSuite(XYPairs()).run() + """ + ParentWithSetFactory.__init__(self, (), policy=policy, + category=EnumeratedSets().Finite()) + DisjointUnionEnumeratedSets.__init__(self, + LazyFamily(range(MAX), + self.pairs_y), + facade=True, keepkey=False, + category=self.category()) + + def pairs_y(self, letter): + r""" + Construct the parent for the disjoint union + + Construct a parent in :class:`Pairs_Y` as a facade parent for + ``self``. + + This is an internal function which should be hidden from the user + (typically under the name ``_pairs_y``. We put it here for + documentation. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs, XYPair + sage: S = XYPairs() + sage: S1 = S.pairs_y(1); S1 + {(a, 1) | a in range(5)} + sage: S.an_element().parent() + AllPairs + + sage: from sage.structure.set_factories import SelfParentPolicy + sage: selfpolicy = SelfParentPolicy(XYPairs, XYPair) + sage: selfS = XYPairs(policy=selfpolicy) + sage: selfS1 = selfS.pairs_y(1); selfS1 + {(a, 1) | a in range(5)} + sage: S.an_element().parent() is selfS + False + sage: selfS.an_element().parent() is selfS + True + """ + return Pairs_Y(letter, policy=self.facade_policy()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs() # indirect doctest + AllPairs + """ + return "AllPairs" + + def check_element(self, el, check): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs() + sage: P.check_element(P.an_element(), True) + sage: XYPairs()((7, 0)) # indirect doctest + Traceback (most recent call last): + ... + ValueError: numbers must be in range(5) + """ + pass + + +class PairsX_(ParentWithSetFactory, UniqueRepresentation): + r""" + The set of pairs `(x, 0), (x, 1), ..., (x, 4)`. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(0); P.list() + [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)] + """ + def __init__(self, x, policy): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: TestSuite(XYPairs(0)).run() + """ + self._x = x + ParentWithSetFactory.__init__(self, (x, None), policy=policy, + category=EnumeratedSets().Finite()) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs(x=1) + {(1, b) | b in range(5)} + """ + return "{(%s, b) | b in range(%s)}" % (self._x, MAX) + + def an_element(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(x=0); P.an_element() + (0, 0) + """ + return self._element_constructor_((self._x, 0), check=False) + + def check_element(self, el, check): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(x=1) + sage: P.check_element(P.an_element(), True) + sage: XYPairs(x=1)((0, 0)) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Wrong first coordinate + """ + (x, y) = el.value + if x != self._x: + raise ValueError("Wrong first coordinate") + + def __iter__(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: list(XYPairs(x=1)) + [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)] + """ + for i in range(MAX): + yield self._element_constructor_((self._x, i), check=False) + + +class Pairs_Y(ParentWithSetFactory, DisjointUnionEnumeratedSets): + r""" + The set of pairs `(0, y), (1, y), ..., (4, y)`. + + It is constructed as the disjoint union + (:class:`DisjointUnionEnumeratedSets`) of :class:`SingletonPair` parents: + + .. math:: + + S^y := \bigcup_{i = 0,1,..., 4} S_i^y + + .. SEEALSO:: + + :class:`AllPairs` for how to properly construct + :class:`DisjointUnionEnumeratedSets` using + :class:`~sage.structure.set_factories.ParentWithSetFactory`. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(y=1); P.list() + [(0, 1), (1, 1), (2, 1), (3, 1), (4, 1)] + """ + def __init__(self, y, policy): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: TestSuite(XYPairs(y=1)).run() + """ + self._y = y + ParentWithSetFactory.__init__(self, (None, y), policy=policy, + category=EnumeratedSets().Finite()) + DisjointUnionEnumeratedSets.__init__( + self, LazyFamily(range(MAX), self.single_pair), + facade=True, keepkey=False, + category=self.category()) # TODO remove and fix disjoint union. + + def _repr_(self): + """ + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs(y=1) + {(a, 1) | a in range(5)} + """ + return "{(a, %s) | a in range(%s)}" % (self._y, MAX) + + def an_element(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs(y=1).an_element() + (0, 1) + """ + return self._element_constructor_((0, self._y), check=False) + + def single_pair(self, letter): + r""" + Construct the singleton pair parent + + Construct a singleton pair for ``(self.y, letter)`` as a facade parent + for ``self``. + + .. SEEALSO:: + + :class:`AllPairs` for how to properly construct + :class:`DisjointUnionEnumeratedSets` using + :class:`~sage.structure.set_factories.ParentWithSetFactory`. + + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(y=1) + sage: P.single_pair(0) + {(0, 1)} + sage: P.single_pair(0).an_element().parent() + AllPairs + """ + return SingletonPair(letter, self._y, policy=self.facade_policy()) + + def check_element(self, el, check): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(y=1) + sage: P.check_element(P.an_element(), True) + sage: XYPairs(y=1)((1, 0)) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Wrong second coordinate + """ + (x, y) = el.value + if y != self._y: + raise ValueError("Wrong second coordinate") + + +class SingletonPair(ParentWithSetFactory, UniqueRepresentation): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: P = XYPairs(0,1); P.list() + [(0, 1)] + """ + def __init__(self, x, y, policy): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: TestSuite(XYPairs(0,1)).run() + """ + self._xy = (x, y) + ParentWithSetFactory.__init__(self, (x, y), policy=policy, + category=EnumeratedSets().Finite()) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs(x=2, y=1) + {(2, 1)} + """ + return "{%s}" % (self._xy,) + + def check_element(self, el, check): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: XYPairs(0,1).check_element(XYPairs()((0,1)), True) + sage: XYPairs(0,1).check_element(XYPairs()((1,0)), True) + Traceback (most recent call last): + ... + ValueError: Wrong coordinate + sage: XYPairs(0,1)((1,1)) + Traceback (most recent call last): + ... + ValueError: Wrong coordinate + """ + if el.value != self._xy: + raise ValueError("Wrong coordinate") + + def __iter__(self): + r""" + TESTS:: + + sage: from sage.structure.set_factories_example import XYPairs + sage: list(XYPairs(0,1)) + [(0, 1)] + """ + yield self._element_constructor_(self._xy, check=False) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 7e7b1356168..3809dc83610 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -120,7 +120,7 @@ Test if :trac:`9947` is fixed:: sage: a.real_part() 4*sqrt(3)/(sqrt(3) + 5) sage: a.imag_part() - sqrt(abs(4*(sqrt(3) + 5)*(sqrt(3) - 5) + 48))/(sqrt(3) + 5) + 2*sqrt(10)/(sqrt(3) + 5) """ ############################################################################### @@ -1912,6 +1912,14 @@ cdef class Expression(CommutativeRingElement): sage: SR(5).is_integer() True + + TESTS: + + Check that integer variables are recognized (:trac:`18921`):: + + sage: _ = var('n', domain='integer') + sage: n.is_integer() + True """ return self._gobj.info(info_integer) @@ -2396,6 +2404,12 @@ cdef class Expression(CommutativeRingElement): sage: assert(sqrt(2) < SR(oo)) sage: assert(SR(-oo) < sqrt(2)) sage: assert(sqrt(2) > SR(-oo)) + + Check that :trac:`18360` is fixed:: + + sage: f(x) = matrix() + sage: bool(f(x) - f(x) == 0) + True """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3042,6 +3056,14 @@ cdef class Expression(CommutativeRingElement): sage: ex.substitute(a=z, b=z) (r1*x2 - r2 - x1)/x3 + TESTS: + + Check that :trac:`18360` is fixed:: + + sage: f(x) = matrix() + sage: f(x)*1 + [] + Check that floating point numbers +/- 1.0 are treated differently from integers +/- 1 (:trac:`12257`):: @@ -4127,6 +4149,11 @@ cdef class Expression(CommutativeRingElement): sage: ((-(-a*x*p)^3*(b*y*p)^3)^(c/2)).expand() (a^3*b^3*x^3*y^3)^(1/2*c)*p^(3*c) sage: x,y,p,q = var('x,y,p,q', domain='complex') + + Check that :trac:`18568` is fixed:: + + sage: ((x+sqrt(2)*x)^2).expand() + 2*sqrt(2)*x^2 + 3*x^2 """ if side is not None: if not is_a_relational(self._gobj): @@ -6441,6 +6468,63 @@ cdef class Expression(CommutativeRingElement): sig_off() return new_Expression_from_GEx(self._parent, x) + def horner(self, x): + """ + Rewrite this expression as a polynomial in Horner form in ``x``. + + EXAMPLES:: + + sage: add((i+1)*x^i for i in range(5)).horner(x) + (((5*x + 4)*x + 3)*x + 2)*x + 1 + + sage: x, y, z = SR.var('x,y,z') + sage: (x^5 + y*cos(x) + z^3 + (x + y)^2 + y^x).horner(x) + z^3 + ((x^3 + 1)*x + 2*y)*x + y^2 + y*cos(x) + y^x + + sage: expr = sin(5*x).expand_trig(); expr + 5*cos(x)^4*sin(x) - 10*cos(x)^2*sin(x)^3 + sin(x)^5 + sage: expr.horner(sin(x)) + (5*cos(x)^4 - (10*cos(x)^2 - sin(x)^2)*sin(x)^2)*sin(x) + sage: expr.horner(cos(x)) + sin(x)^5 + 5*(cos(x)^2*sin(x) - 2*sin(x)^3)*cos(x)^2 + + TESTS:: + + sage: SR(0).horner(x), SR(1).horner(x), x.horner(x) + (0, 1, x) + sage: (x^(1/3)).horner(x) + Traceback (most recent call last): + ... + ValueError: Cannot return dense coefficient list with noninteger exponents. + """ + coef = self.coefficients(x, sparse=False) + res = coef[-1] + for c in reversed(coef[:-1]): + res = res*x + c + return res + + def _evaluate_polynomial(self, pol): + """ + Evaluate a univariate polynomial on this expression. + + EXAMPLES:: + + sage: pol = QQ['s'](range(5)) + sage: pol(sin(x)) + 4*sin(x)^4 + 3*sin(x)^3 + 2*sin(x)^2 + sin(x) + + TESTS:: + + sage: SR(0.1)._evaluate_polynomial(pol) + 0.123400000000000 + """ + cdef Expression zero + try: + return new_Expression_from_pyobject(self._parent, pol(self.pyobject())) + except TypeError: + zero = self._parent.zero() + return zero.add(*(pol[i]*self**i + for i in xrange(pol.degree() + 1))) def collect_common_factors(self): """ This function does not perform a full factorization but only @@ -6705,7 +6789,7 @@ cdef class Expression(CommutativeRingElement): .. SEEALSO:: - - :func:`sage.misc.functional.norm` + :func:`sage.misc.functional.norm` EXAMPLES:: @@ -8092,7 +8176,7 @@ cdef class Expression(CommutativeRingElement): """ Return this expression normalized as a fraction - .. SEEALSO: + .. SEEALSO:: :meth:`numerator`, :meth:`denominator`, :meth:`numerator_denominator`, :meth:`combine` @@ -10502,8 +10586,10 @@ cdef class Expression(CommutativeRingElement): ... AttributeError: Please use a tuple or list for several variables. - .. SEEALSO: http://docs.sympy.org/latest/modules/solvers/diophantine.html - """ + .. SEEALSO:: + + http://docs.sympy.org/latest/modules/solvers/diophantine.html + """ from sympy.solvers.diophantine import diophantine from sympy import sympify diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 29f68b21fa4..64d9ae6768f 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -852,8 +852,8 @@ def composition(self, ex, operator): sage: a = AlgebraicConverter(QQbar) sage: a.composition(exp(I*pi/3), exp) 0.500000000000000? + 0.866025403784439?*I - sage: a.composition(sin(pi/5), sin) - 0.5877852522924731? + 0.?e-18*I + sage: a.composition(sin(pi/7), sin) + 0.4338837391175581? + 0.?e-18*I TESTS:: diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index ba2a3b64a26..08d51cfe37d 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -22,7 +22,7 @@ from expression cimport new_Expression_from_GEx, Expression from ring import SR from sage.structure.coerce cimport py_scalar_to_element, is_numpy_type -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model # we keep a database of symbolic functions initialized in a session # this also makes the .operator() method of symbolic expressions work @@ -255,7 +255,7 @@ cdef class Function(SageObject): evalf = self._evalf_ # catch AttributeError early if any(self._is_numerical(x) for x in args): if not any(isinstance(x, Expression) for x in args): - p = get_coercion_model().common_parent(*args) + p = coercion_model.common_parent(*args) return evalf(*args, parent=p) except Exception: pass diff --git a/src/sage/symbolic/ginac.pxd b/src/sage/symbolic/ginac.pxd index 3f1a67ba012..95b10bb44e3 100644 --- a/src/sage/symbolic/ginac.pxd +++ b/src/sage/symbolic/ginac.pxd @@ -176,6 +176,7 @@ cdef extern from "sage/symbolic/ginac_wrap.h": unsigned domain_real "GiNaC::domain::real" unsigned domain_positive "GiNaC::domain::positive" unsigned domain_infinity "GiNaC::domain::infinity" + unsigned domain_integer "GiNaC::domain::integer" # info flags unsigned info_real "GiNaC::info_flags::real" diff --git a/src/sage/symbolic/random_tests.py b/src/sage/symbolic/random_tests.py index a2164f69bda..6b6e5944dbe 100644 --- a/src/sage/symbolic/random_tests.py +++ b/src/sage/symbolic/random_tests.py @@ -333,42 +333,43 @@ def assert_strict_weak_order(a,b,c, cmp_func): sage: x = [SR(unsigned_infinity), SR(oo), -SR(oo)] sage: cmp = matrix(3,3) - sage: indices = list(CartesianProduct(range(0,3),range(0,3))) - sage: for i,j in CartesianProduct(range(0,3),range(0,3)): - ... cmp[i,j] = x[i].__cmp__(x[j]) + sage: for i in range(3): + ....: for j in range(3): + ....: cmp[i,j] = x[i].__cmp__(x[j]) sage: cmp [ 0 -1 -1] [ 1 0 -1] [ 1 1 0] """ from sage.matrix.constructor import matrix - from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.permutation import Permutations x = (a,b,c) cmp = matrix(3,3) - indices = list(CartesianProduct(range(0,3),range(0,3))) - for i,j in indices: - cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter + for i in range(3): + for j in range(3): + cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter msg = 'The binary relation failed to be a strict weak order on the elements\n' msg += ' a = '+str(a)+'\n' msg += ' b = '+str(b)+'\n' msg += ' c = '+str(c)+'\n' msg += str(cmp) - for i in range(0,3): # irreflexivity + for i in range(3): + # irreflexivity if cmp[i,i]: raise ValueError(msg) - for i,j in indices: # asymmetric - if i==j: continue - #if x[i] == x[j]: continue - if cmp[i,j] and cmp[j,i]: raise ValueError(msg) + # asymmetric + for j in range(i): + if cmp[i,j] and cmp[j,i]: raise ValueError(msg) - for i,j,k in Permutations([0,1,2]): # transitivity + def incomparable(i,j): + return not (cmp[i,j] or cmp[j,i]) + + for i,j,k in Permutations([0,1,2]): + # transitivity if cmp[i,j] and cmp[j,k] and not cmp[i,k]: raise ValueError(msg) - def incomparable(i,j): - return (not cmp[i,j]) and (not cmp[j,i]) - for i,j,k in Permutations([0,1,2]): # transitivity of equivalence + # transitivity of equivalence if incomparable(i,j) and incomparable(j,k) and not incomparable(i,k): raise ValueError(msg) def test_symbolic_expression_order(repetitions=100): diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index c1ea1b43f46..a7c4bb57f4f 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -32,7 +32,7 @@ from sage.rings.ring cimport CommutativeRing from sage.categories.morphism cimport Morphism from sage.structure.coerce cimport is_numpy_type -from sage.rings.all import RR, CC +from sage.rings.all import RR, CC, ZZ pynac_symbol_registry = {} @@ -251,6 +251,34 @@ cdef class SymbolicRing(CommutativeRing): sage: bool(si == CC.0) True + Polynomial ring element factorizations:: + + sage: R. = QQ[] + sage: SR(factor(5*x^2 - 5)) + 5*(x + 1)*(x - 1) + sage: R. = QQ[] + sage: SR(factor(x^2 - y^2)) + (x + y)*(x - y) + sage: R. = QQ[] + sage: SR(factor(x^2*y^3 + x^2*y^2*z - x*y^3 - x*y^2*z - 2*x*y*z - 2*x*z^2 + 2*y*z + 2*z^2)) + (x*y^2 - 2*z)*(x - 1)*(y + z) + + Asymptotic expansions:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^ZZ', coefficient_ring=ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. + ... + See http://trac.sagemath.org/17601 for details. + sage: s = SR(3*x^5 * log(y) + 4*y^(3/7) + O(x*log(y))); s + 3*x^5*log(y) + 4*y^(3/7) + Order(x*log(y)) + sage: s.operator(), s.operands() + (, + [3*x^5*log(y), 4*y^(3/7), Order(x*log(y))]) + sage: t = s.operands()[0]; t + 3*x^5*log(y) + sage: t.operator(), t.operands() + (, [x^5, log(y), 3]) """ cdef GEx exp @@ -271,6 +299,7 @@ cdef class SymbolicRing(CommutativeRing): from sage.rings.infinity import (infinity, minus_infinity, unsigned_infinity) + from sage.structure.factorization import Factorization if isinstance(x, (Integer, RealNumber, float, long, complex)): GEx_construct_pyobject(exp, x) @@ -284,6 +313,9 @@ cdef class SymbolicRing(CommutativeRing): return new_Expression_from_GEx(self, g_UnsignedInfinity) elif isinstance(x, (RingElement, Matrix)): GEx_construct_pyobject(exp, x) + elif isinstance(x, Factorization): + from sage.misc.all import prod + return prod([SR(p)**e for p,e in x], SR(x.unit())) else: raise TypeError @@ -564,6 +596,12 @@ cdef class SymbolicRing(CommutativeRing): sage: SR.symbol() # temporary variable symbol... + + We propagate the domain to the assumptions database:: + + sage: n = var('n', domain='integer') + sage: solve([n^2 == 3],n) + [] """ cdef GSymbol symb cdef Expression e @@ -582,8 +620,10 @@ cdef class SymbolicRing(CommutativeRing): if latex_name is not None: symb.set_texname(latex_name) if domain is not None: - symb.set_domain(sage_domain_to_ginac(domain)) + symb.set_domain(sage_domain_to_ginac_domain(domain)) GEx_construct_symbol(&e._gobj, symb) + if domain is not None: + send_sage_domain_to_maxima(e, domain) return e @@ -595,18 +635,21 @@ cdef class SymbolicRing(CommutativeRing): if name is None: # Check if we need a temporary anonymous new symbol symb = ginac_new_symbol() if domain is not None: - symb.set_domain(sage_domain_to_ginac(domain)) + symb.set_domain(sage_domain_to_ginac_domain(domain)) else: if latex_name is None: latex_name = latex_variable_name(name) if domain is not None: - domain = sage_domain_to_ginac(domain) + ginac_domain = sage_domain_to_ginac_domain(domain) else: - domain = domain_complex - symb = ginac_symbol(name, latex_name, domain) + ginac_domain = domain_complex + symb = ginac_symbol(name, latex_name, ginac_domain) pynac_symbol_registry[name] = e GEx_construct_symbol(&e._gobj, symb) + if domain is not None: + send_sage_domain_to_maxima(e, domain) + return e cpdef var(self, name, latex_name=None, domain=None): @@ -788,16 +831,40 @@ cdef class SymbolicRing(CommutativeRing): SR = SymbolicRing() -cdef unsigned sage_domain_to_ginac(object domain) except +: - # convert the domain argument to something easy to parse - if domain is RR or domain == 'real': - return domain_real - elif domain == 'positive': - return domain_positive - elif domain is CC or domain == 'complex': - return domain_complex - else: - raise ValueError("domain must be one of 'complex', 'real' or 'positive'") +cdef unsigned sage_domain_to_ginac_domain(object domain) except -1: + """ + TESTS:: + + sage: var('x', domain='foo') + Traceback (most recent call last): + ... + ValueError: 'foo': domain must be one of 'complex', 'real', 'positive' or 'integer' + """ + # convert the domain argument to something easy to parse + if domain is RR or domain == 'real': + return domain_real + elif domain == 'positive': + return domain_positive + elif domain is CC or domain == 'complex': + return domain_complex + elif domain is ZZ or domain == 'integer': + return domain_integer + else: + raise ValueError(repr(domain)+": domain must be one of 'complex', 'real', 'positive' or 'integer'") + +cdef send_sage_domain_to_maxima(Expression v, object domain) except +: + from sage.symbolic.assumptions import assume + # convert the domain argument to something easy to parse + if domain is RR or domain == 'real': + assume(v, 'real') + elif domain == 'positive': + assume(v>0) + elif domain is CC or domain == 'complex': + assume(v, 'complex') + elif domain is ZZ or domain == 'integer': + assume(v, 'integer') + else: + raise ValueError(repr(domain)+": domain must be one of 'complex', 'real', 'positive' or 'integer'") cdef class NumpyToSRMorphism(Morphism): r""" diff --git a/src/sage/tensor/coordinate_patch.py b/src/sage/tensor/coordinate_patch.py index 88df32a42d7..908e1f9d972 100644 --- a/src/sage/tensor/coordinate_patch.py +++ b/src/sage/tensor/coordinate_patch.py @@ -81,20 +81,18 @@ def __init__(self, coordinates, metric = None): INPUT: - ``coordinates`` -- a set of symbolic variables that serve - as coordinates on this space. + as coordinates on this space. - - ``metric`` (default: None) -- a metric tensor on this - coordinate patch. Providing anything other than ``None`` - is currently not defined. + - ``metric`` (default: ``None``) -- a metric tensor on this + coordinate patch. Providing anything other than ``None`` + is currently not defined. EXAMPLES:: sage: x, y, z = var('x, y, z') sage: S = CoordinatePatch((x, y, z)); S Open subset of R^3 with coordinates x, y, z - """ - from sage.symbolic.ring import is_SymbolicVariable if not all(is_SymbolicVariable(c) for c in coordinates): @@ -107,8 +105,6 @@ def __init__(self, coordinates, metric = None): if metric is not None: raise NotImplementedError("Metric geometry not supported yet.") - - def __eq__(self, other): """ Return equality if and only if other has the same coordinates diff --git a/src/sage/tensor/differential_form_element.py b/src/sage/tensor/differential_form_element.py index 2305d98b0a5..87abcc47173 100644 --- a/src/sage/tensor/differential_form_element.py +++ b/src/sage/tensor/differential_form_element.py @@ -24,7 +24,7 @@ from sage.symbolic.ring import SR -from sage.rings.ring_element import RingElement +from sage.structure.element import RingElement from sage.algebras.algebra_element import AlgebraElement from sage.rings.integer import Integer from sage.combinat.permutation import Permutation @@ -44,7 +44,7 @@ def sort_subscript(subscript): INPUT: - ``subscript`` -- a subscript, i.e. a range of not necessarily - distinct integers + distinct integers OUTPUT: @@ -396,16 +396,14 @@ def __init__(self, parent, degree, fun = None): if degree == 0 and fun is not None: self.__setitem__([], fun) - def __getitem__(self, subscript): r""" Return a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer - or a list of integers. - + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: @@ -426,7 +424,6 @@ def __getitem__(self, subscript): sage: df[2] 0 """ - if isinstance(subscript, (Integer, int)): subscript = (subscript, ) else: @@ -447,14 +444,14 @@ def __getitem__(self, subscript): else: return 0 - def __setitem__(self, subscript, fun): r""" Modify a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer or a list of integers. + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: diff --git a/src/sage/tensor/differential_forms.py b/src/sage/tensor/differential_forms.py index 6f367db317b..d4d2332b19e 100644 --- a/src/sage/tensor/differential_forms.py +++ b/src/sage/tensor/differential_forms.py @@ -76,11 +76,13 @@ class DifferentialForms(Algebra): def __init__(self, coordinate_patch = None): """ Construct the algebra of differential forms on a given coordinate patch. + See ``DifferentialForms`` for details. INPUT: - ``coordinate_patch`` -- Coordinate patch where the algebra lives. + If no coordinate patch is given, a default coordinate patch with coordinates (x, y, z) is used. @@ -91,9 +93,7 @@ def __init__(self, coordinate_patch = None): Open subset of R^2 with coordinates p, q sage: F = DifferentialForms(U); F Algebra of differential forms in the variables p, q - """ - from sage.categories.graded_algebras_with_basis \ import GradedAlgebrasWithBasis from sage.structure.parent_gens import ParentWithGens diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 8fbe8419514..15a1fea98e2 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -243,7 +243,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: M.category() Category of modules over Integer Ring sage: N.category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) In other words, the module created by ``FreeModule`` is actually `\ZZ^3`, while, in the absence of any distinguished basis, no *canonical* isomorphism @@ -367,7 +368,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: V = VectorSpace(QQ,3) ; V Vector space of dimension 3 over Rational Field sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) sage: V is QQ^3 True sage: V.basis() diff --git a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py index 116e4a37784..15581a2cd6e 100644 --- a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py +++ b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py @@ -475,7 +475,7 @@ Sage example in ./kschurnotes/notes-mike-anne.tex, line 2810:: sage: c = Partition([3,2,1]).to_core(3) - sage: for p in f.support(): + sage: for p in sorted(f.support()): # Sorted for consistant doctest ordering ....: print p, SkewPartition([p.to_core(3).to_partition(),c.to_partition()]) ....: [3, 1, 1, 1, 1] [[5, 2, 1, 1, 1], [5, 2, 1]] diff --git a/src/sage/tests/cmdline.py b/src/sage/tests/cmdline.py index cac3d5c575d..02ec2d60764 100644 --- a/src/sage/tests/cmdline.py +++ b/src/sage/tests/cmdline.py @@ -37,7 +37,6 @@ --root --rst2txt --rst2sws ---scons --sh --singular --sqlite3 @@ -199,8 +198,9 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 0 - Test ``sage --info [packages]``, unless this is a binary (bdist) - distribution which doesn't ship spkgs:: + Test ``sage --info [packages]`` and the equivalent + ``sage -p --info --info [packages]`` (the doubling of ``--info`` + is intentional, that option should be idempotent):: sage: out, err, ret = test_executable(["sage", "--info", "sqlite"]) sage: print out @@ -215,6 +215,19 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 0 + sage: out, err, ret = test_executable(["sage", "-p", "--info", "--info", "sqlite"]) + sage: print out + Found local metadata for sqlite-... + = SQLite = + ... + SQLite is a software library that implements a self-contained, + serverless, zero-configuration, transactional SQL database engine. + ... + sage: err + '' + sage: ret + 0 + Test ``sage-run`` on a Python file, both with an absolute and with a relative path:: sage: dir = tmp_dir(); name = 'python_test_file.py' @@ -574,14 +587,6 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 0 - sage: (out, err, ret) = test_executable(["sage", "--scons", "--version"]) - sage: out.find("SCons") >= 0 - True - sage: err - '' - sage: ret - 0 - sage: (out, err, ret) = test_executable(["sage", "--sqlite3", "--version"]) sage: out.startswith("3.") True diff --git a/src/sage/tests/french_book/domaines_doctest.py b/src/sage/tests/french_book/domaines_doctest.py index f8a15b85660..4628ffd4740 100644 --- a/src/sage/tests/french_book/domaines_doctest.py +++ b/src/sage/tests/french_book/domaines_doctest.py @@ -174,7 +174,7 @@ Sage example in ./domaines.tex, line 415:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces Sage example in ./domaines.tex, line 421:: diff --git a/src/sage/tests/french_book/nonlinear_doctest.py b/src/sage/tests/french_book/nonlinear_doctest.py index cb175085117..e51096a073e 100644 --- a/src/sage/tests/french_book/nonlinear_doctest.py +++ b/src/sage/tests/french_book/nonlinear_doctest.py @@ -37,10 +37,11 @@ Sage example in ./nonlinear.tex, line 231:: + sage: from itertools import product sage: def build_complex_roots(degree): ....: R. = PolynomialRing(CDF, 'x') ....: v = [] - ....: for c in CartesianProduct(*[[-1, 1]] * (degree + 1)): + ....: for c in product([-1, 1], repeat=degree+1): ....: v.extend(R(c).roots(multiplicities=False)) ....: return v sage: data = build_complex_roots(12) # long time diff --git a/src/sage/version.py b/src/sage/version.py index 2e19d638c50..6de652178fb 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.9.beta0' -date = '2015-07-29' +version = '6.10.beta2' +date = '2015-10-28' diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index f4d8a04d823..639673bd024 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -47,7 +47,7 @@ def pari_share(): return os.path.join(SAGE_LOCAL, "share", "pari") paren_re = re.compile(r"[(](.*)[)]") -argname_re = re.compile(r"[ {]*([A-Za-z0-9_]+)") +argname_re = re.compile(r"[ {]*([A-Za-z_][A-Za-z0-9_]*)") def read_pari_desc(): """ diff --git a/src/setup.py b/src/setup.py index 4c7b0939c87..cf0013689f4 100755 --- a/src/setup.py +++ b/src/setup.py @@ -324,6 +324,7 @@ def plural(n,noun): ######################################################################## from distutils.command.build_ext import build_ext +from distutils.command.install import install from distutils.dep_util import newer_group from distutils import log @@ -577,7 +578,6 @@ def run_cythonize(): version_file = os.path.join(os.path.dirname(__file__), '.cython_version') version_stamp = '\n'.join([ 'cython version: ' + str(Cython.__version__), - 'NTL dependencies fixed: True', 'debug: ' + str(debug), 'profile: ' + str(profile), ""]) @@ -634,6 +634,22 @@ def run_cythonize(): print('Finished cleaning, time: %.2f seconds.' % (time.time() - t)) +######################################################### +### Install also Jupyter kernel spec +######################################################### + +# We cannot just add the installation of the kernel spec to data_files +# since the file is generated, not copied. +class sage_install(install): + def run(self): + install.run(self) + self.install_kernel_spec() + + def install_kernel_spec(self): + from sage.repl.ipython_kernel.install import SageKernelSpec + SageKernelSpec.update() + + ######################################################### ### Distutils ######################################################### @@ -648,6 +664,5 @@ def run_cythonize(): packages = python_packages, data_files = python_data_files, scripts = [], - cmdclass = { 'build_ext': sage_build_ext }, + cmdclass = dict(build_ext=sage_build_ext, install=sage_install), ext_modules = ext_modules) -