diff --git a/.gitignore b/.gitignore index 210c9bf780a..cc0faa6dd04 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,6 @@ /config.log /config.status /configure -/build/Makefile-auto -/build/Makefile-auto.in ################### # Temporary Files # diff --git a/Makefile b/Makefile index 93efbf405b5..1024330110f 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,12 @@ default: all build: all-build -# Defer unknown targets to build/Makefile +# Defer unknown targets to build/make/Makefile %:: $(MAKE) configure logs - +cd build && ./pipestatus \ + +cd build/make && ./pipestatus \ "./install '$@' 2>&1" \ - "tee -a ../logs/install.log" + "tee -a ../../logs/install.log" logs: mkdir -p $@ @@ -42,7 +42,7 @@ misc-clean: rm -rf tmp rm -f aclocal.m4 config.log config.status confcache rm -rf autom4te.cache - rm -f build/Makefile build/Makefile-auto + rm -f build/make/Makefile build/make/Makefile-auto rm -f .BUILDSTART bdist-clean: clean @@ -56,7 +56,7 @@ distclean: build-clean # Delete all auto-generated files which are distributed as part of the # source tarball bootstrap-clean: - rm -rf config configure build/Makefile-auto.in + rm -rf config configure build/make/Makefile-auto.in # Remove absolutely everything which isn't part of the git repo maintainer-clean: distclean bootstrap-clean diff --git a/VERSION.txt b/VERSION.txt index 4da12faf295..c5de210ce0a 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.8.beta8, released 2015-07-10 +Sage version 6.9.beta0, released 2015-07-29 diff --git a/bootstrap b/bootstrap index 5dd961dff82..290059d0a06 100755 --- a/bootstrap +++ b/bootstrap @@ -27,7 +27,7 @@ CONFVERSION=`cat $PKG/package-version.txt` bootstrap () { aclocal -I m4 && \ - automake --add-missing --copy build/Makefile-auto && \ + automake --add-missing --copy build/make/Makefile-auto && \ autoconf st=$? @@ -75,7 +75,7 @@ save () { # Create configure tarball echo "Creating $CONFBALL..." mkdir -p upstream - tar zcf "$CONFBALL" configure config/* build/Makefile-auto.in + tar zcf "$CONFBALL" configure config/* build/make/Makefile-auto.in # Update version number echo "$CONFVERSION" >$PKG/package-version.txt diff --git a/build/.gitignore b/build/.gitignore index 5fc607b9e2f..d7cf999bf8f 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1 +1,2 @@ -/Makefile +/.tox +/MANIFEST diff --git a/build/bin/sage-download-file b/build/bin/sage-download-file new file mode 100755 index 00000000000..24750aa5e8f --- /dev/null +++ b/build/bin/sage-download-file @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# USAGE: +# +# sage-download-file --print-fastest-mirror +# +# Print out the fastest mirror. All further arguments are ignored in +# that case. +# +# sage-download-file [--quiet] url-or-tarball [destination] +# +# The single mandatory argument can be a http:// url or a tarball +# filename. In the latter case, the tarball is downloaded from the +# mirror network and its checksum is verified. +# +# If the destination is not specified: +# * a url will be downloaded and the content written to stdout +# * a tarball will be saved under {SAGE_DISTFILES} + +try: + import sage_bootstrap +except ImportError: + import os, sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + import sage_bootstrap + +from sage_bootstrap.cmdline import SageDownloadFileApplication +SageDownloadFileApplication().run() diff --git a/build/bin/sage-package b/build/bin/sage-package new file mode 100755 index 00000000000..a8d21612366 --- /dev/null +++ b/build/bin/sage-package @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Script to manage third-party tarballs. +# +# Usage: +# +# * Print the configuration +# +# $ sage-package config +# Configuration: +# * log = info +# * interactive = True +# +# * Print a list of all available packages +# +# $ sage-package list | sort +# 4ti2 +# arb +# atlas +# autotools +# [...] +# zn_poly +# +# * Find the package name given a tarball filename +# +# $ sage-package name pari-2.8-1564-gdeac36e.tar.gz +# pari +# +# * Find the tarball filename given a package name +# +# $ sage-package tarball pari +# pari-2.8-1564-gdeac36e.tar.gz + +try: + import sage_bootstrap +except ImportError: + import os, sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + import sage_bootstrap + +from sage_bootstrap.cmdline import SagePkgApplication +SagePkgApplication().run() diff --git a/src/bin/sage-spkg b/build/bin/sage-spkg similarity index 99% rename from src/bin/sage-spkg rename to build/bin/sage-spkg index 648c5951361..f4b085b515d 100755 --- a/src/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -106,10 +106,10 @@ fi # The following sets environment variables for building packages. # Since this is sourced, it returns a non-zero value on errors rather # than exiting. Using dot suggested by W. Cheung. -. "${0%spkg}env" +. sage-env if [ $? -ne 0 ]; then - echo >&2 "Error setting environment variables by sourcing '$SAGE_ROOT/spkg/bin/sage-env';" + echo >&2 "Error setting environment variables by sourcing sage-env." echo >&2 "possibly contact sage-devel (see http://groups.google.com/group/sage-devel)." exit 1 fi @@ -320,7 +320,7 @@ if [ ! -f "$PKG_SRC" ]; then else echo "Attempting to get on-line info for package $PKG_NAME" fi - + # Reduce everything to case 4: full URL. if [ -n "$PKG_HAS_PATH" ]; then PKG_URL="$PKG_SRC" @@ -397,7 +397,7 @@ if [ ! -f "$PKG_SRC" ]; then echo >&2 "Error: could not find a package matching $PKG_NAME on $MIRROR" 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." @@ -407,7 +407,7 @@ if [ ! -f "$PKG_SRC" ]; then exit 0 fi fi - + # Trac #5852: check write permissions mkdir -p "$SAGE_DISTFILES" if [ ! -w "$SAGE_DISTFILES" ]; then @@ -415,7 +415,7 @@ if [ ! -f "$PKG_SRC" ]; then exit 1 fi cd "$SAGE_DISTFILES" || exit $? - + # Download to a temporary file (such that we don't end up with a # corrupted .spkg file). PKG_TMP="${PKG_URL##*/}.tmp" @@ -426,7 +426,7 @@ if [ ! -f "$PKG_SRC" ]; then rm -f "$PKG_TMP" exit 1 fi - + PKG_SRC="`pwd`/${PKG_URL##*/}" mv -f "$PKG_TMP" "$PKG_SRC" fi diff --git a/build/make/.gitignore b/build/make/.gitignore new file mode 100644 index 00000000000..29883e5755b --- /dev/null +++ b/build/make/.gitignore @@ -0,0 +1,3 @@ +/Makefile +/Makefile-auto +/Makefile-auto.in diff --git a/build/Makefile-auto.am b/build/make/Makefile-auto.am similarity index 100% rename from build/Makefile-auto.am rename to build/make/Makefile-auto.am diff --git a/build/deps b/build/make/deps similarity index 90% rename from build/deps rename to build/make/deps index 9f197da4f20..771b666d574 100644 --- a/build/deps +++ b/build/make/deps @@ -1,11 +1,11 @@ ############################################################################### -# This file ($SAGE_ROOT/build/deps) will be copied into -# $SAGE_ROOT/build/Makefile by $SAGE_ROOT/build/install +# This file ($SAGE_ROOT/build/make/deps) will be copied into +# $SAGE_ROOT/build/make/Makefile by $SAGE_ROOT/build/make/install ############################################################################### # 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/pipestatus +PIPE = $(SAGE_ROOT)/build/make/pipestatus STARTED = $(SAGE_LOCAL)/etc/sage-started.txt # Tell make not to look for files with these names: @@ -46,7 +46,7 @@ all-sage: \ $(EXTCODE) \ $(SCRIPTS) -# TOOLCHAIN consists of dependencies determined by build/install, +# TOOLCHAIN consists of dependencies determined by build/make/install, # including for example the GCC package. toolchain: $(TOOLCHAIN) @@ -76,7 +76,7 @@ start: all-build # We make this depend on all standard packages because running # sage-starts runs sage-location, which should be run after installing # any package. -$(STARTED): $(STANDARD_PACKES) +$(STARTED): $(STANDARD_PACKAGES) "$(SAGE_LOCAL)/bin/sage-starts" @@ -87,8 +87,8 @@ $(STARTED): $(STANDARD_PACKES) ############################################################################### base: $(INST)/$(BZIP2) $(INST)/$(PATCH) $(INST)/$(PKGCONF) -$(INST)/prereq: ../configure - @cd ..; rm -f config.log; ln -s logs/pkgs/config.log config.log; \ +$(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),"; \ @@ -198,23 +198,23 @@ DOC_DEPENDENCIES = sagelib $(INST)/$(SPHINX) $(INST)/$(SAGENB) \ 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 ../.. && $(PIPE) "./sage --docbuild --no-pdf-links all html $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a 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 ../.. && $(PIPE) "./sage --docbuild --no-pdf-links --no-plot all html $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a 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 ../.. && $(PIPE) "./sage --docbuild --no-pdf-links all html -j $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a 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 ../.. && $(PIPE) "./sage --docbuild all pdf $(SAGE_DOCBUILD_OPTS) 2>&1" "tee -a logs/docpdf.log" doc-clean: cd "$(SAGE_SRC)/doc" && $(MAKE) clean diff --git a/build/install b/build/make/install similarity index 97% rename from build/install rename to build/make/install index f11b0802acf..13c99ec3601 100755 --- a/build/install +++ b/build/make/install @@ -4,8 +4,8 @@ # Set various environment variables ######################################################################## -# Assume current directory is SAGE_ROOT/build -SAGE_ROOT=`cd .. && pwd -P` +# Assume current directory is SAGE_ROOT/build/make +SAGE_ROOT=`cd ../.. && pwd -P` SAGE_SRC="$SAGE_ROOT/src" SAGE_LOCAL="$SAGE_ROOT/local" SAGE_SHARE="$SAGE_LOCAL/share" @@ -18,7 +18,7 @@ if [ -z "${SAGE_ORIG_PATH_SET}" ]; then SAGE_ORIG_PATH=$PATH && export SAGE_ORIG_PATH SAGE_ORIG_PATH_SET=True && export SAGE_ORIG_PATH_SET fi -PATH="$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" +PATH="$SAGE_ROOT/build/bin:$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" PYTHONPATH="$SAGE_LOCAL" export SAGE_ROOT SAGE_SRC SAGE_LOCAL SAGE_EXTCODE SAGE_LOGS SAGE_SPKG_INST SAGE_VERSION PATH PYTHONPATH @@ -98,7 +98,7 @@ fi ############################################################################### # Determine various compilers. These variables should not be exported, -# they are only used in this build/install script to determine whether to +# 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. @@ -248,7 +248,7 @@ fi ############################################################################### -# Create $SAGE_ROOT/build/Makefile starting from build/deps +# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps ############################################################################### # Trac #15624: use file descriptor 5 since make uses 3 and 4 @@ -257,14 +257,14 @@ exec 5>Makefile cat >&5 <&5 "SHELL = `command -v bash`" echo >&5 @@ -411,18 +411,18 @@ for file in `find "$SAGE_SRC"/ext -type f`; do done echo >&5 -# Copy build/deps +# Copy build/make/deps cat >&5 <&5 -# Copy build/deps +cat "$SAGE_ROOT/build/make/deps" >&5 +# Copy build/make/deps cat >&5 <rand(21)-10.1); - B:=A-X*identity(k); - simplify(det(B)); --factor(x^4+1,exp(i*pi/4)); --factor(x^4+1,exp(i*pi/4)+1); -+regroup(factor(x^4+1,exp(i*pi/4))-'1/20736*(12*x+12*(-1/sqrt(2)+(i)/sqrt(2)))*(12*x+12*(-1/sqrt(2)-(i)/sqrt(2)))*(12*x+12*(1/sqrt(2)+(i)/sqrt(2)))*(12*x+12*(1/sqrt(2)-(i)/sqrt(2)))'); -+regroup((factor(x^4+1,exp(i*pi/4)+1))-'1/20736*(12*x+12*((i)/sqrt(2)-1/sqrt(2)))*(12*x+12*(-(i)/sqrt(2)-1/sqrt(2)))*(12*x+12*((i)/sqrt(2)+1/sqrt(2)))*(12*x+12*(-(i)/sqrt(2)+1/sqrt(2)))'); - A:=[[1,2],[3,4]]; - B:=approx(A); - inv(B); -diff -ruN check/TP12-sol.cas check.new/TP12-sol.cas ---- a/check/TP12-sol.cas 2012-07-23 13:19:37.000000000 +0200 -+++ b/check/TP12-sol.cas 2015-05-31 12:03:31.629446903 +0200 -@@ -105,7 +105,7 @@ - m; - end; - ordre(-1,2^1000); -- pari(); -+ pari():; - if (pari_znorder(Mod(5,11^5*2^40*101)) == ordre(5,11^5*2^40*101)) then - print("ca marche") else print("il y a une erreur") fi; - -diff -ruN check/TP12-sol.cas.out1 check.new/TP12-sol.cas.out1 ---- a/check/TP12-sol.cas.out1 2014-06-25 09:15:40.000000000 +0200 -+++ b/check/TP12-sol.cas.out1 2015-05-31 12:05:59.525449300 +0200 -@@ -126,10 +126,5 @@ - - end;, - 2, --"All PARI functions are now defined with the pari_ prefix. --PARI functions are also defined without prefix except: --% abs acos acosh apply arg asin asinh atan atanh binomial bitand bitor bitxor break ceil charpoly concat conj content cos cosh default divisors erfc eval exp factor factorial floor frac gcd global hilbert imag isprime kill lcm length local matrix max min next nextprime norm print real round select shift sign simplify sin sinh solve sqrt subst sum tan tanh taylor trace truncate type until valuation vector version write --When working with p-adic numbers use them in a pari() call --Type ?pari for short help --Inside xcas, try Help->Manuals->PARI for HTML help", -+"Done", - 0 diff --git a/build/pkgs/giac/spkg-src b/build/pkgs/giac/spkg-src index c790766a419..a57098d4ae2 100755 --- a/build/pkgs/giac/spkg-src +++ b/build/pkgs/giac/spkg-src @@ -10,30 +10,32 @@ if [ "$SAGE_LOCAL" = "" ]; then exit 1 fi - -# Sanity check: must be run from current directory -if ! [ -f spkg-src ]; then - echo >&2 "This script must be run from its own source directory!" - exit 1 -fi - # Exit on failure set -e -# +# TODO on the next update: l71 switch from gz to bz2 as wished in #18826 VERSION="1.2.0" -VERSIONREV="13" +VERSIONREV="19" # The upstream tarball name is: giac"$SOURCEORIG".tar.gz SOURCEORIG=_"$VERSION"-"$VERSIONREV" -# Build a temporary working dir. -mkdir giactmpbuildpkg -cd giactmpbuildpkg +# The name of the output file without tar.gz or tar.bz2 extension +OUTPUTFILEBASENAME="$SAGE_DISTFILES"/giac-"$VERSION"."$VERSIONREV" + +# Testing if the output file already exist in one of gz or bz2 format: +if [ -f "$OUTPUTFILEBASENAME".tar.gz -o -f "$OUTPUTFILEBASENAME".tar.bz2 ] ; then + echo >&2 "there is already a giac archive of this version" + exit 1 +fi +# Build a temporary working dir. (subdir of SAGE_DISTFILES) +TARGET=$(mktemp -d -p"$SAGE_DISTFILES") +ORIGDIR=`pwd` +cd "$TARGET" # Downloading upstream source -wget "http://www-fourier.ujf-grenoble.fr/~parisse/debian/dists/stable/main/source/giac$SOURCEORIG.tar.gz" +sage-download-file "http://www-fourier.ujf-grenoble.fr/~parisse/debian/dists/stable/main/source/giac$SOURCEORIG.tar.gz" "giac$SOURCEORIG.tar.gz" # untar upstream source @@ -44,7 +46,7 @@ tar -xzf giac"$SOURCEORIG".tar.gz mv giac-"$VERSION" src # removing french html doc, but keep keywords, and working makefiles. -# NB: the french html doc is huge and not GPL. +# NB: the french html doc is huge and not GPL. # it is freely redistibuable only for non commercial purposes. cd src/doc/fr rm -rf [^Mkx]* @@ -54,19 +56,26 @@ echo -e "EXTRA_DIST = xcasmenu xcasex keywords\n\nlocaldocdir = \$(docdir)/fr \n dist_localdoc_DATA = xcasmenu xcasex keywords html_mall html_mtt html_vall">Makefile.am # copy and adjust a minimal Makefile.in cp ../local/Makefile.in ./ -sed -ie 's|localdocdir = $(docdir)/local|localdocdir = $(docdir)/fr|' Makefile.in +sed -ie 's|localdocdir = $(docdir)/local|localdocdir = $(docdir)/fr|' Makefile.in sed -ie 's|doc/local|subdir = doc/fr|g' Makefile.in # touch html_mall touch html_mtt touch html_vall -# +# # building giac source tarball for the spkg cd ../../../ -tar -cz src -f ../giac-"$VERSION"."$VERSIONREV".tar.gz +tar -cz src -f "$OUTPUTFILEBASENAME".tar.gz +# On the next update don't forget to change to bz2 as wished in #18826 +#tar -cj src -f -f "$OUTPUTFILEBASENAME".tar.bz2 + + # cleaning extracted dir. cd .. -rm -rf giactmpbuildpkg +rm -rf "$TARGET" + +# going back to starting dir +cd "$ORIGDIR" diff --git a/build/pkgs/giacpy/checksums.ini b/build/pkgs/giacpy/checksums.ini index c47b777673f..0d5300e00c8 100644 --- a/build/pkgs/giacpy/checksums.ini +++ b/build/pkgs/giacpy/checksums.ini @@ -1,4 +1,4 @@ tarball=giacpy-VERSION.tar.gz -sha1=fec4f61a221ba6ed06023bf3b28e26e359d9cae3 -md5=2b95a4c2f7c53bb56851e2a2a094a6bd -cksum=4192057913 +sha1=a27b4684c3e456246a04d44c12461ce9bd157763 +md5=8cd131d34313671848f3f8294b72418e +cksum=1582753592 diff --git a/build/pkgs/giacpy/dependencies b/build/pkgs/giacpy/dependencies new file mode 100644 index 00000000000..cd887f96e86 --- /dev/null +++ b/build/pkgs/giacpy/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(CYTHON) $(INST)/$(GIAC) + +---------- +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/giacpy/package-version.txt b/build/pkgs/giacpy/package-version.txt index cb498ab2c89..4b9fcbec101 100644 --- a/build/pkgs/giacpy/package-version.txt +++ b/build/pkgs/giacpy/package-version.txt @@ -1 +1 @@ -0.4.8 +0.5.1 diff --git a/build/pkgs/git/dependencies b/build/pkgs/git/dependencies index a57776e9c71..d113fb51f29 100644 --- a/build/pkgs/git/dependencies +++ b/build/pkgs/git/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ZLIB) $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/givaro/dependencies b/build/pkgs/givaro/dependencies index 6ded91e70ee..7d1ec46a294 100644 --- a/build/pkgs/givaro/dependencies +++ b/build/pkgs/givaro/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/glpk/dependencies b/build/pkgs/glpk/dependencies index 3f1f9b6007e..050ba4a7af5 100644 --- a/build/pkgs/glpk/dependencies +++ b/build/pkgs/glpk/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(ZLIB) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/gsl/dependencies b/build/pkgs/gsl/dependencies index a1bd1c730ba..63c337af036 100644 --- a/build/pkgs/gsl/dependencies +++ b/build/pkgs/gsl/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ATLAS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/iml/dependencies b/build/pkgs/iml/dependencies index caee05d7137..4b4eee97eb7 100644 --- a/build/pkgs/iml/dependencies +++ b/build/pkgs/iml/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(GSL) $(INST)/$(ATLAS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ipython/dependencies b/build/pkgs/ipython/dependencies index b03a0fc4095..1a644804181 100644 --- a/build/pkgs/ipython/dependencies +++ b/build/pkgs/ipython/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) $(INST)/$(PYZMQ) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/jinja2/dependencies b/build/pkgs/jinja2/dependencies index a82c3c71cc9..77714d907b2 100644 --- a/build/pkgs/jinja2/dependencies +++ b/build/pkgs/jinja2/dependencies @@ -2,4 +2,4 @@ $(INST)/$(MARKUPSAFE) $(INST)/$(SETUPTOOLS) $(INST)/$(DOCUTILS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/jmol/dependencies b/build/pkgs/jmol/dependencies index d580ad91f68..50320851772 100644 --- a/build/pkgs/jmol/dependencies +++ b/build/pkgs/jmol/dependencies @@ -2,4 +2,4 @@ ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/jsonschema/dependencies b/build/pkgs/jsonschema/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/jsonschema/dependencies +++ b/build/pkgs/jsonschema/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/latte_int/spkg-install b/build/pkgs/latte_int/spkg-install index 2fc9718d9d0..d325e1124cc 100755 --- a/build/pkgs/latte_int/spkg-install +++ b/build/pkgs/latte_int/spkg-install @@ -13,6 +13,8 @@ cd "latte-int-${LATTE_VERSION}" #CFLAGS="-I $SAGE_LOCAL/include -L$SAGE_LOCAL/lib $CFLAGS" #export CFLAGS +CXXFLAGS="-DNTL_STD_CXX $CXXFLAGS" +export CXXFLAGS ./configure --prefix=$SAGE_LOCAL --enable-shared=yes --enable-static=false --with-gmp=$SAGE_LOCAL --with-ntl=$SAGE_LOCAL --with-cddlib=$SAGE_LOCAL --with-4ti2=$SAGE_LOCAL --with-lidia=$SAGE_LOCAL $MAKE $MAKE install diff --git a/build/pkgs/lcalc/dependencies b/build/pkgs/lcalc/dependencies index d9b110c648a..8923cc4ef61 100644 --- a/build/pkgs/lcalc/dependencies +++ b/build/pkgs/lcalc/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PARI) $(INST)/$(MPFR) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libfplll/dependencies b/build/pkgs/libfplll/dependencies index 9cfe5efb280..e788b6103c0 100644 --- a/build/pkgs/libfplll/dependencies +++ b/build/pkgs/libfplll/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(MPFR) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libgap/dependencies b/build/pkgs/libgap/dependencies index 74937cb57a4..4025bb41da1 100644 --- a/build/pkgs/libgap/dependencies +++ b/build/pkgs/libgap/dependencies @@ -2,4 +2,4 @@ $(INST)/$(GAP) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libgd/dependencies b/build/pkgs/libgd/dependencies index 2b534ecbbb6..562cc85a16e 100644 --- a/build/pkgs/libgd/dependencies +++ b/build/pkgs/libgd/dependencies @@ -2,4 +2,4 @@ $(INST)/$(LIBPNG) $(INST)/$(FREETYPE) $(INST)/$(ICONV) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libpng/dependencies b/build/pkgs/libpng/dependencies index 1b16c1ab392..b83cee0ff49 100644 --- a/build/pkgs/libpng/dependencies +++ b/build/pkgs/libpng/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ZLIB) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/linbox/dependencies b/build/pkgs/linbox/dependencies index 819f64ee1ef..875ee42d4de 100644 --- a/build/pkgs/linbox/dependencies +++ b/build/pkgs/linbox/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(NTL) $(INST)/$(GIVARO) $(INST)/$(MPFR) $(IN ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/m4ri/dependencies b/build/pkgs/m4ri/dependencies index b41f5876a0c..b9c15a418b1 100644 --- a/build/pkgs/m4ri/dependencies +++ b/build/pkgs/m4ri/dependencies @@ -2,4 +2,4 @@ $(INST)/$(LIBPNG) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/m4rie/dependencies b/build/pkgs/m4rie/dependencies index ce2651f580e..b1949652f69 100644 --- a/build/pkgs/m4rie/dependencies +++ b/build/pkgs/m4rie/dependencies @@ -2,4 +2,4 @@ $(INST)/$(M4RI) $(INST)/$(GIVARO) $(INST)/$(NTL) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/markupsafe/dependencies b/build/pkgs/markupsafe/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/markupsafe/dependencies +++ b/build/pkgs/markupsafe/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/matplotlib/dependencies b/build/pkgs/matplotlib/dependencies index d9e9b15a544..158406ed84b 100644 --- a/build/pkgs/matplotlib/dependencies +++ b/build/pkgs/matplotlib/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(NUMPY) $(INST)/$(FREETYPE) $(INST)/$(LIBPNG) $(INST) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/maxima/dependencies b/build/pkgs/maxima/dependencies index 609b828f61f..93a0916f715 100644 --- a/build/pkgs/maxima/dependencies +++ b/build/pkgs/maxima/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ECL) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/mistune/dependencies b/build/pkgs/mistune/dependencies index 7b6ca5345ac..5e36acca3f8 100644 --- a/build/pkgs/mistune/dependencies +++ b/build/pkgs/mistune/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SETUPTOOLS) $(INST)/$(CYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/mpc/dependencies b/build/pkgs/mpc/dependencies index 9cfe5efb280..e788b6103c0 100644 --- a/build/pkgs/mpc/dependencies +++ b/build/pkgs/mpc/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(MPFR) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/mpfi/dependencies b/build/pkgs/mpfi/dependencies index 9cfe5efb280..e788b6103c0 100644 --- a/build/pkgs/mpfi/dependencies +++ b/build/pkgs/mpfi/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(MPFR) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/mpfr/dependencies b/build/pkgs/mpfr/dependencies index 6ded91e70ee..7d1ec46a294 100644 --- a/build/pkgs/mpfr/dependencies +++ b/build/pkgs/mpfr/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/mpmath/dependencies b/build/pkgs/mpmath/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/mpmath/dependencies +++ b/build/pkgs/mpmath/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/networkx/dependencies b/build/pkgs/networkx/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/networkx/dependencies +++ b/build/pkgs/networkx/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ntl/checksums.ini b/build/pkgs/ntl/checksums.ini index 8e7eb1410a3..635ab261fdc 100644 --- a/build/pkgs/ntl/checksums.ini +++ b/build/pkgs/ntl/checksums.ini @@ -1,4 +1,4 @@ tarball=ntl-VERSION.tar.bz2 -sha1=d4bac931615602ee886168ce0017b2d3b139a2b5 -md5=ad5dec8eeab05f7675cdd985322c0182 -cksum=3622064974 +sha1=0091c590c7003247e140adc08411055b571be0a4 +md5=a93f1f50447d62436545a349aefd2d33 +cksum=1733127244 diff --git a/build/pkgs/ntl/dependencies b/build/pkgs/ntl/dependencies index 90ebb52df11..f6009a9c906 100644 --- a/build/pkgs/ntl/dependencies +++ b/build/pkgs/ntl/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(GF2X) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ntl/package-version.txt b/build/pkgs/ntl/package-version.txt index b1dc079ac4d..982edbb7f57 100644 --- a/build/pkgs/ntl/package-version.txt +++ b/build/pkgs/ntl/package-version.txt @@ -1 +1 @@ -9.2.0.p0 +9.3.0.p0 diff --git a/build/pkgs/ntl/patches/errorcallback.patch b/build/pkgs/ntl/patches/errorcallback.patch deleted file mode 100644 index 55cde0ef012..00000000000 --- a/build/pkgs/ntl/patches/errorcallback.patch +++ /dev/null @@ -1,67 +0,0 @@ -Modify tools.c and tools.h to provide an error callback framework -(SetErrorCallbackFunction). - -NTL provides its own error callback framework, but it's not suitable -for Sage since we don't want to see the error message printed, we want -the error message to be part of the callback function. ---- ntl.orig/src/tools.c 2014-08-27 05:51:42.000000000 +1200 -+++ ntl/src/tools.c 2014-10-09 22:02:59.679703796 +1300 -@@ -18,8 +18,35 @@ - NTL_THREAD_LOCAL void (*ErrorCallback)() = 0; - - -+/* -+ The following code differs from vanilla NTL. -+ -+ We add a SetErrorCallbackFunction(). This sets a global callback function _function_, -+ which gets called with parameter _context_ and an error message string whenever Error() -+ gets called. -+ -+ Note that if the custom error handler *returns*, then NTL will dump the error message -+ back to stderr and abort() as it habitually does. -+ -+ -- David Harvey (2008-04-12) -+*/ -+ -+void (*ErrorCallbackFunction)(const char*, void*) = NULL; -+void *ErrorCallbackContext = NULL; -+ -+ -+void SetErrorCallbackFunction(void (*function)(const char*, void*), void *context) -+{ -+ ErrorCallbackFunction = function; -+ ErrorCallbackContext = context; -+} -+ -+ - void TerminalError(const char *s) - { -+ if (ErrorCallbackFunction != NULL) -+ ErrorCallbackFunction(s, ErrorCallbackContext); -+ - cerr << s << "\n"; - _ntl_abort(); - } ---- ntl.orig/include/NTL/tools.h 2014-08-27 05:51:43.000000000 +1200 -+++ ntl/include/NTL/tools.h 2014-10-09 22:02:59.679703796 +1300 -@@ -10,6 +10,7 @@ - - #include - #include -+#include - - - -@@ -487,6 +488,12 @@ - - - NTL_THREAD_LOCAL extern void (*ErrorCallback)(); -+ -+/* -+ This function is not present in vanilla NTL. -+ See tools.c for documentation. -+ */ -+void SetErrorCallbackFunction(void (*func)(const char *s, void *context), void *context); - - void TerminalError(const char *s); - diff --git a/build/pkgs/ntl/patches/sp_arith.patch b/build/pkgs/ntl/patches/sp_arith.patch deleted file mode 100644 index 852583f3667..00000000000 --- a/build/pkgs/ntl/patches/sp_arith.patch +++ /dev/null @@ -1,15 +0,0 @@ -Typo in sp_arith.h. - -Make build with NTL_LEGACY_SP_MULMOD=on. -diff -druN a/include/NTL/sp_arith.h b/include/NTL/sp_arith.h ---- a/include/NTL/sp_arith.h 2015-05-23 07:44:52.000000000 -0700 -+++ b/include/NTL/sp_arith.h 2015-07-07 03:57:28.254261864 -0700 -@@ -896,7 +896,7 @@ - - static inline long MulMod2(long a, long b, long n, wide_double bninv) - { -- return MulMod2_legacy(a, b, n, ninv); -+ return MulMod2_legacy(a, b, n, bninv); - } - - diff --git a/build/pkgs/ntl/spkg-src b/build/pkgs/ntl/spkg-src index 263833265f2..a18339df6d3 100755 --- a/build/pkgs/ntl/spkg-src +++ b/build/pkgs/ntl/spkg-src @@ -1,19 +1,25 @@ #!/usr/bin/env bash -NTL=ntl-6.2.1 +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "Error: SAGE_LOCAL undefined - exiting..." + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi -SPKG_ROOT=`pwd` +NTL=ntl-9.3.0 +SPKG_ROOT="$SAGE_ROOT/build/pkgs/ntl" set -e -shopt -s extglob + +cd "$SPKG_ROOT" # Remove old sources and download new rm -rf src mkdir src cd src -tar xzf <( curl -L "http://www.shoup.net/ntl/$NTL.tar.gz" ) +tar xzf <( sage-download-file "http://www.shoup.net/ntl/$NTL.tar.gz" ) if [ ! -d "$NTL" ]; then echo "$NTL directory not in tarball, aborting" @@ -40,3 +46,5 @@ rm -rf autom4te.cache # Make everything writable cd "$SPKG_ROOT" chmod -R u+w src +tar c src | bzip2 --best >"$SAGE_DISTFILES/$NTL.tar.bz2" +rm -rf src diff --git a/build/pkgs/numpy/dependencies b/build/pkgs/numpy/dependencies index 2f3d8d534d1..20134598370 100644 --- a/build/pkgs/numpy/dependencies +++ b/build/pkgs/numpy/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(ATLAS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/openssl/checksums.ini b/build/pkgs/openssl/checksums.ini index 5814a9699a1..fcb0b5c367e 100644 --- a/build/pkgs/openssl/checksums.ini +++ b/build/pkgs/openssl/checksums.ini @@ -1,4 +1,4 @@ tarball=openssl-VERSION.tar.gz -sha1=6e4a5e91159eb32383296c7c83ac0e59b83a0a44 -md5=8c8d81a9ae7005276e486702edbcd4b6 -cksum=1938030126 +sha1=d01d17b44663e8ffa6a33a5a30053779d9593c3d +md5=38dd619b2e77cbac69b99f52a053d25a +cksum=4293826663 diff --git a/build/pkgs/openssl/package-version.txt b/build/pkgs/openssl/package-version.txt index 4574bd49817..80cc84cf3e1 100644 --- a/build/pkgs/openssl/package-version.txt +++ b/build/pkgs/openssl/package-version.txt @@ -1 +1 @@ -1.0.2c +1.0.2d diff --git a/build/pkgs/pari/dependencies b/build/pkgs/pari/dependencies index 9d788d49909..a4df15b4879 100644 --- a/build/pkgs/pari/dependencies +++ b/build/pkgs/pari/dependencies @@ -2,4 +2,4 @@ $(INST)/$(READLINE) $(INST)/$(SAGE_MP_LIBRARY) | $(INST)/$(PARI_GALDATA) $(INST) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 45d3c5637ea..8db184f072c 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-1637-g489005a.p0 +2.8-1637-g489005a.p1 diff --git a/build/pkgs/pari/patches/perl_regex.patch b/build/pkgs/pari/patches/perl_regex.patch new file mode 100644 index 00000000000..038f4d604e8 --- /dev/null +++ b/build/pkgs/pari/patches/perl_regex.patch @@ -0,0 +1,200 @@ +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/pexpect/dependencies b/build/pkgs/pexpect/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/pexpect/dependencies +++ b/build/pkgs/pexpect/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pillow/dependencies b/build/pkgs/pillow/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/pillow/dependencies +++ b/build/pkgs/pillow/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pip/dependencies b/build/pkgs/pip/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/pip/dependencies +++ b/build/pkgs/pip/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pkgconf/dependencies b/build/pkgs/pkgconf/dependencies index 5bea52c49d2..fc0ee6c6a03 100644 --- a/build/pkgs/pkgconf/dependencies +++ b/build/pkgs/pkgconf/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PATCH) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pkgconfig/dependencies b/build/pkgs/pkgconfig/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/pkgconfig/dependencies +++ b/build/pkgs/pkgconfig/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/polybori/dependencies b/build/pkgs/polybori/dependencies index f014f5fb97e..0b47764f31e 100644 --- a/build/pkgs/polybori/dependencies +++ b/build/pkgs/polybori/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(IPYTHON) $(INST)/$(SCONS) $(INST)/$(BOOST_CROPPED) $ ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ppl/dependencies b/build/pkgs/ppl/dependencies index ca623407f20..7fe73ce6a85 100644 --- a/build/pkgs/ppl/dependencies +++ b/build/pkgs/ppl/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(GLPK) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pycrypto/dependencies b/build/pkgs/pycrypto/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/pycrypto/dependencies +++ b/build/pkgs/pycrypto/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pygments/dependencies b/build/pkgs/pygments/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/pygments/dependencies +++ b/build/pkgs/pygments/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pynac/checksums.ini b/build/pkgs/pynac/checksums.ini index dffd4fefeb7..efc93ed0ab4 100644 --- a/build/pkgs/pynac/checksums.ini +++ b/build/pkgs/pynac/checksums.ini @@ -1,4 +1,4 @@ tarball=pynac-VERSION.tar.bz2 -sha1=3ba61f2d7d57d519488a15e1dd4aa367a991dae4 -md5=a2d7b4acdc5a0266e030467ab3b4cb83 -cksum=4256371947 +sha1=ef031834c14780d71c7d53b739e32559a0fa0434 +md5=615c0ee928ef4f9e25caaba485559929 +cksum=3373265562 diff --git a/build/pkgs/pynac/dependencies b/build/pkgs/pynac/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/pynac/dependencies +++ b/build/pkgs/pynac/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pynac/package-version.txt b/build/pkgs/pynac/package-version.txt index da7b1744778..8ef1e6f217f 100644 --- a/build/pkgs/pynac/package-version.txt +++ b/build/pkgs/pynac/package-version.txt @@ -1 +1 @@ -0.3.9.1 +0.3.9.2 diff --git a/build/pkgs/pyparsing/dependencies b/build/pkgs/pyparsing/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/pyparsing/dependencies +++ b/build/pkgs/pyparsing/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/python2/dependencies b/build/pkgs/python2/dependencies index 75c33d39880..38b48a3c93e 100644 --- a/build/pkgs/python2/dependencies +++ b/build/pkgs/python2/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ZLIB) $(INST)/$(READLINE) $(INST)/$(SQLITE) $(INST)/$(LIBPNG) | $(INST ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/python3/dependencies b/build/pkgs/python3/dependencies index 75c33d39880..38b48a3c93e 100644 --- a/build/pkgs/python3/dependencies +++ b/build/pkgs/python3/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ZLIB) $(INST)/$(READLINE) $(INST)/$(SQLITE) $(INST)/$(LIBPNG) | $(INST ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pyzmq/dependencies b/build/pkgs/pyzmq/dependencies index 7a38ed0fc00..0e4c6ddccd9 100644 --- a/build/pkgs/pyzmq/dependencies +++ b/build/pkgs/pyzmq/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(ZEROMQ) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/r/dependencies b/build/pkgs/r/dependencies index 817ce3bcd7b..53de075c536 100644 --- a/build/pkgs/r/dependencies +++ b/build/pkgs/r/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ATLAS) $(INST)/$(ICONV) $(INST)/$(READLINE) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ratpoints/dependencies b/build/pkgs/ratpoints/dependencies index 6ded91e70ee..7d1ec46a294 100644 --- a/build/pkgs/ratpoints/dependencies +++ b/build/pkgs/ratpoints/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/readline/dependencies b/build/pkgs/readline/dependencies index 600b6eff414..1c073b506d3 100644 --- a/build/pkgs/readline/dependencies +++ b/build/pkgs/readline/dependencies @@ -2,4 +2,4 @@ $(INST)/$(NCURSES) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/rpy2/dependencies b/build/pkgs/rpy2/dependencies index 81d605d6db9..f72074737bb 100644 --- a/build/pkgs/rpy2/dependencies +++ b/build/pkgs/rpy2/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(R) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sagenb/dependencies b/build/pkgs/sagenb/dependencies index 219f7c44d4d..36a01885101 100644 --- a/build/pkgs/sagenb/dependencies +++ b/build/pkgs/sagenb/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(PEXPECT) $(INST)/$(JINJA2) $(I ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sagetex/dependencies b/build/pkgs/sagetex/dependencies index b3f8e41abbb..1a3861d162b 100644 --- a/build/pkgs/sagetex/dependencies +++ b/build/pkgs/sagetex/dependencies @@ -5,4 +5,4 @@ SageTeX, you actually need to run Sage, produce plots,... ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/scipy/dependencies b/build/pkgs/scipy/dependencies index 8a179735f3a..0f1b4db2c02 100644 --- a/build/pkgs/scipy/dependencies +++ b/build/pkgs/scipy/dependencies @@ -2,4 +2,4 @@ $(INST)/$(ATLAS) $(INST)/$(NUMPY) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/scons/dependencies b/build/pkgs/scons/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/scons/dependencies +++ b/build/pkgs/scons/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/setuptools/dependencies b/build/pkgs/setuptools/dependencies index 80dd6946a6b..edf27112135 100644 --- a/build/pkgs/setuptools/dependencies +++ b/build/pkgs/setuptools/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/singular/dependencies b/build/pkgs/singular/dependencies index f3bf4130e28..7001dd5d162 100644 --- a/build/pkgs/singular/dependencies +++ b/build/pkgs/singular/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(NTL) $(INST)/$(FLINT) $(INST)/$(READLINE) $ ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/singular/patches/singular-3.1.7-use_cxx_for_linking.patch b/build/pkgs/singular/patches/singular-3.1.7-use_cxx_for_linking.patch new file mode 100644 index 00000000000..132d418bc1e --- /dev/null +++ b/build/pkgs/singular/patches/singular-3.1.7-use_cxx_for_linking.patch @@ -0,0 +1,73 @@ +Trac #18892: Singular fails to build on Ubuntu 15.04 32-bit + +Singular fails to build on Ubuntu 15.04 32-bit due to a missing symbol related to the stack protector + +Patch taken from https://raw.githubusercontent.com/cschwan/sage-on-gentoo/master/sci-mathematics/singular/files/singular-3.1.7-use_cxx_for_linking.patch + + +diff -Naur Singular-3-1-7.orig/Singular/Makefile.in Singular-3-1-7/Singular/Makefile.in +--- Singular-3-1-7.orig/Singular/Makefile.in 2014-11-20 02:06:05.000000000 +1300 ++++ Singular-3-1-7/Singular/Makefile.in 2015-04-30 11:55:25.285611669 +1200 +@@ -67,7 +67,7 @@ + ## + @SET_MAKE@ + CC = @CC@ +-LD = @LD@ ++LD = @CXX@ + CXX = @CXX@ + LEX = sh flexer.sh + +diff -Naur Singular-3-1-7.orig/dyn_modules/modgen/Makefile.in Singular-3-1-7/dyn_modules/modgen/Makefile.in +--- Singular-3-1-7.orig/dyn_modules/modgen/Makefile.in 2014-11-20 02:06:05.000000000 +1300 ++++ Singular-3-1-7/dyn_modules/modgen/Makefile.in 2015-04-30 11:55:25.285611669 +1200 +@@ -20,7 +20,7 @@ + ## + @SET_MAKE@ + CC = @CC@ +-LD = @LD@ ++LD = @CXX@ + CXX = @CXX@ + LEX = sh ../../Singular/flexer.sh + PERL = @PERL@ +diff -Naur Singular-3-1-7.orig/kernel/Makefile.in Singular-3-1-7/kernel/Makefile.in +--- Singular-3-1-7.orig/kernel/Makefile.in 2014-11-20 02:06:05.000000000 +1300 ++++ Singular-3-1-7/kernel/Makefile.in 2015-04-30 11:55:25.285611669 +1200 +@@ -30,7 +30,7 @@ + ## + @SET_MAKE@ + CC = @CC@ +-LD = @LD@ ++LD = @CXX@ + CXX = @CXX@ + LEX = @LEX@ + PERL = @PERL@ +diff -Naur Singular-3-1-7.orig/Singular/configure Singular-3-1-7/Singular/configure +--- Singular-3-1-7.orig/Singular/configure 2015-07-15 10:18:31.000000000 +1200 ++++ Singular-3-1-7/Singular/configure 2015-07-15 10:41:16.000000000 +1200 +@@ -6876,7 +6876,7 @@ + LD_DYN_FLAGS1="-dynamic" + LD_DYN_FLAGS2="-ldl" + SFLAGS="-fpic -DPIC" +- SLDFLAGS="-dynamic -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" ++ SLDFLAGS="-dynamiclib -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" + if test "${LD+set}" != set; then + LD=libtool + fi +@@ -6933,7 +6933,7 @@ + LD_DYN_FLAGS1="-dynamic" + LD_DYN_FLAGS2="-ldl" + SFLAGS="-fpic -DPIC" +- SLDFLAGS="-dynamic -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" ++ SLDFLAGS="-dynamiclib -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" + if test "${LD+set}" != set; then + LD=libtool + fi +@@ -6990,7 +6990,7 @@ + LD_DYN_FLAGS1="-dynamic" + LD_DYN_FLAGS2="-ldl" + SFLAGS="-fpic -DPIC" +- SLDFLAGS="-dynamic -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" ++ SLDFLAGS="-dynamiclib -twolevel_namespace -weak_reference_mismatches weak -undefined dynamic_lookup" + if test "${LD+set}" != set; then + LD=libtool + fi diff --git a/build/pkgs/six/dependencies b/build/pkgs/six/dependencies index d4017dcb044..643eeddd35b 100644 --- a/build/pkgs/six/dependencies +++ b/build/pkgs/six/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sphinx/dependencies b/build/pkgs/sphinx/dependencies index 9abd9e61288..865dd3451cc 100644 --- a/build/pkgs/sphinx/dependencies +++ b/build/pkgs/sphinx/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(DOCUTILS) $(INST)/$(JINJA2) $( ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sqlite/dependencies b/build/pkgs/sqlite/dependencies index cb56e6568b4..69a20909349 100644 --- a/build/pkgs/sqlite/dependencies +++ b/build/pkgs/sqlite/dependencies @@ -2,4 +2,4 @@ $(INST)/$(READLINE) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sympy/dependencies b/build/pkgs/sympy/dependencies index fa70e3014d0..d8bb970bcf7 100644 --- a/build/pkgs/sympy/dependencies +++ b/build/pkgs/sympy/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(MPMATH) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/tachyon/dependencies b/build/pkgs/tachyon/dependencies index b41f5876a0c..b9c15a418b1 100644 --- a/build/pkgs/tachyon/dependencies +++ b/build/pkgs/tachyon/dependencies @@ -2,4 +2,4 @@ $(INST)/$(LIBPNG) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/tides/dependencies b/build/pkgs/tides/dependencies index 9cfe5efb280..e788b6103c0 100644 --- a/build/pkgs/tides/dependencies +++ b/build/pkgs/tides/dependencies @@ -2,4 +2,4 @@ $(INST)/$(SAGE_MP_LIBRARY) $(INST)/$(MPFR) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/tornado/dependencies b/build/pkgs/tornado/dependencies index deb1bd85eb3..3e87ffe857c 100644 --- a/build/pkgs/tornado/dependencies +++ b/build/pkgs/tornado/dependencies @@ -2,4 +2,4 @@ $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(BACKPORTS_SSL_MATCH_HOSTNAME) ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zn_poly/dependencies b/build/pkgs/zn_poly/dependencies index 2c057a4ac63..253888d3306 100644 --- a/build/pkgs/zn_poly/dependencies +++ b/build/pkgs/zn_poly/dependencies @@ -5,4 +5,4 @@ The 'configure' script in zn_poly calls Python to make a 'makefile'. ---------- All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/sage_bootstrap/README b/build/sage_bootstrap/README new file mode 100644 index 00000000000..c3c7e196c4a --- /dev/null +++ b/build/sage_bootstrap/README @@ -0,0 +1,24 @@ +Sage-Bootstrap + +This is a utility libary for dealing with third-party tarballs and +building Sage. You should never import anything from the actual Sage +library here, nor should you import anything from sage_bootstrap into +Sage (because this would reconfigure logging). They must be kept +separate. + +Everything here must support Python 2.6, 2.7, and 3.3+. Use tox +(https://testrun.org/tox/latest/) to automatically run the tests with +all relevant Python versions. Tests are written as unittest, not as +doctests, because the library is not meant to be used interactively. +Note that the library comes with a setup.py file so that tox can test +it, but it is not meant to be installed to SAGE_LOCAL. + +Command-line utilities must be able to run as part of a pipe | filter +chain. So you have to be careful about what you send to stdout. You +should use: + +* print() for anything that is meant to be sent to the output pipe. + +* log.info() for human-readable messages about the normal program + flow. + diff --git a/build/sage_bootstrap/__init__.py b/build/sage_bootstrap/__init__.py new file mode 100644 index 00000000000..3e74d02db70 --- /dev/null +++ b/build/sage_bootstrap/__init__.py @@ -0,0 +1,9 @@ + +from sage_bootstrap.config import Configuration +config = Configuration() + +from sage_bootstrap.stdio import init_streams +init_streams(config) + +from sage_bootstrap.logger import init_logger +init_logger(config) diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py new file mode 100644 index 00000000000..ce3036decf4 --- /dev/null +++ b/build/sage_bootstrap/cmdline.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +""" +Commandline handling + +Note that argparse is not part of Python 2.6, so we cannot rely on it here. +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import os +import sys +from textwrap import dedent +import logging +log = logging.getLogger() + +from sage_bootstrap.env import SAGE_DISTFILES +from sage_bootstrap.package import Package +from sage_bootstrap.download import Download +from sage_bootstrap.mirror_list import MirrorList +from sage_bootstrap.tarball import Tarball + + +class CmdlineSubcommands(object): + + def __init__(self, argv=None): + if argv is None: + argv = sys.argv + if len(argv) == 1: + self.print_help() + sys.exit(0) + self.subcommand = argv[1] + self.extra_args = argv[2:] + + def print_help(self): + print(dedent(self.__doc__).lstrip()) + print('Usage:') + for method in sorted(dir(self)): + if not method.startswith('run_'): + continue + doc = dedent(getattr(self, method).__doc__).lstrip().splitlines() + print('') + print('* ' + doc[0]) + for line in doc[1:]: + print(' ' + line) + + def run(self): + try: + method = getattr(self, 'run_{0}'.format(self.subcommand)) + except AttributeError: + log.error('unknown subcommand: {0}'.format(self.subcommand)) + sys.exit(1) + try: + method(*self.extra_args) + except TypeError as err: + log.error('invalid arguments to the {0} subcommand: {1}' + .format(self.subcommand, self.extra_args)) + sys.exit(1) + + +class SagePkgApplication(CmdlineSubcommands): + """ + sage-package + -------- + + The package script is used to manage third-party tarballs. + """ + + def run_config(self): + """ + Print the configuration + + $ sage-package config + Configuration: + * log = info + * interactive = True + """ + from sage_bootstrap.config import Configuration + print(Configuration()) + + def run_list(self): + """ + Print a list of all available packages + + $ sage-package list | sort + 4ti2 + arb + atlas + autotools + [...] + zn_poly + """ + for pkg in Package.all(): + print(pkg.name) + + def run_name(self, tarball_filename): + """ + Find the package name given a tarball filename + + $ sage-package name pari-2.8-1564-gdeac36e.tar.gz + pari + """ + tarball = Tarball(os.path.basename(tarball_filename)) + print(tarball.package.name) + + def run_tarball(self, package_name): + """ + 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) + + +class SageDownloadFileApplication(object): + """ + USAGE: + + sage-download-file --print-fastest-mirror + + Print out the fastest mirror. All further arguments are ignored in + that case. + + sage-download-file [--quiet] url-or-tarball [destination] + + The single mandatory argument can be a http:// url or a tarball + filename. In the latter case, the tarball is downloaded from the + mirror network and its checksum is verified. + + If the destination is not specified: + * a url will be downloaded and the content written to stdout + * a tarball will be saved under {SAGE_DISTFILES} + """ + + def run(self): + progress = True + url = None + destination = None + for arg in sys.argv[1:]: + if arg.startswith('--print-fastest-mirror'): + print(MirrorList().fastest) + sys.exit(0) + if arg.startswith('--quiet'): + progress = False + continue + if url is None: + url = arg + continue + if destination is None: + destination = arg + continue + 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) + diff --git a/build/sage_bootstrap/compat.py b/build/sage_bootstrap/compat.py new file mode 100644 index 00000000000..e5c485a1bd7 --- /dev/null +++ b/build/sage_bootstrap/compat.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Python 2/3 compatibility utils +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +try: + # Python 3 + import urllib.request as urllib + import urllib.parse as urlparse +except ImportError: + import urllib + import urlparse + + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO diff --git a/build/sage_bootstrap/config.py b/build/sage_bootstrap/config.py new file mode 100644 index 00000000000..666411e28ff --- /dev/null +++ b/build/sage_bootstrap/config.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +""" +Set Up Logging + +Logging can be customized using the ``SAGE_BOOTSTRAP`` environment +variable. It is a comma-separated list of ``key:value`` pairs. They +are not case sensitive. Valid pairs are: + +* ``log:[level]``, where ``[level]`` is one of + + * ``debug`` + * ``info`` + * ``warning`` + * ``critical`` + * ``error`` + +* ``interactive:true`` or ``interactive:false``, to override isatty detection. +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import sys +import os +import logging + + +LOG_LEVELS = ( + 'debug', + 'info', + 'warning', + 'critical', + 'error' +) + + + + +class Configuration(object): + + _initialized = False + + log = 'info' + + interactive = os.isatty(sys.stdout.fileno()) + + def __init__(self): + if not Configuration._initialized: + Configuration._init_from_environ() + if self.log not in LOG_LEVELS: + raise ValueError('invalid log level: {0}'.format(self.log)) + assert isinstance(self.interactive, bool) + + @classmethod + def _init_from_environ(cls): + env = os.environ.get('SAGE_BOOTSTRAP', '').lower() + for pair in env.split(','): + if not pair.strip(): + continue + key, value = pair.split(':', 1) + key = key.strip() + value = value.strip() + if key == 'log': + cls.log = value + elif key == 'interactive': + if value == 'true': + cls.interactive = True + elif value == 'false': + cls.interactive = False + else: + raise ValueError('interactive value must be "true" or "false", got "{0}"' + .format(value)) + else: + raise ValueError('unknown key: "{0}"'.format(key)) + cls._initialized = True + + def __repr__(self): + return '\n'.join([ + 'Configuration:', + ' * log = {0}'.format(self.log), + ' * interactive = {0}'.format(self.interactive) + ]) diff --git a/build/sage_bootstrap/download.py b/build/sage_bootstrap/download.py new file mode 100644 index 00000000000..0ccd8d6f1d3 --- /dev/null +++ b/build/sage_bootstrap/download.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +Download files from the internet +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import sys +import logging +log = logging.getLogger() + +from sage_bootstrap.stdio import flush +from sage_bootstrap.compat import urllib + + +class ProgressBar(object): + """ + Progress bar as urllib reporthook + """ + + def __init__(self, stream, length=70): + self.length = length + self.progress = 0 + self.stream = stream + + def start(self): + flush() # make sure to not interleave stdout/stderr + self.stream.write('[') + self.stream.flush() + + def __call__(self, chunks_so_far, chunk_size, total_size): + if total_size == -1: # we do not know size + n = 0 if chunks_so_far == 0 else self.length // 2 + else: + n = chunks_so_far * chunk_size * self.length // total_size + if n > self.length: + # If there is a Content-Length, this will be sent as the last progress + return + # n ranges from 0 to length*total (exclude), so we'll print at most length dots + if n >= self.progress: + self.stream.write('.' * (n-self.progress)) + self.stream.flush() + self.progress = n + + def stop(self): + missing = '.' * (self.length - self.progress) + self.stream.write(missing + ']\n') + self.stream.flush() + + def error_stop(self): + missing = 'x' * (self.length - self.progress) + self.stream.write(missing + ']\n') + self.stream.flush() + + + + +class Download(object): + """ + Download URL + + Right now, only via HTTP + + This should work for FTP as well but, in fact, hangs on python < + 3.4, see http://bugs.python.org/issue16270 + + INPUT: + + - ``url`` -- string. The URL to download. + + - ``destination`` -- string or ``None`` (default). The destination + file name to save to. If not specified, the file is written to + stdout. + + - ``progress`` -- boolean (default: ``True``). Whether to print a + progress bar to stderr. For testing, this can also be a stream + to which the progress bar is being sent. + + - ``ignore_errors`` -- boolean (default: ``False``). Catch network + errors (a message is still being logged). + """ + + def __init__(self, url, destination=None, progress=True, ignore_errors=False): + self.url = url + self.destination = destination or '/dev/stdout' + self.progress = (progress is not False) + self.progress_stream = sys.stderr if isinstance(progress, bool) else progress + self.ignore_errors = ignore_errors + + def http_error_default(self, url, fp, errcode, errmsg, headers): + """ + Callback for the URLopener to raise an exception on HTTP errors + """ + fp.close() + raise IOError(errcode, errmsg, url) + + def start_progress_bar(self): + if self.progress: + self.progress_bar = ProgressBar(self.progress_stream) + self.progress_bar.start() + + def success_progress_bar(self): + if self.progress: + self.progress_bar.stop() + + def error_progress_bar(self): + if self.progress: + self.progress_bar.error_stop() + + def run(self): + opener = urllib.FancyURLopener() + opener.http_error_default = self.http_error_default + self.start_progress_bar() + try: + if self.progress: + filename, info = opener.retrieve( + self.url, self.destination, self.progress_bar) + else: + filename, info = opener.retrieve( + self.url, self.destination) + except IOError as err: + self.error_progress_bar() + log.error(err) + if not self.ignore_errors: + raise + self.success_progress_bar() diff --git a/build/sage_bootstrap/env.py b/build/sage_bootstrap/env.py new file mode 100644 index 00000000000..6e7ad570a19 --- /dev/null +++ b/build/sage_bootstrap/env.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +Environment Variables + +This module defines the following subset of the Sage environment +variables: + +* ``SAGE_ROOT`` +* ``SAGE_SRC`` +* ``SAGE_DISTFILES`` +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import os + + +try: + SAGE_ROOT = os.environ['SAGE_ROOT'] +except KeyError: + SAGE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + +SAGE_SRC = os.environ.get('SAGE_SRC', + os.path.join(SAGE_ROOT, 'src')) +SAGE_DISTFILES = os.environ.get('SAGE_DISTFILES', + os.path.join(SAGE_ROOT, 'upstream')) + + +assert os.path.isfile(os.path.join(SAGE_ROOT, 'configure.ac')), SAGE_ROOT + +try: + # SAGE_DISTFILES does not exist in a fresh git clone + os.mkdir(SAGE_DISTFILES) +except OSError: + pass + +assert os.path.isdir(SAGE_DISTFILES) diff --git a/build/sage_bootstrap/logger.py b/build/sage_bootstrap/logger.py new file mode 100644 index 00000000000..894aecefd8c --- /dev/null +++ b/build/sage_bootstrap/logger.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Set Up Logging + +When using a script interactively, logging should go to stdout and be +human-readable. When using a script as part of a pipe (usually +involving tee), logging should go to stderr. +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import sys +import os +import logging + +logger = logging.getLogger() + + +default_formatter = logging.Formatter( + '%(levelname)s [%(module)s|%(funcName)s:%(lineno)s]: %(message)s') + +plain_formatter = logging.Formatter('%(message)s') + + +class ExcludeInfoFilter(logging.Filter): + + def filter(self, record): + return record.levelno != logging.INFO + +class OnlyInfoFilter(logging.Filter): + + def filter(self, record): + return record.levelno == logging.INFO + + +def init_logger(config): + level = getattr(logging, config.log.upper()) + logger.setLevel(level) + ch_all = logging.StreamHandler(sys.stderr) + ch_all.setLevel(logging.DEBUG) + ch_all.setFormatter(default_formatter) + ch_all.addFilter(ExcludeInfoFilter()) + logger.addHandler(ch_all) + if config.interactive: + ch_info = logging.StreamHandler(sys.stdout) + else: + ch_info = logging.StreamHandler(sys.stderr) + ch_info.setLevel(logging.DEBUG) + ch_info.setFormatter(plain_formatter) + ch_info.addFilter(OnlyInfoFilter()) + logger.addHandler(ch_info) + diff --git a/build/sage_bootstrap/mirror_list.py b/build/sage_bootstrap/mirror_list.py new file mode 100644 index 00000000000..5d38dc02e7d --- /dev/null +++ b/build/sage_bootstrap/mirror_list.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +""" +Access the List of Sage Download Mirrors +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import os +import contextlib +import logging +log = logging.getLogger() + +from sage_bootstrap.compat import urllib, urlparse +from sage_bootstrap.env import SAGE_DISTFILES + + +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 = 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): + """ + Load and return `mirror_list` (defaults to the one on disk) as + a list of strings + """ + if mirror_list is None: + try: + with open(self.filename, 'rt') as f: + mirror_list = f.read() + except IOError: + log.critical('Failed to load the cached mirror list') + return [] + import ast + return ast.literal_eval(mirror_list) + + def _save(self): + """ + Save the mirror list for (short-term) future use. + """ + with open(self.filename, 'wt') as f: + f.write(repr(self.mirrors)) + + def _port_of_mirror(self, mirror): + if mirror.startswith('http://'): + return 80 + if mirror.startswith('https://'): + return 443 + if mirror.startswith('ftp://'): + return 21 + + def _rank_mirrors(self): + """ + Sort the mirrors by speed, fastest being first + + This method is used by the YUM fastestmirror plugin + """ + timed_mirrors = [] + import time, socket + log.info('Searching fastest mirror') + timeout = 1 + for mirror in self.mirrors: + if not mirror.startswith('http'): + log.debug('we currently can only handle http, got %s', mirror) + continue + port = self._port_of_mirror(mirror) + mirror_hostname = urlparse.urlsplit(mirror).netloc + time_before = time.time() + try: + sock = socket.create_connection((mirror_hostname, port), timeout) + sock.close() + except (IOError, socket.error, socket.timeout) as err: + log.warning(str(err).strip() + ': ' + mirror) + continue + result = time.time() - time_before + result_ms = int(1000 * result) + log.info(str(result_ms).rjust(5) + 'ms: ' + mirror) + timed_mirrors.append((result, mirror)) + if len(timed_mirrors) == 0: + # We cannot reach any mirror directly, most likely firewall issue + if 'http_proxy' not in os.environ: + log.error('Could not reach any mirror directly and no proxy set') + raise RuntimeError('no internet connection') + log.info('Cannot time mirrors via proxy, using default order') + else: + timed_mirrors.sort() + 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): + """ + Return the age of the cached mirror list in seconds + """ + import time + mtime = os.path.getmtime(self.filename) + now = time.mktime(time.localtime()) + return now - mtime + + 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): + return True + return self.age() > self.MAXAGE + + def __iter__(self): + """ + Iterate through the list of mirrors. + + This is the main entry point into the mirror list. Every + script should just use this function to try mirrors in order + of preference. This will not just yield the official mirrors, + but also urls for packages that are currently being tested. + """ + try: + yield os.environ['SAGE_SERVER'] + except KeyError: + pass + for mirror in self.mirrors: + yield mirror + # If all else fails: Try the packages we host ourselves + yield 'http://sagepad.org/' + + diff --git a/build/sage_bootstrap/package.py b/build/sage_bootstrap/package.py new file mode 100644 index 00000000000..346eb976cea --- /dev/null +++ b/build/sage_bootstrap/package.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +""" +Sage Packages +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import re +import os + +import logging +log = logging.getLogger() + +from sage_bootstrap.env import SAGE_ROOT + + + +class Package(object): + + def __init__(self, package_name): + """ + Sage Package + + A package is defined by a subdirectory of + ``SAGE_ROOT/build/pkgs/``. The name of the package is the name + of the subdirectory; The metadata of the package is contained + in various files in the package directory. This class provides + an abstraction to the metadata, you should never need to + access the package directory directly. + + INPUT: + + -- ``package_name`` -- string. Name of the package. The Sage + convention is that all package names are lower case. + """ + self.__name = package_name + self.__tarball = None + self._init_checksum() + self._init_version() + + def __repr__(self): + return 'Package {0}'.format(self.name) + + @property + def name(self): + """ + Return the package name + + A package is defined by a subdirectory of + ``SAGE_ROOT/build/pkgs/``. The name of the package is the name + of the subdirectory. + + OUTPUT: + + String. + """ + return self.__name + + @property + def md5(self): + """ + Return the MD5 checksum + + Do not use, this is ancient! Use :meth:`sha1` instead. + + OUTPUT: + + String. + """ + return self.__md5 + + @property + def sha1(self): + """ + Return the SHA1 checksum + + OUTPUT: + + String. + """ + return self.__sha1 + + @property + def cksum(self): + """ + Return the Ck sum checksum + + Do not use, this is ancient! Use :meth:`sha1` instead. + + OUTPUT: + + String. + """ + return self.__cksum + + @property + def tarball(self): + """ + Return the (primary) tarball + + If there are multiple tarballs (currently unsupported), this + property returns the one that is unpacked automatically. + + OUTPUT: + + Instance of :class:`sage_bootstrap.tarball.Tarball` + """ + if self.__tarball is None: + from sage_bootstrap.tarball import Tarball + self.__tarball = Tarball(self.tarball_filename, package=self) + return self.__tarball + + @property + def tarball_pattern(self): + """ + Return the (primary) tarball file pattern + + If there are multiple tarballs (currently unsupported), this + property returns the one that is unpacked automatically. + + OUTPUT: + + String. The full-qualified tarball filename, but with + ``VERSION`` instead of the actual tarball filename. + """ + return self.__tarball_pattern + + @property + def tarball_filename(self): + """ + Return the (primary) tarball filename + + If there are multiple tarballs (currently unsupported), this + property returns the one that is unpacked automatically. + + OUTPUT: + + String. The full-qualified tarball filename. + """ + return self.tarball_pattern.replace('VERSION', self.version) + + @property + def version(self): + """ + Return the version + + OUTPUT: + + String. The package version. Excludes the Sage-specific + patchlevel. + """ + return self.__version + + @property + def patchlevel(self): + """ + Return the patchlevel + + OUTPUT: + + String. The patchlevel of the package. Excludes the "p" + prefix. + """ + return self.__patchlevel + + def __eq__(self, other): + return self.tarball == other.tarball + + @classmethod + def all(cls): + """ + Return all packages + """ + 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): + continue + yield cls(subdir) + + @property + def path(self): + """ + Return the package directory + """ + return os.path.join(SAGE_ROOT, 'build', 'pkgs', self.name) + + def _init_checksum(self): + """ + Load the checksums from the appropriate ``checksums.ini`` file + """ + checksums_ini = os.path.join(self.path, 'checksums.ini') + assignment = re.compile('(?P[a-zA-Z0-9]*)=(?P.*)') + result = dict() + with open(checksums_ini, 'rt') as f: + for line in f.readlines(): + match = assignment.match(line) + if match is None: + continue + var, value = match.groups() + result[var] = value + self.__md5 = result['md5'] + self.__sha1 = result['sha1'] + self.__cksum = result['cksum'] + self.__tarball_pattern = result['tarball'] + + VERSION_PATCHLEVEL = re.compile('(?P.*)\.p(?P[0-9]+)') + + def _init_version(self): + with open(os.path.join(self.path, 'package-version.txt')) as f: + package_version = f.read().strip() + match = self.VERSION_PATCHLEVEL.match(package_version) + if match is None: + self.__version = package_version + self.__patchlevel = -1 + else: + self.__version = match.group('version') + self.__patchlevel = match.group('patchlevel') + + diff --git a/build/sage_bootstrap/stdio.py b/build/sage_bootstrap/stdio.py new file mode 100644 index 00000000000..e3984595aba --- /dev/null +++ b/build/sage_bootstrap/stdio.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" +Set Up Input/output + +Output should always be unbuffered so that it appears immediately on +the terminal. +""" + +#***************************************************************************** +# Copyright (C) 2015 Volker Braun +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +import sys +import os + + +class UnbufferedStream(object): + + def __init__(self, stream): + self.stream = stream + + def write(self, data): + self.stream.write(data) + self.stream.flush() + + def __getattr__(self, attr): + return getattr(self.stream, attr) + + +REAL_STDOUT = sys.stdout +REAL_STDERR = sys.stderr + + +def init_streams(config): + if not config.interactive: + sys.stdout = UnbufferedStream(REAL_STDOUT) + + +def flush(): + REAL_STDOUT.flush() + REAL_STDERR.flush() diff --git a/build/sage_bootstrap/tarball.py b/build/sage_bootstrap/tarball.py new file mode 100644 index 00000000000..9d5f2b87628 --- /dev/null +++ b/build/sage_bootstrap/tarball.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +""" +Third-Party Tarballs +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import os +import logging +log = logging.getLogger() + +from sage_bootstrap.env import SAGE_DISTFILES +from sage_bootstrap.download import Download +from sage_bootstrap.package import Package +from sage_bootstrap.mirror_list import MirrorList + + +class ChecksumError(Exception): + """ + Exception raised when the checksum of the tarball does not match + """ + pass + + +class FileNotMirroredError(Exception): + """ + Exception raised when the tarball cannot be downloaded from the mirrors + """ + pass + + +class Tarball(object): + + def __init__(self, tarball_name, package=None): + """ + A (third-party downloadable) tarball + + Note that the tarball might also be a different kind of + archive format that is supported, it does not necessarily have + to be tar. + + INPUT: + + - ``name`` - string. The full filename (``foo-1.3.tar.bz2``) + of a tarball on the Sage mirror network. + """ + self.__filename = tarball_name + if package is None: + self.__package = None + for pkg in Package.all(): + if pkg.tarball_filename == tarball_name: + self.__package = pkg + if self.package is None: + error = 'tarball {0} is not referenced by any Sage package'.format(tarball_name) + log.error(error) + raise ValueError(error) + else: + self.__package = package + if package.tarball_filename != tarball_name: + error = 'tarball {0} is not referenced by the {1} package'.format(tarball_name, package.name) + log.error(error) + raise ValueError(error) + + def __repr__(self): + return 'Tarball {0}'.format(self.filename) + + @property + def filename(self): + """ + Return the tarball filename + + OUTPUT: + + String. The full filename (``foo-1.3.tar.bz2``) of the + tarball. + """ + return self.__filename + + @property + def package(self): + """ + Return the package that the tarball belongs to + + OUTPUT: + + Instance of :class:`sage_bootstrap.package.Package` + """ + return self.__package + + @property + def upstream_fqn(self): + """ + The fully-qualified (including directory) file name in the upstream directory. + """ + return os.path.join(SAGE_DISTFILES, self.filename) + + def __eq__(self, other): + return self.filename == other.filename + + def _compute_hash(self, algorithm): + with open(self.upstream_fqn, 'rb') as f: + while True: + buf = f.read(0x100000) + if not buf: + break + algorithm.update(buf) + return algorithm.hexdigest() + + def _compute_sha1(self): + import hashlib + return self._compute_hash(hashlib.sha1()) + + def _compute_md5(self): + import hashlib + return self._compute_md5(hashlib.md5()) + + def checksum_verifies(self): + """ + Test whether the checksum of the downloaded file is correct. + """ + sha1 = self._compute_sha1() + return sha1 == self.package.sha1 + + def download(self): + """ + Download the tarball to the upstream directory. + """ + destination = os.path.join(SAGE_DISTFILES, self.filename) + if os.path.isfile(destination): + if self.checksum_verifies(): + log.info('Using cached file {destination}'.format(destination=destination)) + return + else: + # Garbage in the upstream directory? Delete and re-download + log.info('Invalid checksum for cached file {destination}, deleting' + .format(destination=destination)) + os.remove(destination) + successful_download = False + log.info('Attempting to download package {0} from mirrors'.format(self.filename)) + for mirror in MirrorList(): + url = mirror + '/'.join(['spkg', 'upstream', self.package.name, self.filename]) + log.info(url) + try: + Download(url, self.upstream_fqn).run() + successful_download = True + break + except IOError: + log.debug('File not on mirror') + if not successful_download: + raise FileNotMirroredError('tarball does not exist on mirror network') + if not self.checksum_verifies(): + raise ChecksumError('checksum does not match') + + def save_as(self, destination): + """ + Save the tarball as a new file + """ + import shutil + shutil.copy(self.upstream_fqn, destination) + diff --git a/build/setup.py b/build/setup.py new file mode 100755 index 00000000000..5f4f7292c0e --- /dev/null +++ b/build/setup.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup( + name='sage_bootstrap', + description='', + author='Volker Braun', + author_email='vbraun.name@gmail.com', + packages=['sage_bootstrap'], + scripts=['bin/sage-package'], + version='1.0', + url='https://www.sagemath.org', +) diff --git a/build/test/__init__.py b/build/test/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/test/capture.py b/build/test/capture.py new file mode 100644 index 00000000000..4b9c4e17f72 --- /dev/null +++ b/build/test/capture.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +""" +Capture output for testing purposes +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import sys +import contextlib +import logging +log = logging.getLogger() + +from sage_bootstrap.compat import StringIO + + +class LogCaptureHandler(logging.Handler): + + def __init__(self, log_capture): + self.records = log_capture.records + logging.Handler.__init__(self) + + def emit(self, record): + self.records.append(record) + + +class CapturedLog(object): + + def __init__(self): + self.records = [] + + def __enter__(self): + self.old_level = log.level + self.old_handlers = log.handlers + log.level = logging.INFO + log.handlers = [LogCaptureHandler(self)] + return self + + def __exit__(self, type, value, traceback): + log.level = self.old_level + log.handlers = self.old_handlers + + def messages(self): + return tuple((rec.levelname, rec.getMessage()) for rec in self.records) + + +@contextlib.contextmanager +def CapturedOutput(): + new_out, new_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err diff --git a/build/test/runnable.py b/build/test/runnable.py new file mode 100755 index 00000000000..9ea98f717e9 --- /dev/null +++ b/build/test/runnable.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Utility to test running with different values for ``SAGE_BOOTSTRAP`` +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +# This function's line numbers are in unit tests, try to not move it +# up or down. This is why it is up here at the beginning. +def print_log(): + import logging + log = logging.getLogger() + log.debug('This is the debug log level') + log.info('This is the info log level') + log.warning('This is the warning log level') + log.critical('This is the critical log level') + log.error('This is the error log level') + print('This is printed') + +# From here on the line number does not matter + +import sys +import os +import json +import subprocess + + +def run_with(command, SAGE_BOOTSTRAP): + env = dict(os.environ) + env['SAGE_BOOTSTRAP'] = SAGE_BOOTSTRAP + proc = subprocess.Popen( + [sys.executable, __file__, command], + env=env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ) + out, err = proc.communicate() + return out.decode('ascii'), err.decode('ascii') + + +def run_config_with(SAGE_BOOTSTRAP): + out, err = run_with('print_config', SAGE_BOOTSTRAP) + assert not err, err + return json.loads(out) + + +def print_config(): + from sage_bootstrap.config import Configuration + from sage_bootstrap.stdio import REAL_STDOUT, REAL_STDERR + config = Configuration() + result = dict( + log=config.log, + interactive=config.interactive, + stdout='default stdout' if sys.stdout == REAL_STDOUT else str(type(sys.stdout)), + stderr='default stderr' if sys.stderr == REAL_STDERR else str(type(sys.stderr)), + ) + print(json.dumps(result)) + + +def run_log_with(SAGE_BOOTSTRAP): + return run_with('print_log', SAGE_BOOTSTRAP) + + +commands = dict( + print_config=print_config, + print_log=print_log, +) + + +if __name__ == '__main__': + sys.path.insert(0, + os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + import sage_bootstrap + commands[sys.argv[1]]() diff --git a/build/test/test_config.py b/build/test/test_config.py new file mode 100644 index 00000000000..03d50deddf8 --- /dev/null +++ b/build/test/test_config.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import unittest +from sage_bootstrap.config import Configuration, LOG_LEVELS +from .runnable import run_config_with + + +class ConfigurationTestCase(unittest.TestCase): + + def test_default(self): + """ + Test the default configuration + """ + config = Configuration() + self.assertEqual(config.log, 'info') + self.assertTrue(config.interactive) + + def test_example(self): + """ + Test all ``SAGE_BOOTSTRAP`` settings + """ + SAGE_BOOTSTRAP=' loG:CrItIcAl, interactive:TRUE' + result = run_config_with(SAGE_BOOTSTRAP) + self.assertEqual(len(result), 4) + self.assertEqual(result['log'], u'critical') + self.assertTrue(result['interactive']) + self.assertEqual(result['stdout'], 'default stdout') + self.assertEqual(result['stderr'], 'default stderr') + + + def test_logging(self): + """ + Test that the different log levels are understood + """ + for level in LOG_LEVELS: + self.assertEqual( + run_config_with('LOG:{0}'.format(level.upper()))['log'], + level) + + def test_logging(self): + """ + Test that overriding the isatty detection works + """ + interactive = run_config_with('interactive:true') + self.assertTrue(interactive['interactive']) + self.assertEqual(interactive['stdout'], 'default stdout') + self.assertEqual(interactive['stderr'], 'default stderr') + in_pipe = run_config_with('interactive:false') + self.assertFalse(in_pipe['interactive']) + self.assertEqual(in_pipe['stdout'], u"") + self.assertEqual(in_pipe['stderr'], 'default stderr') + + diff --git a/build/test/test_download.py b/build/test/test_download.py new file mode 100644 index 00000000000..135583a89d8 --- /dev/null +++ b/build/test/test_download.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +Test downloading files +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import unittest +import tempfile +from textwrap import dedent + + +from .capture import CapturedLog +from sage_bootstrap.download import Download +from sage_bootstrap.mirror_list import MirrorList +from sage_bootstrap.compat import StringIO + + +class DownloadTestCase(unittest.TestCase): + + def test_download_mirror_list(self): + tmp = tempfile.NamedTemporaryFile() + tmp.close() + progress = StringIO() + Download(MirrorList.URL, tmp.name, progress=progress).run() + self.assertEqual(progress.getvalue(), + '[......................................................................]\n') + with open(tmp.name, 'r') as f: + content = f.read() + self.assertTrue(content.startswith('# Sage Mirror List')) + + def test_error(self): + URL = 'http://www.sagemath.org/sage_bootstrap/this_url_does_not_exist' + progress = StringIO() + download = Download(URL, progress=progress) + log = CapturedLog() + def action(): + with log: + download.run() + self.assertRaises(IOError, action) + self.assertIsNotFoundError(log.messages()) + self.assertEqual(progress.getvalue(), + '[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]\n') + + def test_ignore_errors(self): + URL = 'http://www.sagemath.org/sage_bootstrap/this_url_does_not_exist' + with CapturedLog() as log: + Download(URL, progress=False, ignore_errors=True).run() + self.assertIsNotFoundError(log.messages()) + + def assertIsNotFoundError(self, messages): + self.assertEqual(len(messages), 1) + self.assertEqual(messages[0][0], 'ERROR') + self.assertTrue(messages[0][1].startswith('[Errno')) + self.assertTrue(messages[0][1].endswith( + "Not Found: '//www.sagemath.org/sage_bootstrap/this_url_does_not_exist'")) + + diff --git a/build/test/test_logger.py b/build/test/test_logger.py new file mode 100644 index 00000000000..8f5124c782a --- /dev/null +++ b/build/test/test_logger.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +Test the printing and logging +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import unittest +from textwrap import dedent +from .runnable import run_log_with + + + +class LoggerTestCase(unittest.TestCase): + + def test_interactive(self): + stdout, stderr = run_log_with('interactive:true') + self.assertEqual(stderr.strip(), dedent(""" + WARNING [runnable|print_log:25]: This is the warning log level + CRITICAL [runnable|print_log:26]: This is the critical log level + ERROR [runnable|print_log:27]: This is the error log level + """).strip()) + self.assertEqual(stdout.strip(), dedent(""" + This is the info log level + This is printed + """).strip()) + + def test_noninteractive(self): + stdout, stderr = run_log_with('interactive:false') + self.assertEqual(stderr.strip(), dedent(""" + This is the info log level + WARNING [runnable|print_log:25]: This is the warning log level + CRITICAL [runnable|print_log:26]: This is the critical log level + ERROR [runnable|print_log:27]: This is the error log level + """).strip()) + self.assertEqual(stdout.strip(), dedent(""" + This is printed + """).strip()) + + + def test_debug(self): + """ + The lowest logging level + """ + stdout, stderr = run_log_with('log:debug,interactive:true') + self.assertEqual(stderr.strip(), dedent(""" + DEBUG [runnable|print_log:23]: This is the debug log level + WARNING [runnable|print_log:25]: This is the warning log level + CRITICAL [runnable|print_log:26]: This is the critical log level + ERROR [runnable|print_log:27]: This is the error log level + """).strip()) + self.assertEqual(stdout.strip(), dedent(""" + This is the info log level + This is printed + """).strip()) + + def test_error(self): + """ + The highest logging level + """ + stdout, stderr = run_log_with('log:error,interactive:true') + self.assertEqual(stderr.strip(), dedent(""" + CRITICAL [runnable|print_log:26]: This is the critical log level + ERROR [runnable|print_log:27]: This is the error log level + """).strip()) + self.assertEqual(stdout.strip(), dedent(""" + This is printed + """).strip()) + diff --git a/build/test/test_mirror_list.py b/build/test/test_mirror_list.py new file mode 100644 index 00000000000..ddd8e21ca79 --- /dev/null +++ b/build/test/test_mirror_list.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Test downloading files +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import unittest +import tempfile + +from .capture import CapturedLog +from sage_bootstrap.mirror_list import MirrorList + + + +class MirrorListTestCase(unittest.TestCase): + + def test_mirror_list(self): + with CapturedLog() as log: + ml = MirrorList() + msg = log.messages() + if len(msg) > 0: + self.assertEqual(msg[0], ('INFO', 'Downloading the Sage mirror list')) + self.assertTrue(len(ml.mirrors) >= 0) + self.assertTrue(ml.fastest.startswith('http://')) diff --git a/build/test/test_package.py b/build/test/test_package.py new file mode 100644 index 00000000000..18b316771cf --- /dev/null +++ b/build/test/test_package.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Test Sage Package Handling +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import unittest +from sage_bootstrap.package import Package +from sage_bootstrap.tarball import Tarball + + +class PackageTestCase(unittest.TestCase): + + def test_package(self): + pkg = Package('pari') + self.assertTrue(pkg.name, 'pari') + self.assertTrue(pkg.path.endswith('build/pkgs/pari')) + self.assertEqual(pkg.tarball_pattern, 'pari-VERSION.tar.gz') + self.assertEqual(pkg.tarball_filename, pkg.tarball.filename) + self.assertTrue(pkg.tarball.filename.startswith('pari-') and + pkg.tarball.filename.endswith('.tar.gz')) + self.assertTrue(pkg.tarball.filename.startswith('pari-') and + pkg.tarball.filename.endswith('.tar.gz')) + self.assertTrue(isinstance(pkg.tarball, Tarball)) + + def test_all(self): + pari = Package('pari') + self.assertTrue(pari in Package.all()) + diff --git a/build/test/test_tarball.py b/build/test/test_tarball.py new file mode 100644 index 00000000000..4318cc2b803 --- /dev/null +++ b/build/test/test_tarball.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Test Sage Third-Party Tarball Handling +""" + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import unittest + +from sage_bootstrap.package import Package +from sage_bootstrap.tarball import Tarball + +from .capture import CapturedLog, CapturedOutput + + + +class TarballTestCase(unittest.TestCase): + + def test_tarball(self): + pkg = Package('configure') + tarball = Tarball(pkg.tarball_filename) + self.assertEqual(tarball, pkg.tarball) + self.assertEqual(pkg, tarball.package) + with CapturedOutput() as (stdout, stderr): + with CapturedLog() as log: + tarball.download() + self.assertEqual(stdout.getvalue(), '') + self.assertTrue(tarball.checksum_verifies()) + + def test_checksum(self): + pkg = Package('configure') + tarball = pkg.tarball + with CapturedOutput() as (stdout, stderr): + with CapturedLog() as log: + tarball.download() + self.assertTrue(tarball.checksum_verifies()) + with open(tarball.upstream_fqn, 'w') as f: + f.write('foobar') + self.assertFalse(tarball.checksum_verifies()) + with CapturedOutput() as (stdout, stderr): + with CapturedLog() as log: + tarball.download() + msg = log.messages() + self.assertTrue( + ('INFO', 'Attempting to download package {0} from mirrors'.format(pkg.tarball_filename)) in msg) + self.assertEqual(stdout.getvalue(), '') + self.assertEqual(stderr.getvalue(), + '[......................................................................]\n') + self.assertTrue(tarball.checksum_verifies()) diff --git a/build/tox.ini b/build/tox.ini new file mode 100644 index 00000000000..6ac2630ae9f --- /dev/null +++ b/build/tox.ini @@ -0,0 +1,22 @@ +[tox] +envlist = py26, py27, py33, py34 +skip_missing_interpreters=true + + +[testenv:py26] +deps = + unittest2 +commands = unit2 discover + +# We make it harder to get the encoding right by using the dumbest default +setenv = + LC_ALL = C + +[testenv:py27] +commands=python2.7 -m unittest discover + +[testenv:py33] +commands=python3.3 -m unittest discover + +[testenv:py34] +commands=python3.4 -m unittest discover diff --git a/configure.ac b/configure.ac index 81f333dfb4d..d2d76752b9f 100644 --- a/configure.ac +++ b/configure.ac @@ -620,7 +620,7 @@ fi dnl AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([build/Makefile-auto]) +AC_CONFIG_FILES([build/make/Makefile-auto]) AC_CONFIG_MACRO_DIR([m4]) AC_OUTPUT() diff --git a/src/bin/math-readline b/src/bin/math-readline index 9b77a8d9b30..5a1390609d4 100755 --- a/src/bin/math-readline +++ b/src/bin/math-readline @@ -5,21 +5,27 @@ # See # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/363500 -import os, sys -import six -f1 = os.popen('math ', 'w') -f1.flush() -try: - while True: - sys.stdout.write('') - try: - line = six.moves.input() - f1.writelines(line+'\n') - f1.flush() - except KeyboardInterrupt: - f1.close() - break -except EOFError: - pass +import sys, signal, subprocess +import readline +from six.moves import input + +def child_exited(*args): + global child + status = child.poll() + if status is not None: + sys.exit(status) + +signal.signal(signal.SIGCHLD, child_exited) + +child = subprocess.Popen('math', shell=True, stdin=subprocess.PIPE) +pipe = child.stdin +while True: + try: + line = input() + pipe.write(line + '\n') + pipe.flush() + except KeyboardInterrupt: + pipe.close() + except EOFError: + break sys.stdout.write('\n') -sys.exit() diff --git a/src/bin/sage b/src/bin/sage index 12b25b61366..5ecb81bc7f2 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -138,6 +138,7 @@ usage_advanced() { echo " 'version' is a git branch or tag name. Useful values" echo " are 'master' (the current development version, this" echo " is the default) or a version number like '5.13'." + echo " -pip [...] -- invoke pip, the Python package manager" echo #### 1.......................26..................................................78 @@ -205,9 +206,11 @@ usage_advanced() { #### |.....................--.|...................................................| echo "Making Sage packages or distributions:" echo " -bdist -- build a binary distribution of Sage" + echo " -sdist -- build a source distribution of Sage" echo " -pkg -- create Sage package dir.spkg from a given directory" echo " -pkg_nc -- as -pkg, but do not compress the package" - echo " -sdist -- build a source distribution of Sage" + echo " -fix-pkg-checksums -- fix the checksums from build/pkgs directories from " + echo " the packages located in upstream/" echo #### 1.......................26..................................................78 @@ -458,6 +461,11 @@ if [ "$1" = '-pip' -o "$1" = '--pip' ]; then exec pip "$@" fi +if [ "$1" = '-fix-pkg-checksums' -o "$1" = '--fix-pkg-checksums' ]; then + shift + exec sage-fix-pkg-checksums "$@" +fi + if [ "$1" = '-python' -o "$1" = '--python' ]; then shift exec python "$@" @@ -771,7 +779,7 @@ install() { PKG_NAME=`echo "$PKG" | sed -e "s/\.spkg$//"` PKG_NAME=`basename "$PKG_NAME"` - "$SAGE_ROOT"/build/pipestatus \ + "$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')" # Do not try to install further packages if one failed diff --git a/src/bin/sage-banner b/src/bin/sage-banner index c3768b5186b..acc11a4a5e3 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 6.8.beta8, Release Date: 2015-07-10 │ +│ SageMath Version 6.9.beta0, Release Date: 2015-07-29 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-download-file b/src/bin/sage-download-file deleted file mode 100755 index a5778f5f79a..00000000000 --- a/src/bin/sage-download-file +++ /dev/null @@ -1,477 +0,0 @@ -#!/usr/bin/env python - -#***************************************************************************** -# Copyright (C) 2013 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 contextlib -import os -import sys -import re - -try: - # Python 3 - import urllib.request as urllib - import urllib.parse as urlparse -except ImportError: - import urllib - import urlparse - -try: - from sage.env import SAGE_ROOT, SAGE_DISTFILES -except ImportError: - # Sage is not yet installed - SAGE_ROOT = os.environ['SAGE_ROOT'] - SAGE_DISTFILES = os.environ.get('SAGE_DISTFILES', - os.path.join(SAGE_ROOT, 'upstream')) - -try: - # we want to assume that SAGE_DISTFILES is an actual - # directory for the remainder of this script - os.mkdir(SAGE_DISTFILES) -except OSError: - pass - - -def printflush(x): - """Print ``x`` and flush output""" - print(x) - sys.stdout.flush() - - -def http_error_default(url, fp, errcode, errmsg, headers): - """ - Callback for the URLopener to raise an exception on HTTP errors - """ - fp.close() - raise IOError(errcode, errmsg, url) - - -class ProgressBar(object): - """ - Progress bar as urllib reporthook - """ - - def __init__(self, length=70): - self.length = length - self.progress = 0 - self.stream = sys.stderr - - def start(self): - sys.stdout.flush() # make sure to not interleave stdout/stderr - self.stream.write('[') - self.stream.flush() - - def __call__(self, chunks_so_far, chunk_size, total_size): - if total_size == -1: # we do not know size - n = 0 if chunks_so_far == 0 else self.length // 2 - else: - n = chunks_so_far * chunk_size * self.length // total_size - if n > self.length: - # If there is a Content-Length, this will be sent as the last progress - return - # n ranges from 0 to length*total (exclude), so we'll print at most length dots - if n >= self.progress: - self.stream.write('.' * (n-self.progress)) - self.stream.flush() - self.progress = n - - def stop(self): - missing = '.' * (self.length - self.progress) - self.stream.write(missing + ']\n') - self.stream.flush() - - def error_stop(self): - missing = 'x' * (self.length - self.progress) - self.stream.write(missing + ']\n') - self.stream.flush() - - -def http_download(url, destination=None, progress=True, ignore_errors=False): - """ - Download via HTTP - - This should work for FTP as well but, in fact, hangs on python < - 3.4, see http://bugs.python.org/issue16270 - - INPUT: - - - ``url`` -- string. The URL to download. - - - ``destination`` -- string or ``None`` (default). The destination - file name to save to. If not specified, the file is written to - stdout. - - - ``progress`` -- boolean (default: ``True``). Whether to print a - progress bar to stderr. - - - ``ignore_errors`` -- boolean (default: ``False``). Catch network - errors (a message is still printed to stdout). - """ - if destination is None: - destination = '/dev/stdout' - opener = urllib.FancyURLopener() - opener.http_error_default = http_error_default - if progress: - progress_bar = ProgressBar() - progress_bar.start() - try: - filename, info = opener.retrieve(url, destination, progress_bar) - except IOError as err: - progress_bar.error_stop() - printflush(err) - if not ignore_errors: - raise - else: - progress_bar.stop() - else: - filename, info = opener.retrieve(url, destination) - - -class MirrorList(object): - - URL = 'http://www.sagemath.org/mirror_list' - - MAXAGE = 24*60*60 # seconds - - def __init__(self, verbose=True): - """ - If `verbose` is False, don't print messages along the way. - This is needed to produce the appropriate output for - `sage-download-file --print-fastest-mirror`. - """ - self.filename = os.path.join(SAGE_DISTFILES, 'mirror_list') - self.verbose = verbose - if self.must_refresh(): - if self.verbose: - printflush('Downloading the Sage mirror list') - with contextlib.closing(urllib.urlopen(self.URL)) as f: - mirror_list = f.read().decode("ascii") - self.mirrors = self._load(mirror_list) - self._rank_mirrors() - self._save() - else: - self.mirrors = self._load() - - def _load(self, mirror_list = None): - """ - Load and return `mirror_list` (defaults to the one on disk) as - a list of strings - """ - if mirror_list is None: - with open(self.filename, 'rt') as f: - mirror_list = f.read() - import ast - return ast.literal_eval(mirror_list) - - def _save(self): - """ - Save the mirror list for (short-term) future use. - """ - with open(self.filename, 'wt') as f: - f.write(repr(self.mirrors)) - - def _port_of_mirror(self, mirror): - if mirror.startswith('http://'): - return 80 - if mirror.startswith('https://'): - return 443 - if mirror.startswith('ftp://'): - return 21 - - def _rank_mirrors(self): - """ - Sort the mirrors by speed, fastest being first - - This method is used by the YUM fastestmirror plugin - """ - timed_mirrors = [] - import time, socket - if self.verbose: - printflush('Searching fastest mirror') - timeout = 1 - for mirror in self.mirrors: - if not mirror.startswith('http'): - # we currently cannot handle ftp:// - continue - port = self._port_of_mirror(mirror) - mirror_hostname = urlparse.urlsplit(mirror).netloc - time_before = time.time() - try: - sock = socket.create_connection((mirror_hostname, port), timeout) - sock.close() - except (IOError, socket.error, socket.timeout): - continue - result = time.time() - time_before - result_ms = int(1000 * result) - if self.verbose: - printflush(str(result_ms).rjust(5) + 'ms: ' + mirror) - timed_mirrors.append((result, mirror)) - timed_mirrors.sort() - self.mirrors = [m[1] for m in timed_mirrors] - if self.verbose: - printflush('Fastest mirror: ' + self.fastest) - - @property - def fastest(self): - return self.mirrors[0] - - def age(self): - """ - Return the age of the cached mirror list in seconds - """ - import time - mtime = os.path.getmtime(self.filename) - now = time.mktime(time.localtime()) - return now - mtime - - 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): - return True - return self.age() > self.MAXAGE - - def __iter__(self): - """ - Iterate through the list of mirrors. - - This is the main entry point into the mirror list. Every - script should just use this function to try mirrors in order - of preference. This will not just yield the official mirrors, - but also urls for packages that are currently being tested. - """ - try: - yield os.environ['SAGE_SERVER'] - except KeyError: - pass - for mirror in self.mirrors: - yield mirror - # If all else fails: Try the packages we host ourselves - yield 'http://sagepad.org/' - - - -class ChecksumError(Exception): - pass - -class FileNotMirroredError(Exception): - pass - - -class Package(object): - - def __init__(self, package_name): - self.name = package_name - self._init_checksum() - self._init_version() - - @classmethod - 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): - continue - yield cls(subdir) - - @property - def path(self): - return os.path.join(SAGE_ROOT, 'build', 'pkgs', self.name) - - def _init_checksum(self): - """ - Load the checksums from the appropriate ``checksums.ini`` file - """ - checksums_ini = os.path.join(self.path, 'checksums.ini') - assignment = re.compile('(?P[a-zA-Z0-9]*)=(?P.*)') - result = dict() - with open(checksums_ini, 'rt') as f: - for line in f.readlines(): - match = assignment.match(line) - if match is None: - continue - var, value = match.groups() - result[var] = value - self.md5 = result['md5'] - self.sha1 = result['sha1'] - self.cksum = result['cksum'] - self.sha1 = result['sha1'] - self.tarball_pattern = result['tarball'] - - VERSION_PATCHLEVEL = re.compile('(?P.*)\.p(?P[0-9]+)') - - def _init_version(self): - with open(os.path.join(self.path, 'package-version.txt')) as f: - package_version = f.read().strip() - match = self.VERSION_PATCHLEVEL.match(package_version) - if match is None: - self.version = package_version - self.patchlevel = -1 - else: - self.version = match.group('version') - self.patchlevel = match.group('patchlevel') - self.tarball = self.tarball_pattern.replace('VERSION', self.version) - - -class Tarball(object): - - def __init__(self, tarball_name): - """ - A (third-party downloadable) tarball - - INPUT: - - - ``name`` - string. The full filename (``foo-1.3.tar.bz2``) - of a tarball on the Sage mirror network. - """ - self.filename = tarball_name - self.package = None - for pkg in Package.all(): - if pkg.tarball == tarball_name: - self.package = pkg - if self.package is None: - raise ValueError('tarball {0} is not referenced by any Sage package' - .format(tarball_name)) - - @property - def upstream_fqn(self): - """ - The fully-qualified (including directory) file name in the upstream directory. - """ - return os.path.join(SAGE_DISTFILES, self.filename) - - def _compute_hash(self, algorithm): - with open(self.upstream_fqn, 'rb') as f: - while True: - buf = f.read(0x100000) - if not buf: - break - algorithm.update(buf) - return algorithm.hexdigest() - - def _compute_sha1(self): - import hashlib - return self._compute_hash(hashlib.sha1()) - - def _compute_md5(self): - import hashlib - return self._compute_md5(hashlib.md5()) - - def checksum_verifies(self): - """ - Test whether the checksum of the downloaded file is correct. - """ - sha1 = self._compute_sha1() - return sha1 == self.package.sha1 - - def download(self): - """ - Download the tarball to the upstream directory. - """ - destination = os.path.join(SAGE_DISTFILES, self.filename) - if os.path.isfile(destination): - if self.checksum_verifies(): - print('Using cached file {destination}'.format(destination=destination)) - return - else: - # Garbage in the upstream directory? Delete and re-download - print('Invalid checksum for cached file {destination}, deleting' - .format(destination=destination)) - os.remove(destination) - successful_download = False - print('Attempting to download package {0} from mirrors'.format(self.filename)) - for mirror in MirrorList(): - url = mirror + '/'.join(['spkg', 'upstream', self.package.name, self.filename]) - printflush(url) - try: - http_download(url, self.upstream_fqn) - successful_download = True - break - except IOError: - pass # mirror doesn't have file for whatever reason... - if not successful_download: - raise FileNotMirroredError('tarball does not exist on mirror') - if not self.checksum_verifies(): - raise ChecksumError('checksum does not match') - - def save_as(self, destination): - import shutil - shutil.copy(self.upstream_fqn, destination) - - -usage = \ -""" -USAGE: - - sage-download-file --print-fastest-mirror - -Print out the fastest mirror. All further arguments are ignored in -that case. - - sage-download-file [--quiet] url-or-tarball [destination] - -The single mandatory argument can be a http:// url or a tarball -filename. In the latter case, the tarball is downloaded from the -mirror network and its checksum is verified. - -If the destination is not specified: -* a url will be downloaded and the content written to stdout -* a tarball will be saved under {SAGE_DISTFILES} -""".format(SAGE_DISTFILES=SAGE_DISTFILES) - -if __name__ == '__main__': - progress = True - url = None - destination = None - for arg in sys.argv[1:]: - if arg.startswith('--print-fastest-mirror'): - url = "fastest mirror" - continue - if arg.startswith('--quiet'): - progress = False - continue - if url is None: - url = arg - continue - if destination is None: - destination = arg - continue - raise ValueError('too many arguments') - if url is None: - print(usage) - sys.exit(2) - - try: - if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): - http_download(url, destination, progress=progress, ignore_errors=True) - elif url == "fastest mirror": - print(MirrorList(verbose=False).fastest) - else: - # url is a tarball name - tarball = Tarball(url) - tarball.download() - if destination is not None: - tarball.save_as(destination) - except: - 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/src/bin/sage-env b/src/bin/sage-env index fe14164018d..53246b12513 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -154,7 +154,7 @@ fi # "version number" of sage-env. If a different version of sage-env was # sourced earlier (when upgrading), we still source the new version. # The sage-env version should be increased whenever a newer sage-env is -# required for upgrading. Note that build/deps might also need +# required for upgrading. Note that build/make/deps might also need # to be changed: the packages which need the new sage-env must depend on # SAGE_ROOT_REPO (the root repo contains sage-env). # @@ -258,7 +258,7 @@ if [ -z "${SAGE_ORIG_PATH_SET}" ]; then SAGE_ORIG_PATH=$PATH && export SAGE_ORIG_PATH SAGE_ORIG_PATH_SET=True && export SAGE_ORIG_PATH_SET fi -export PATH="$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" +export PATH="$SAGE_ROOT/build/bin:$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" # We offer a toolchain option, so if $SAGE_LOCAL/toolchain/toolchain-env exists source it. # Since the user might do something crazy we do not do any checks, but hope for the best. @@ -377,8 +377,13 @@ if [ -d "$SAGE_LOCAL/lib/python" ]; then PYTHONPATH="$SAGE_PATH:$PYTHONPATH" fi PYTHONHOME="$SAGE_LOCAL" + # Set PYTHONNOUSERSITE to avoid picking up non-Sage versions of + # Matplotlib, numpy, etc. See http://trac.sagemath.org/ticket/14243 + # and http://trac.sagemath.org/ticket/18955. + PYTHONNOUSERSITE=yes export PYTHONPATH export PYTHONHOME + export PYTHONNOUSERSITE fi if [ -z "${SAGE_ORIG_LD_LIBRARY_PATH_SET}" ]; then diff --git a/src/bin/sage-sdist b/src/bin/sage-sdist index dc84b7bee84..6b08cec0dc1 100755 --- a/src/bin/sage-sdist +++ b/src/bin/sage-sdist @@ -45,7 +45,7 @@ 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) -cd "$SAGE_ROOT/build" +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 diff --git a/src/bin/sage-starts b/src/bin/sage-starts index f9781a36bc4..9f5e29ef02d 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/pipestatus "./sage --nodotsage -c '$cmd' 2>&1" "tee -a logs/start.log" +build/make/pipestatus "./sage --nodotsage -c '$cmd' 2>&1" "tee -a logs/start.log" if [ $? -ne 0 ]; then echo >&2 "Sage failed to start up." diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index f94a22d07ea..493e910c254 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.8.beta8' -SAGE_RELEASE_DATE='2015-07-10' +SAGE_VERSION='6.9.beta0' +SAGE_RELEASE_DATE='2015-07-29' diff --git a/src/doc/en/constructions/linear_algebra.rst b/src/doc/en/constructions/linear_algebra.rst index 8eb58b2a72c..432681f737f 100644 --- a/src/doc/en/constructions/linear_algebra.rst +++ b/src/doc/en/constructions/linear_algebra.rst @@ -193,7 +193,7 @@ Sage has a full range of functions for computing eigenvalues and both left and right eigenvectors and eigenspaces. If our matrix is :math:`A`, then the ``eigenmatrix_right`` (resp. ``eightmatrix_left``) command also gives matrices :math:`D` and :math:`P` such that :math:`AP=PD` (resp. -:math:`PA=PD`.) +:math:`PA=DP`.) :: diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index f78420e30c1..3c02e00abc6 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -9,7 +9,7 @@ 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 -``SAGE_ROOT/local/bin/sage-spkg``. This script is typically invoked by +``SAGE_ROOT/build/bin/sage-spkg``. This script is typically invoked by giving the command:: [user@localhost]$ sage -i ... @@ -331,7 +331,7 @@ 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/deps`` is called when building Sage so +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``. diff --git a/src/doc/en/developer/packaging_old_spkgs.rst b/src/doc/en/developer/packaging_old_spkgs.rst index bcc07d37b27..49220bf09b6 100644 --- a/src/doc/en/developer/packaging_old_spkgs.rst +++ b/src/doc/en/developer/packaging_old_spkgs.rst @@ -33,7 +33,7 @@ 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 -``SAGE_ROOT/src/bin/sage-spkg`` takes care of the unpacking, +``SAGE_ROOT/build/bin/sage-spkg`` takes care of the unpacking, compilation, and installation of Sage packages for you. You can type:: tar -jxvf mypackage-version.spkg diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index f7fb0ba2d8c..22e73970903 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -61,6 +61,7 @@ Comprehensive Module list sage/combinat/crystals/fast_crystals sage/combinat/crystals/generalized_young_walls sage/combinat/crystals/highest_weight_crystals + sage/combinat/crystals/induced_structure sage/combinat/crystals/infinity_crystals sage/combinat/crystals/polyhedral_realization sage/combinat/crystals/kirillov_reshetikhin diff --git a/src/doc/en/reference/lfunctions/index.rst b/src/doc/en/reference/lfunctions/index.rst index 2bebdb5ef21..03238db6534 100644 --- a/src/doc/en/reference/lfunctions/index.rst +++ b/src/doc/en/reference/lfunctions/index.rst @@ -10,5 +10,6 @@ with :math:`L`-functions. sage/lfunctions/lcalc sage/lfunctions/sympow sage/lfunctions/dokchitser + sage/lfunctions/zero_sums .. include:: ../footer.txt diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index a605e055c73..6efcf30199e 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -293,6 +293,7 @@ Miscellaneous Inspection and Development Tools sage/misc/dev_tools sage/misc/function_mangling sage/misc/memory_info + sage/misc/rest_index_of_methods Low-Level Utilities ------------------- diff --git a/src/doc/en/thematic_tutorials/sandpile.rst b/src/doc/en/thematic_tutorials/sandpile.rst index 156b25ed878..873a852012c 100644 --- a/src/doc/en/thematic_tutorials/sandpile.rst +++ b/src/doc/en/thematic_tutorials/sandpile.rst @@ -342,7 +342,7 @@ taken to be the sum of the number of times each vertex fires. **Example (distribution of avalanche sizes).** :: - sage: S = grid_sandpile(10,10) + sage: S = sandpiles.Grid(10,10) sage: m = S.max_stable() sage: a = [] sage: for i in range(10000): # long time (15s on sage.math, 2012) @@ -400,13 +400,9 @@ that `|D-E|\neq0` for all effective divisors `E` of degree `s`. Define the r(D)-r(K-D)=\deg(D)+1-g. -**Example.** (Some of the following calculations require the installation of :ref:`4ti2 `.) :: +**Example.**:: - sage: G = complete_sandpile(5) # the sandpile on the complete graph with 5 vertices - - The genus (num_edges method counts each undirected edge twice): - - sage: g = G.num_edges()/2 - G.num_verts() + 1 + sage: G = sandpiles.Complete(5) # the sandpile on the complete graph with 5 vertices A divisor on the graph: @@ -415,24 +411,24 @@ that `|D-E|\neq0` for all effective divisors `E` of degree `s`. Define the Verify the Riemann-Roch theorem: sage: K = G.canonical_divisor() - sage: D.r_of_D() - (K - D).r_of_D() == D.deg() + 1 - g # optional - 4ti2 + sage: D.rank() - (K - D).rank() == D.deg() + 1 - G.genus() True The effective divisors linearly equivalent to D: - sage: [E.values() for E in D.effective_div()] # optional - 4ti2 - [[0, 1, 1, 4, 1], [4, 0, 0, 3, 0], [1, 2, 2, 0, 2]] + sage: D.effective_div(False) + [[0, 1, 1, 4, 1], [1, 2, 2, 0, 2], [4, 0, 0, 3, 0]] The nonspecial divisors up to linear equivalence (divisors of degree g-1 with empty linear systems) sage: N = G.nonspecial_divisors() sage: [E.values() for E in N[:5]] # the first few - [[-1, 2, 1, 3, 0], - [-1, 0, 3, 1, 2], - [-1, 2, 0, 3, 1], - [-1, 3, 1, 2, 0], - [-1, 2, 0, 1, 3]] + [[-1, 0, 1, 2, 3], + [-1, 0, 1, 3, 2], + [-1, 0, 2, 1, 3], + [-1, 0, 2, 3, 1], + [-1, 0, 3, 1, 2]] sage: len(N) 24 sage: len(N) == G.h_vector()[-1] @@ -449,8 +445,8 @@ attained from `E` by firing a single unstable vertex. :: sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) sage: D.is_alive() True - sage: eff = D.effective_div() # optional - 4ti2 - sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01,iterations=500) # optional - 4ti2 + sage: eff = D.effective_div() + sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01,iterations=500) .. figure:: media/sandpile/C_6.png :align: center @@ -462,8 +458,8 @@ if `F` is obtained from `E` by firing all unstable vertices of `E`. :: sage: S = Sandpile(graphs.CycleGraph(6),0) sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01,iterations=500) # optional - 4ti2 + sage: eff = D.effective_div() + sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01,iterations=500) .. figure:: media/sandpile/C_6-parallel.png :align: center @@ -691,21 +687,21 @@ The Betti number `\beta_{i,D}` equals the dimension over `\mathbb{C}` of the Representatives of all divisor classes with nontrivial homology: - sage: p = S.betti_complexes() # optional - 4ti2 - sage: p[0] # optional - 4ti2 + sage: p = S.betti_complexes() + sage: p[0] [{0: -8, 1: 5, 2: 4, 3: 1}, - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (3,)}] + Simplicial complex with vertex set (1, 2, 3) and facets {(1, 2), (3,)}] The homology associated with the first divisor in the list: - sage: D = p[0][0] # optional - 4ti2 - sage: D.effective_div() # optional - 4ti2 - [{0: 0, 1: 1, 2: 1, 3: 0}, {0: 0, 1: 0, 2: 0, 3: 2}] - sage: [E.support() for E in D.effective_div()] # optional - 4ti2 - [[1, 2], [3]] - sage: D.Dcomplex() # optional - 4ti2 - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (3,)} - sage: D.Dcomplex().homology() # optional - 4ti2 + sage: D = p[0][0] + sage: D.effective_div() + [{0: 0, 1: 0, 2: 0, 3: 2}, {0: 0, 1: 1, 2: 1, 3: 0}] + sage: [E.support() for E in D.effective_div()] + [[3], [1, 2]] + sage: D.Dcomplex() + Simplicial complex with vertex set (1, 2, 3) and facets {(1, 2), (3,)} + sage: D.Dcomplex().homology() {0: Z, 1: 0} @@ -721,13 +717,13 @@ The Betti number `\beta_{i,D}` equals the dimension over `\mathbb{C}` of the 2: - - - 1 ------------------------------ total: 1 5 5 1 - sage: len(p) # optional - 4ti2 + sage: len(p) 11 The degrees and ranks of the homology groups for each element of the list p (compare with the Betti table, above): - sage: [[sum(d[0].values()),d[1].betti()] for d in p] # optional - 4ti2 + sage: [[sum(d[0].values()),d[1].betti()] for d in p] [[2, {0: 2, 1: 0}], [3, {0: 1, 1: 1, 2: 0}], [2, {0: 2, 1: 0}], @@ -753,27 +749,6 @@ Betti numbers for undirected graphs To do. -.. _installation: - -4ti2 installation ------------------ -.. warning:: - - The methods for computing linear systems of divisors and their corresponding - simplicial complexes require the installation of 4ti2. - -To install 4ti2: - -Go to the `Sage website `_ and - look for the *precise name* of the 4ti2 package and install it according to - the instructions given there. For instance, suppose the package is named - 4ti2.p0.spkg. Install the package with the following command from a UNIX shell - prompt: - -.. code-block:: python - - sage -i 4ti2.p0 - Usage ----- @@ -881,95 +856,111 @@ Sandpile **Summary of methods.** -- :ref:`all_k_config(k) ` --- The configuration with all values set to k. +- :ref:`all_k_config ` --- The constant configuration with all values set to k. + +- :ref:`all_k_div ` --- The divisor with all values set to k. + +- :ref:`avalanche_polynomial ` --- The avalanche polynomial. + +- :ref:`betti ` --- The Betti table for the homogeneous toppling ideal. + +- :ref:`betti_complexes ` --- The support-complexes with non-trivial homology. + +- :ref:`burning_config ` --- The minimal burning configuration. + +- :ref:`burning_script ` --- A script for the minimal burning configuration. -- :ref:`all_k_div(k) ` --- The divisor with all values set to k. +- :ref:`canonical_divisor ` --- The canonical divisor. -- :ref:`betti(verbose=True) ` --- The Betti table for the homogeneous sandpile ideal. +- :ref:`dict ` --- A dictionary of dictionaries representing a directed graph. -- :ref:`betti_complexes() ` --- The divisors with nonempty linear systems along with their simplicial complexes. +- :ref:`genus ` --- The genus: (# non-loop edges) - (# vertices) + 1. -- :ref:`burning_config() ` --- A minimal burning configuration. +- :ref:`groebner ` --- A Groebner basis for the homogeneous toppling ideal. -- :ref:`burning_script() ` --- A script for the minimal burning configuration. +- :ref:`group_gens ` --- A minimal list of generators for the sandpile group. -- :ref:`canonical_divisor() ` --- The canonical divisor (for undirected graphs). +- :ref:`group_order ` --- The size of the sandpile group. -- :ref:`dict() ` --- A dictionary of dictionaries representing a directed graph. +- :ref:`h_vector ` --- The number of superstable configurations in each degree. -- :ref:`groebner() ` --- Groebner basis for the homogeneous sandpile ideal with respect to the standard sandpile ordering. +- :ref:`help ` --- List of Sandpile-specific methods (not inherited from Graph). -- :ref:`group_order() ` --- The size of the sandpile group. +- :ref:`hilbert_function ` --- The Hilbert function of the homogeneous toppling ideal. -- :ref:`h_vector() ` --- The first differences of the Hilbert function of the homogeneous sandpile ideal. +- :ref:`ideal ` --- The saturated homogeneous toppling ideal. -- :ref:`hilbert_function() ` --- The Hilbert function of the homogeneous sandpile ideal. +- :ref:`identity ` --- The identity configuration. -- :ref:`ideal(gens=False) ` --- The saturated, homogeneous sandpile ideal. +- :ref:`in_degree ` --- The in-degree of a vertex or a list of all in-degrees. -- :ref:`identity() ` --- The identity configuration. +- :ref:`invariant_factors ` --- The invariant factors of the sandpile group. -- :ref:`in_degree(v=None) ` --- The in-degree of a vertex or a list of all in-degrees. +- :ref:`is_undirected ` --- Is the underlying graph undirected? -- :ref:`invariant_factors() ` --- The invariant factors of the sandpile group (a finite abelian group). +- :ref:`jacobian_representatives ` --- Representatives for the elements of the Jacobian group. -- :ref:`is_undirected() ` --- ``True`` if ``(u,v)`` is and edge if and only if ``(v,u)`` is an edges, each edge with the same weight. +- :ref:`laplacian ` --- The Laplacian matrix of the graph. -- :ref:`laplacian() ` --- The Laplacian matrix of the graph. +- :ref:`markov_chain ` --- The sandpile Markov chain for configurations or divisors. -- :ref:`max_stable() ` --- The maximal stable configuration. +- :ref:`max_stable ` --- The maximal stable configuration. -- :ref:`max_stable_div() ` --- The maximal stable divisor. +- :ref:`max_stable_div ` --- The maximal stable divisor. -- :ref:`max_superstables(verbose=True) ` --- The maximal superstable configurations. +- :ref:`max_superstables ` --- The maximal superstable configurations. -- :ref:`min_recurrents(verbose=True) ` --- The minimal recurrent elements. +- :ref:`min_recurrents ` --- The minimal recurrent elements. -- :ref:`nonsink_vertices() ` --- The names of the nonsink vertices. +- :ref:`nonsink_vertices ` --- The nonsink vertices. -- :ref:`nonspecial_divisors(verbose=True) ` --- The nonspecial divisors (only for undirected graphs). +- :ref:`nonspecial_divisors ` --- The nonspecial divisors. -- :ref:`num_edges() ` --- The number of edges. +- :ref:`out_degree ` --- The out-degree of a vertex or a list of all out-degrees. -- :ref:`num_verts() ` --- The number of vertices. +- :ref:`picard_representatives ` --- Representatives of the divisor classes of degree d in the Picard group. -- :ref:`out_degree(v=None) ` --- The out-degree of a vertex or a list of all out-degrees. +- :ref:`points ` --- Generators for the multiplicative group of zeros of the sandpile ideal. -- :ref:`points() ` --- Generators for the multiplicative group of zeros of the sandpile ideal. +- :ref:`postulation ` --- The postulation number of the toppling ideal. -- :ref:`postulation() ` --- The postulation number of the sandpile ideal. +- :ref:`recurrents ` --- The recurrent configurations. -- :ref:`recurrents(verbose=True) ` --- The list of recurrent configurations. +- :ref:`reduced_laplacian ` --- The reduced Laplacian matrix of the graph. -- :ref:`reduced_laplacian() ` --- The reduced Laplacian matrix of the graph. +- :ref:`reorder_vertices ` --- A copy of the sandpile with vertex names permuted. -- :ref:`reorder_vertices() ` --- Create a copy of the sandpile but with the vertices reordered. +- :ref:`resolution ` --- A minimal free resolution of the homogeneous toppling ideal. -- :ref:`resolution(verbose=False) ` --- The minimal free resolution of the homogeneous sandpile ideal. +- :ref:`ring ` --- The ring containing the homogeneous toppling ideal. -- :ref:`ring() ` --- The ring containing the homogeneous sandpile ideal. +- :ref:`show ` --- Draw the underlying graph. -- :ref:`show(kwds) ` --- Draws the graph. +- :ref:`show3d ` --- Draw the underlying graph. -- :ref:`show3d(kwds) ` --- Draws the graph. +- :ref:`sink ` --- The sink vertex. -- :ref:`sink() ` --- The identifier for the sink vertex. +- :ref:`smith_form ` --- The Smith normal form for the Laplacian. -- :ref:`solve() ` --- Approximations of the complex affine zeros of the sandpile ideal. +- :ref:`solve ` --- Approximations of the complex affine zeros of the sandpile ideal. -- :ref:`superstables(verbose=True) ` --- The list of superstable configurations. +- :ref:`stable_configs ` --- Generator for all stable configurations. -- :ref:`symmetric_recurrents(orbits) ` --- The list of symmetric recurrent configurations. +- :ref:`stationary_density ` --- The stationary density of the sandpile. -- :ref:`unsaturated_ideal() ` --- The unsaturated, homogeneous sandpile ideal. +- :ref:`superstables ` --- The superstable configurations. -- :ref:`version() ` --- The version number of Sage Sandpiles. +- :ref:`symmetric_recurrents ` --- The symmetric recurrent configurations. -- :ref:`vertices(key=None, boundary_first=False) ` --- List of the vertices. +- :ref:`tutte_polynomial ` --- The Tutte polynomial. -- :ref:`zero_config() ` --- The all-zero configuration. +- :ref:`unsaturated_ideal ` --- The unsaturated, homogeneous toppling ideal. -- :ref:`zero_div() ` --- The all-zero divisor. +- :ref:`version ` --- The version number of Sage Sandpiles. + +- :ref:`zero_config ` --- The all-zero configuration. + +- :ref:`zero_div ` --- The all-zero divisor. -------- @@ -977,1327 +968,1599 @@ Sandpile --- -.. _all_k_config(k): +.. _all_k_config: **all_k_config(k)** - The configuration with all values set to k. +The constant configuration with all values set to `k`. - INPUT: +INPUT: - ``k`` - integer +``k`` -- integer - OUTPUT: +OUTPUT: - SandpileConfig +SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.all_k_config(7) - {1: 7, 2: 7, 3: 7, 4: 7, 5: 7} + sage: s = sandpiles.Diamond() + sage: s.all_k_config(7) + {1: 7, 2: 7, 3: 7} --- -.. _all_k_div(k): +.. _all_k_div: **all_k_div(k)** - The divisor with all values set to k. +The divisor with all values set to `k`. - INPUT: +INPUT: - ``k`` - integer +``k`` -- integer - OUTPUT: +OUTPUT: - SandpileDivisor +SandpileDivisor - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.all_k_div(7) - {0: 7, 1: 7, 2: 7, 3: 7, 4: 7, 5: 7} + sage: S = sandpiles.House() + sage: S.all_k_div(7) + {0: 7, 1: 7, 2: 7, 3: 7, 4: 7} --- -.. _betti(verbose=True): +.. _avalanche_polynomial: -**betti(verbose=True)** +**avalanche_polynomial(multivariable=True)** - Computes the Betti table for the homogeneous sandpile ideal. If - ``verbose`` is ``True``, it prints the standard Betti table, - otherwise, it returns a less formated table. +The avalanche polynomial. See NOTE for details. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``multivariable`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - Betti numbers for the sandpile +polynomial - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.betti() # long time - 0 1 2 3 4 5 - ------------------------------------------ - 0: 1 1 - - - - - 1: - 4 6 2 - - - 2: - 2 7 7 2 - - 3: - - 6 16 14 4 - ------------------------------------------ - total: 1 7 19 25 16 4 - sage: S.betti(False) # long time - [1, 7, 19, 25, 16, 4] + sage: s = sandpiles.Complete(4) + sage: s.avalanche_polynomial() + 9*x0*x1*x2 + 2*x0*x1 + 2*x0*x2 + 2*x1*x2 + 3*x0 + 3*x1 + 3*x2 + 24 + sage: s.avalanche_polynomial(False) + 9*x0^3 + 6*x0^2 + 9*x0 + 24 + +.. NOTE:: + + For each nonsink vertex `v`, let `x_v` be an indeterminate. + If `(r,v)` is a pair consisting of a recurrent `r` and nonsink + vertex `v`, then for each nonsink vertex `w`, let `n_w` be the + number of times vertex `w` fires in the stabilization of `r + v`. + Let `M(r,v)` be the monomial `\prod_w x_w^{n_w}`, i.e., the exponent + records the vector of `n_w` as `w` ranges over the nonsink vertices. + The avalanche polynomial is then the sum of `M(r,v)` as `r` ranges + over the recurrents and `v` ranges over the nonsink vertices. If + ``multivariable`` is ``False``, then set all the indeterminates equal + to each other (and, thus, only count the number of vertex firings in the + stabilizations, forgetting which particular vertices fired). --- -.. _betti_complexes(): +.. _betti: -**betti_complexes()** +**betti(verbose=True)** - A list of all the divisors with nonempty linear systems whose - corresponding simplicial complexes have nonzero homology in some - dimension. Each such divisor is returned with its corresponding - simplicial complex. +The Betti table for the homogeneous toppling ideal. If +``verbose`` is ``True``, it prints the standard Betti table, otherwise, +it returns a less formated table. - INPUT: +INPUT: - None +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list (of pairs [divisors, corresponding simplicial complex]) +Betti numbers for the sandpile - EXAMPLES:: - sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0) - sage: p = S.betti_complexes() # optional - 4ti2 - sage: p[0] # optional - 4ti2 - [{0: -8, 1: 5, 2: 4, 3: 1}, - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (3,)}] - sage: S.resolution() - 'R^1 <-- R^5 <-- R^5 <-- R^1' - sage: S.betti() - 0 1 2 3 - ------------------------------ - 0: 1 - - - - 1: - 5 5 - - 2: - - - 1 - ------------------------------ - total: 1 5 5 1 - sage: len(p) # optional - 4ti2 - 11 - sage: p[0][1].homology() # optional - 4ti2 - {0: Z, 1: 0} - sage: p[-1][1].homology() # optional - 4ti2 - {0: 0, 1: 0, 2: Z} - ---- - -.. _burning_config(): +EXAMPLES:: -**burning_config()** + sage: S = sandpiles.Diamond() + sage: S.betti() + 0 1 2 3 + ------------------------------ + 0: 1 - - - + 1: - 2 - - + 2: - 4 9 4 + ------------------------------ + total: 1 6 9 4 + sage: S.betti(False) + [1, 6, 9, 4] + +--- - A minimal burning configuration. +.. _betti_complexes: - INPUT: +**betti_complexes()** - None +The support-complexes with non-trivial homology. (See NOTE.) - OUTPUT: +OUTPUT: - dict (configuration) +list (of pairs [divisors, corresponding simplicial complex]) - EXAMPLES:: +EXAMPLES:: + + sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0) + sage: p = S.betti_complexes() + sage: p[0] + [{0: -8, 1: 5, 2: 4, 3: 1}, Simplicial complex with vertex set (1, 2, 3) and facets {(1, 2), (3,)}] + sage: S.resolution() + 'R^1 <-- R^5 <-- R^5 <-- R^1' + sage: S.betti() + 0 1 2 3 + ------------------------------ + 0: 1 - - - + 1: - 5 5 - + 2: - - - 1 + ------------------------------ + total: 1 5 5 1 + sage: len(p) + 11 + sage: p[0][1].homology() + {0: Z, 1: 0} + sage: p[-1][1].homology() + {0: 0, 1: 0, 2: Z} - sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},\ - 3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}} - sage: S = Sandpile(g,0) - sage: S.burning_config() - {1: 2, 2: 0, 3: 1, 4: 1, 5: 0} - sage: S.burning_config().values() - [2, 0, 1, 1, 0] - sage: S.burning_script() - {1: 1, 2: 3, 3: 5, 4: 1, 5: 4} - sage: script = S.burning_script().values() - sage: script - [1, 3, 5, 1, 4] - sage: matrix(script)*S.reduced_laplacian() - [2 0 1 1 0] +.. NOTE:: - NOTES: + A ``support-complex`` is the simplicial complex formed from the + supports of the divisors in a linear system. - The burning configuration and script are computed using a modified - version of Speer's script algorithm. This is a generalization to - directed multigraphs of Dhar's burning algorithm. +--- - A *burning configuration* is a nonnegative integer-linear - combination of the rows of the reduced Laplacian matrix having - nonnegative entries and such that every vertex has a path from some - vertex in its support. The corresponding *burning script* gives - the integer-linear combination needed to obtain the burning - configuration. So if b is the burning configuration, sigma is its - script, and tilde{L} is the reduced Laplacian, then sigma * - tilde{L} = b. The *minimal burning configuration* is the one with - the minimal script (its components are no larger than the - components of any other script for a burning configuration). +.. _burning_config: - The following are equivalent for a configuration c with burning - configuration b having script sigma: +**burning_config()** - * c is recurrent; +The minimal burning configuration. - * c+b stabilizes to c; +OUTPUT: - * the firing vector for the stabilization of c+b is sigma. +dict (configuration) ---- +EXAMPLES:: + + sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1}, \ + 3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}} + sage: S = Sandpile(g,0) + sage: S.burning_config() + {1: 2, 2: 0, 3: 1, 4: 1, 5: 0} + sage: S.burning_config().values() + [2, 0, 1, 1, 0] + sage: S.burning_script() + {1: 1, 2: 3, 3: 5, 4: 1, 5: 4} + sage: script = S.burning_script().values() + sage: script + [1, 3, 5, 1, 4] + sage: matrix(script)*S.reduced_laplacian() + [2 0 1 1 0] -.. _burning_script(): +.. NOTE:: -**burning_script()** + The burning configuration and script are computed using a modified + version of Speer's script algorithm. This is a generalization to + directed multigraphs of Dhar's burning algorithm. - A script for the minimal burning configuration. + A *burning configuration* is a nonnegative integer-linear + combination of the rows of the reduced Laplacian matrix having + nonnegative entries and such that every vertex has a path from some + vertex in its support. The corresponding *burning script* gives + the integer-linear combination needed to obtain the burning + configuration. So if `b` is the burning configuration, `\sigma` is its + script, and `\tilde{L}` is the reduced Laplacian, then `\sigma\cdot + \tilde{L} = b`. The *minimal burning configuration* is the one + with the minimal script (its components are no larger than the + components of any other script + for a burning configuration). - INPUT: + The following are equivalent for a configuration `c` with burning + configuration `b` having script `\sigma`: - None + - `c` is recurrent; + - `c+b` stabilizes to `c`; + - the firing vector for the stabilization of `c+b` is `\sigma`. - OUTPUT: +--- - dict +.. _burning_script: - EXAMPLES:: +**burning_script()** - sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},\ - 3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}} - sage: S = Sandpile(g,0) - sage: S.burning_config() - {1: 2, 2: 0, 3: 1, 4: 1, 5: 0} - sage: S.burning_config().values() - [2, 0, 1, 1, 0] - sage: S.burning_script() - {1: 1, 2: 3, 3: 5, 4: 1, 5: 4} - sage: script = S.burning_script().values() - sage: script - [1, 3, 5, 1, 4] - sage: matrix(script)*S.reduced_laplacian() - [2 0 1 1 0] +A script for the minimal burning configuration. - NOTES: +OUTPUT: - The burning configuration and script are computed using a modified - version of Speer's script algorithm. This is a generalization to - directed multigraphs of Dhar's burning algorithm. +dict - A *burning configuration* is a nonnegative integer-linear - combination of the rows of the reduced Laplacian matrix having - nonnegative entries and such that every vertex has a path from some - vertex in its support. The corresponding *burning script* gives - the integer-linear combination needed to obtain the burning - configuration. So if b is the burning configuration, s is its - script, and L_{mathrm{red}} is the reduced Laplacian, then s * - L_{mathrm{red}}= b. The *minimal burning configuration* is the - one with the minimal script (its components are no larger than the - components of any other script for a burning configuration). +EXAMPLES:: - The following are equivalent for a configuration c with burning - configuration b having script s: + sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},\ + 3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}} + sage: S = Sandpile(g,0) + sage: S.burning_config() + {1: 2, 2: 0, 3: 1, 4: 1, 5: 0} + sage: S.burning_config().values() + [2, 0, 1, 1, 0] + sage: S.burning_script() + {1: 1, 2: 3, 3: 5, 4: 1, 5: 4} + sage: script = S.burning_script().values() + sage: script + [1, 3, 5, 1, 4] + sage: matrix(script)*S.reduced_laplacian() + [2 0 1 1 0] + +.. NOTE:: - * c is recurrent; + The burning configuration and script are computed using a modified + version of Speer's script algorithm. This is a generalization to + directed multigraphs of Dhar's burning algorithm. - * c+b stabilizes to c; + A *burning configuration* is a nonnegative integer-linear + combination of the rows of the reduced Laplacian matrix having + nonnegative entries and such that every vertex has a path from some + vertex in its support. The corresponding *burning script* gives the + integer-linear combination needed to obtain the burning configuration. + So if `b` is the burning configuration, `s` is its script, and + `L_{\mathrm{red}}` is the reduced Laplacian, then `s\cdot + L_{\mathrm{red}}= b`. The *minimal burning configuration* is the one + with the minimal script (its components are no larger than the + components of any other script + for a burning configuration). - * the firing vector for the stabilization of c+b is s. + The following are equivalent for a configuration `c` with burning + configuration `b` having script `s`: + + - `c` is recurrent; + - `c+b` stabilizes to `c`; + - the firing vector for the stabilization of `c+b` is `s`. --- -.. _canonical_divisor(): +.. _canonical_divisor: **canonical_divisor()** - The canonical divisor: the divisor ``deg(v)-2`` grains of sand on - each vertex. Only for undirected graphs. +The canonical divisor. This is the divisor with `\deg(v)-2` grains of +sand on each vertex (not counting loops). Only for undirected graphs. - INPUT: +OUTPUT: - None +SandpileDivisor - OUTPUT: +EXAMPLES:: - SandpileDivisor + sage: S = sandpiles.Complete(4) + sage: S.canonical_divisor() + {0: 1, 1: 1, 2: 1, 3: 1} + sage: s = Sandpile({0:[1,1],1:[0,0,1,1,1]},0) + sage: s.canonical_divisor() # loops are disregarded + {0: 0, 1: 0} - EXAMPLES:: +.. WARNING:: - sage: S = complete_sandpile(4) - sage: S.canonical_divisor() - {0: 1, 1: 1, 2: 1, 3: 1} + The underlying graph must be undirected. --- -.. _dict(): +.. _dict: **dict()** - A dictionary of dictionaries representing a directed graph. +A dictionary of dictionaries representing a directed graph. - INPUT: +OUTPUT: - None +dict - OUTPUT: +EXAMPLES:: - dict + sage: S = sandpiles.Diamond() + sage: S.dict() + {0: {1: 1, 2: 1}, + 1: {0: 1, 2: 1, 3: 1}, + 2: {0: 1, 1: 1, 3: 1}, + 3: {1: 1, 2: 1}} + sage: S.sink() + 0 - EXAMPLES:: +--- + +.. _genus: + +**genus()** + +The genus: (# non-loop edges) - (# vertices) + 1. Only defined for undirected graphs. - sage: G = sandlib('generic') - sage: G.dict() - {0: {}, - 1: {0: 1, 3: 1, 4: 1}, - 2: {0: 1, 3: 1, 5: 1}, - 3: {2: 1, 5: 1}, - 4: {1: 1, 3: 1}, - 5: {2: 1, 3: 1}} - sage: G.sink() - 0 +OUTPUT: + +integer + +EXAMPLES:: + + sage: sandpiles.Complete(4).genus() + 3 + sage: sandpiles.Cycle(5).genus() + 1 --- -.. _groebner(): +.. _groebner: **groebner()** - A Groebner basis for the homogeneous sandpile ideal with respect to - the standard sandpile ordering (see ``ring``). +A Groebner basis for the homogeneous toppling ideal. It is computed +with respect to the standard sandpile ordering (see ``ring``). - INPUT: +OUTPUT: - None +Groebner basis - OUTPUT: +EXAMPLES:: - Groebner basis + sage: S = sandpiles.Diamond() + sage: S.groebner() + [x3*x2^2 - x1^2*x0, x2^3 - x3*x1*x0, x3*x1^2 - x2^2*x0, x1^3 - x3*x2*x0, x3^2 - x0^2, x2*x1 - x0^2] - EXAMPLES:: +--- - sage: S = sandlib('generic') - sage: S.groebner() - [x4*x1^2 - x5*x0^2, x1^3 - x4*x3*x0, x5^2 - x3*x0, x4^2 - x3*x1, x5*x3 - x0^2, - x3^2 - x5*x0, x2 - x0] +.. _group_gens: ---- +**group_gens(verbose=True)** -.. _group_order(): +A minimal list of generators for the sandpile group. If ``verbose`` is ``False`` +then the generators are represented as lists of integers. -**group_order()** +INPUT: - The size of the sandpile group. +``verbose`` -- (default: ``True``) boolean - INPUT: +OUTPUT: - None +list of SandpileConfig (or of lists of integers if ``verbose`` is ``False``) - OUTPUT: +EXAMPLES:: - int + sage: s = sandpiles.Cycle(5) + sage: s.group_gens() + [{1: 1, 2: 1, 3: 1, 4: 0}] + sage: s.group_gens()[0].order() + 5 + sage: s = sandpiles.Complete(5) + sage: s.group_gens(False) + [[2, 2, 3, 2], [2, 3, 2, 2], [3, 2, 2, 2]] + sage: [i.order() for i in s.group_gens()] + [5, 5, 5] + sage: s.invariant_factors() + [1, 5, 5, 5] - EXAMPLES:: +--- + +.. _group_order: + +**group_order()** + +The size of the sandpile group. + +OUTPUT: + +integer - sage: S = sandlib('generic') - sage: S.group_order() - 15 +EXAMPLES:: + + sage: S = sandpiles.House() + sage: S.group_order() + 11 --- -.. _h_vector(): +.. _h_vector: **h_vector()** - The first differences of the Hilbert function of the homogeneous - sandpile ideal. It lists the number of superstable configurations - in each degree. - - INPUT: +The number of superstable configurations in each degree. Equivalently, +this is the list of first differences of the Hilbert function of the +(homogeneous) toppling ideal. - None +OUTPUT: - OUTPUT: +list of nonnegative integers - list of nonnegative integers - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.hilbert_function() - [1, 5, 11, 15] - sage: S.h_vector() - [1, 4, 6, 4] + sage: s = sandpiles.Grid(2,2) + sage: s.hilbert_function() + [1, 5, 15, 35, 66, 106, 146, 178, 192] + sage: s.h_vector() + [1, 4, 10, 20, 31, 40, 40, 32, 14] --- -.. _hilbert_function(): +.. _help: + +**help(verbose=True)** + +List of Sandpile-specific methods (not inherited from Graph). If ``verbose``, include short descriptions. + +INPUT: + +``verbose`` -- (default: ``True``) boolean + +OUTPUT: + +printed string + +EXAMPLES:: + + sage: Sandpile.help() + For detailed help with any method FOO listed below, + enter "Sandpile.FOO?" or enter "S.FOO?" for any Sandpile S. + + all_k_config -- The constant configuration with all values set to k. + all_k_div -- The divisor with all values set to k. + avalanche_polynomial -- The avalanche polynomial. + betti -- The Betti table for the homogeneous toppling ideal. + betti_complexes -- The support-complexes with non-trivial homology. + burning_config -- The minimal burning configuration. + burning_script -- A script for the minimal burning configuration. + canonical_divisor -- The canonical divisor. + dict -- A dictionary of dictionaries representing a directed graph. + genus -- The genus: (# non-loop edges) - (# vertices) + 1. + groebner -- A Groebner basis for the homogeneous toppling ideal. + group_gens -- A minimal list of generators for the sandpile group. + group_order -- The size of the sandpile group. + h_vector -- The number of superstable configurations in each degree. + help -- List of Sandpile-specific methods (not inherited from Graph). + hilbert_function -- The Hilbert function of the homogeneous toppling ideal. + ideal -- The saturated homogeneous toppling ideal. + identity -- The identity configuration. + in_degree -- The in-degree of a vertex or a list of all in-degrees. + invariant_factors -- The invariant factors of the sandpile group. + is_undirected -- Is the underlying graph undirected? + jacobian_representatives -- Representatives for the elements of the Jacobian group. + laplacian -- The Laplacian matrix of the graph. + markov_chain -- The sandpile Markov chain for configurations or divisors. + max_stable -- The maximal stable configuration. + max_stable_div -- The maximal stable divisor. + max_superstables -- The maximal superstable configurations. + min_recurrents -- The minimal recurrent elements. + nonsink_vertices -- The nonsink vertices. + nonspecial_divisors -- The nonspecial divisors. + out_degree -- The out-degree of a vertex or a list of all out-degrees. + picard_representatives -- Representatives of the divisor classes of degree d in the Picard group. + points -- Generators for the multiplicative group of zeros of the sandpile ideal. + postulation -- The postulation number of the toppling ideal. + recurrents -- The recurrent configurations. + reduced_laplacian -- The reduced Laplacian matrix of the graph. + reorder_vertices -- A copy of the sandpile with vertex names permuted. + resolution -- A minimal free resolution of the homogeneous toppling ideal. + ring -- The ring containing the homogeneous toppling ideal. + show -- Draw the underlying graph. + show3d -- Draw the underlying graph. + sink -- The sink vertex. + smith_form -- The Smith normal form for the Laplacian. + solve -- Approximations of the complex affine zeros of the sandpile ideal. + stable_configs -- Generator for all stable configurations. + stationary_density -- The stationary density of the sandpile. + superstables -- The superstable configurations. + symmetric_recurrents -- The symmetric recurrent configurations. + tutte_polynomial -- The Tutte polynomial. + unsaturated_ideal -- The unsaturated, homogeneous toppling ideal. + version -- The version number of Sage Sandpiles. + zero_config -- The all-zero configuration. + zero_div -- The all-zero divisor. -**hilbert_function()** +--- - The Hilbert function of the homogeneous sandpile ideal. +.. _hilbert_function: - INPUT: +**hilbert_function()** - None +The Hilbert function of the homogeneous toppling ideal. - OUTPUT: +OUTPUT: - list of nonnegative integers +list of nonnegative integers - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.hilbert_function() - [1, 5, 11, 15] + sage: s = sandpiles.Wheel(5) + sage: s.hilbert_function() + [1, 5, 15, 31, 45] + sage: s.h_vector() + [1, 4, 10, 16, 14] --- -.. _ideal(gens=False): +.. _ideal: **ideal(gens=False)** - The saturated, homogeneous sandpile ideal (or its generators if - ``gens=True``). +The saturated homogeneous toppling ideal. If ``gens`` is ``True``, the +generators for the ideal are returned instead. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``gens`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - ideal or, optionally, the generators of an ideal +ideal or, optionally, the generators of an ideal - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.ideal() - Ideal (x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, - x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2) of Multivariate Polynomial Ring - in x5, x4, x3, x2, x1, x0 over Rational Field - sage: S.ideal(True) - [x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, - x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2] - sage: S.ideal().gens() # another way to get the generators - [x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, - x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2] + sage: S = sandpiles.Diamond() + sage: S.ideal() + Ideal (x2*x1 - x0^2, x3^2 - x0^2, x1^3 - x3*x2*x0, x3*x1^2 - x2^2*x0, x2^3 - x3*x1*x0, x3*x2^2 - x1^2*x0) of Multivariate Polynomial Ring in x3, x2, x1, x0 over Rational Field + sage: S.ideal(True) + [x2*x1 - x0^2, x3^2 - x0^2, x1^3 - x3*x2*x0, x3*x1^2 - x2^2*x0, x2^3 - x3*x1*x0, x3*x2^2 - x1^2*x0] + sage: S.ideal().gens() # another way to get the generators + [x2*x1 - x0^2, x3^2 - x0^2, x1^3 - x3*x2*x0, x3*x1^2 - x2^2*x0, x2^3 - x3*x1*x0, x3*x2^2 - x1^2*x0] --- -.. _identity(): +.. _identity: -**identity()** +**identity(verbose=True)** - The identity configuration. +The identity configuration. If ``verbose`` is ``False``, the +configuration are converted to a list of integers. - INPUT: +INPUT: - None +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - dict (the identity configuration) +SandpileConfig or a list of integers If ``verbose`` is ``False``, the +configuration are converted to a list of integers. - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: e = S.identity() - sage: x = e & S.max_stable() # stable addition - sage: x - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} - sage: x == S.max_stable() - True + sage: s = sandpiles.Diamond() + sage: s.identity() + {1: 2, 2: 2, 3: 0} + sage: s.identity(False) + [2, 2, 0] + sage: s.identity() & s.max_stable() == s.max_stable() + True --- -.. _in_degree(v=None): +.. _in_degree: **in_degree(v=None)** - The in-degree of a vertex or a list of all in-degrees. +The in-degree of a vertex or a list of all in-degrees. - INPUT: +INPUT: - ``v`` - vertex name or None +``v`` -- (optional) vertex name - OUTPUT: +OUTPUT: - integer or dict +integer or dict - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.in_degree(2) - 2 - sage: S.in_degree() - {0: 2, 1: 1, 2: 2, 3: 4, 4: 1, 5: 2} + sage: s = sandpiles.House() + sage: s.in_degree() + {0: 2, 1: 2, 2: 3, 3: 3, 4: 2} + sage: s.in_degree(2) + 3 --- -.. _invariant_factors(): +.. _invariant_factors: **invariant_factors()** - The invariant factors of the sandpile group (a finite abelian - group). +The invariant factors of the sandpile group. - INPUT: +OUTPUT: - None +list of integers - OUTPUT: +EXAMPLES:: - list of integers + sage: s = sandpiles.Grid(2,2) + sage: s.invariant_factors() + [1, 1, 8, 24] - EXAMPLES:: +--- + +.. _is_undirected: + +**is_undirected()** + +Is the underlying graph undirected? ``True`` if `(u,v)` is and edge if +and only if `(v,u)` is an edge, each edge with the same weight. + +OUTPUT: + +boolean - sage: S = sandlib('generic') - sage: S.invariant_factors() - [1, 1, 1, 1, 15] +EXAMPLES:: + + sage: sandpiles.Complete(4).is_undirected() + True + sage: s = Sandpile({0:[1,2], 1:[0,2], 2:[0]}, 0) + sage: s.is_undirected() + False --- -.. _is_undirected(): +.. _jacobian_representatives: -**is_undirected()** +**jacobian_representatives(verbose=True)** - ``True`` if ``(u,v)`` is and edge if and only if ``(v,u)`` is an - edges, each edge with the same weight. +Representatives for the elements of the Jacobian group. If ``verbose`` +is ``False``, then lists representing the divisors are returned. - INPUT: +INPUT: - None +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - boolean +list of SandpileDivisor (or of lists representing divisors) - EXAMPLES:: +EXAMPLES: - sage: complete_sandpile(4).is_undirected() - True - sage: sandlib('gor').is_undirected() - False +For an undirected graph, divisors of the form ``s - deg(s)*sink`` as +``s`` varies over the superstables forms a distinct set of +representatives for the Jacobian group.:: + + sage: s = sandpiles.Complete(3) + sage: s.superstables(False) + [[0, 0], [0, 1], [1, 0]] + sage: s.jacobian_representatives(False) + [[0, 0, 0], [-1, 0, 1], [-1, 1, 0]] + +If the graph is directed, the representatives described above may by +equivalent modulo the rowspan of the Laplacian matrix:: + + sage: s = Sandpile({0: {1: 1, 2: 2}, 1: {0: 2, 2: 4}, 2: {0: 4, 1: 2}},0) + sage: s.group_order() + 28 + sage: s.jacobian_representatives() + [{0: -5, 1: 3, 2: 2}, {0: -4, 1: 3, 2: 1}] + +Let `\tau` be the nonnegative generator of the kernel of the transpose of +the Laplacian, and let `tau_s` be it sink component, then the sandpile +group is isomorphic to the direct sum of the cyclic group of order +`\tau_s` and the Jacobian group. In the example above, we have:: + + sage: s.laplacian().left_kernel() + Free module of degree 3 and rank 1 over Integer Ring + Echelon basis matrix: + [14 5 8] + +.. NOTE:: + + The Jacobian group is the set of all divisors of degree zero modulo the + integer rowspan of the Laplacian matrix. --- -.. _laplacian(): +.. _laplacian: **laplacian()** - The Laplacian matrix of the graph. +The Laplacian matrix of the graph. Its *rows* encode the vertex firing rules. - INPUT: +OUTPUT: - None +matrix - OUTPUT: - matrix +EXAMPLES:: - EXAMPLES:: + sage: G = sandpiles.Diamond() + sage: G.laplacian() + [ 2 -1 -1 0] + [-1 3 -1 -1] + [-1 -1 3 -1] + [ 0 -1 -1 2] + +.. WARNING:: - sage: G = sandlib('generic') - sage: G.laplacian() - [ 0 0 0 0 0 0] - [-1 3 0 -1 -1 0] - [-1 0 3 -1 0 -1] - [ 0 0 -1 2 0 -1] - [ 0 -1 0 -1 2 0] - [ 0 0 -1 -1 0 2] + The function ``laplacian_matrix`` should be avoided. It returns the + indegree version of the Laplacian. - NOTES: +--- - The function ``laplacian_matrix`` should be avoided. It returns - the indegree version of the laplacian. +.. _markov_chain: + +**markov_chain(state, distrib=None)** + +The sandpile Markov chain for configurations or divisors. +The chain starts at ``state``. See NOTE for details. + +INPUT: + +- ``state`` -- SandpileConfig, SandpileDivisor, or list representing one of these + +- ``distrib`` -- (optional) list of nonnegative numbers summing to 1 (representing a prob. dist.) + +OUTPUT: + +generator for Markov chain (see NOTE) + +EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: m = s.markov_chain([0,0,0]) + sage: m.next() # random + {1: 0, 2: 0, 3: 0} + sage: m.next().values() # random + [0, 0, 0] + sage: m.next().values() # random + [0, 0, 0] + sage: m.next().values() # random + [0, 0, 0] + sage: m.next().values() # random + [0, 1, 0] + sage: m.next().values() # random + [0, 2, 0] + sage: m.next().values() # random + [0, 2, 1] + sage: m.next().values() # random + [1, 2, 1] + sage: m.next().values() # random + [2, 2, 1] + sage: m = s.markov_chain(s.zero_div(), [0.1,0.1,0.1,0.7]) + sage: m.next().values() # random + [0, 0, 0, 1] + sage: m.next().values() # random + [0, 0, 1, 1] + sage: m.next().values() # random + [0, 0, 1, 2] + sage: m.next().values() # random + [1, 1, 2, 0] + sage: m.next().values() # random + [1, 1, 2, 1] + sage: m.next().values() # random + [1, 1, 2, 2] + sage: m.next().values() # random + [1, 1, 2, 3] + sage: m.next().values() # random + [1, 1, 2, 4] + sage: m.next().values() # random + [1, 1, 3, 4] + +.. NOTE:: + + The ``closed sandpile Markov chain`` has state space consisting of the configurations + on a sandpile. It transitions from a state by choosing a vertex at random + (according to the probability distribution ``distrib``), dropping a grain of sand at + that vertex, and stabilizing. If the chosen vertex is the sink, the chain stays + at the current state. + + The ``open sandpile Markov chain`` has state space consisting of the recurrent elements, + i.e., the state space is the sandpile group. It transitions from the configuration `c` + by choosing a vertex `v` at random according to ``distrib``. The next state is the + stabilization of `c+v`. If `v` is the sink vertex, then the stabilization of `c+v` + is defined to be `c`. + + Note that in either case, if ``distrib`` is specified, its length is equal to + the total number of vertices (including the sink). + +REFERENCES: + +.. [Levine2014] Lionel Levine. Threshold state and a conjecture of Poghosyan, Poghosyan, + Priezzhev and Ruelle, Communications in Mathematical Physics. --- -.. _max_stable(): +.. _max_stable: **max_stable()** - The maximal stable configuration. - - INPUT: +The maximal stable configuration. - None +OUTPUT: - OUTPUT: +SandpileConfig (the maximal stable configuration) - SandpileConfig (the maximal stable configuration) - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.max_stable() - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} + sage: S = sandpiles.House() + sage: S.max_stable() + {1: 1, 2: 2, 3: 2, 4: 1} --- -.. _max_stable_div(): +.. _max_stable_div: **max_stable_div()** - The maximal stable divisor. - - INPUT: - - SandpileDivisor +The maximal stable divisor. - OUTPUT: +OUTPUT: - SandpileDivisor (the maximal stable divisor) +SandpileDivisor (the maximal stable divisor) - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.max_stable_div() - {0: -1, 1: 2, 2: 2, 3: 1, 4: 1, 5: 1} - sage: S.out_degree() - {0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2} + sage: s = sandpiles.Diamond() + sage: s.max_stable_div() + {0: 1, 1: 2, 2: 2, 3: 1} + sage: s.out_degree() + {0: 2, 1: 3, 2: 3, 3: 2} --- -.. _max_superstables(verbose=True): +.. _max_superstables: **max_superstables(verbose=True)** - The maximal superstable configurations. If the underlying graph is - undirected, these are the superstables of highest degree. If - ``verbose`` is ``False``, the configurations are converted to lists - of integers. +The maximal superstable configurations. If the underlying graph is +undirected, these are the superstables of highest degree. If +``verbose`` is ``False``, the configurations are converted to lists of +integers. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list (of maximal superstables) +tuple of SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S=sandlib('riemann-roch2') - sage: S.max_superstables() - [{1: 1, 2: 1, 3: 1}, {1: 0, 2: 0, 3: 2}] - sage: S.superstables(False) - [[0, 0, 0], - [1, 0, 1], - [1, 0, 0], - [0, 1, 1], - [0, 1, 0], - [1, 1, 0], - [0, 0, 1], - [1, 1, 1], - [0, 0, 2]] - sage: S.h_vector() - [1, 3, 4, 1] + sage: s = sandpiles.Diamond() + sage: s.superstables(False) + [[0, 0, 0], + [0, 0, 1], + [1, 0, 1], + [0, 2, 0], + [2, 0, 0], + [0, 1, 1], + [1, 0, 0], + [0, 1, 0]] + sage: s.max_superstables(False) + [[1, 0, 1], [0, 2, 0], [2, 0, 0], [0, 1, 1]] + sage: s.h_vector() + [1, 3, 4] --- -.. _min_recurrents(verbose=True): +.. _min_recurrents: **min_recurrents(verbose=True)** - The minimal recurrent elements. If the underlying graph is - undirected, these are the recurrent elements of least degree. If - ``verbose is ``False``, the configurations are converted to lists - of integers. +The minimal recurrent elements. If the underlying graph is +undirected, these are the recurrent elements of least degree. +If ``verbose`` is ``False``, the configurations are converted +to lists of integers. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list of SandpileConfig +list of SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S=sandlib('riemann-roch2') - sage: S.min_recurrents() - [{1: 0, 2: 0, 3: 1}, {1: 1, 2: 1, 3: 0}] - sage: S.min_recurrents(False) - [[0, 0, 1], [1, 1, 0]] - sage: S.recurrents(False) - [[1, 1, 2], - [0, 1, 1], - [0, 1, 2], - [1, 0, 1], - [1, 0, 2], - [0, 0, 2], - [1, 1, 1], - [0, 0, 1], - [1, 1, 0]] - sage: [i.deg() for i in S.recurrents()] - [4, 2, 3, 2, 3, 2, 3, 1, 2] + sage: s = sandpiles.Diamond() + sage: s.recurrents(False) + [[2, 2, 1], + [2, 2, 0], + [1, 2, 0], + [2, 0, 1], + [0, 2, 1], + [2, 1, 0], + [1, 2, 1], + [2, 1, 1]] + sage: s.min_recurrents(False) + [[1, 2, 0], [2, 0, 1], [0, 2, 1], [2, 1, 0]] + sage: [i.deg() for i in s.recurrents()] + [5, 4, 3, 3, 3, 3, 4, 4] --- -.. _nonsink_vertices(): +.. _nonsink_vertices: **nonsink_vertices()** - The names of the nonsink vertices. - - INPUT: - - None +The nonsink vertices. - OUTPUT: +OUTPUT: - None +list of vertices - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.nonsink_vertices() - [1, 2, 3, 4, 5] + sage: s = sandpiles.Grid(2,3) + sage: s.nonsink_vertices() + [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)] --- -.. _nonspecial_divisors(verbose=True): +.. _nonspecial_divisors: **nonspecial_divisors(verbose=True)** - The nonspecial divisors: those divisors of degree ``g-1`` with - empty linear system. The term is only defined for undirected - graphs. Here, ``g = |E| - |V| + 1`` is the genus of the graph. If - ``verbose`` is ``False``, the divisors are converted to lists of - integers. +The nonspecial divisors. Only for undirected graphs. (See NOTE.) - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list (of divisors) +list (of divisors) - EXAMPLES:: +EXAMPLES:: - sage: S = complete_sandpile(4) - sage: ns = S.nonspecial_divisors() # optional - 4ti2 - sage: D = ns[0] # optional - 4ti2 - sage: D.values() # optional - 4ti2 - [-1, 1, 0, 2] - sage: D.deg() # optional - 4ti2 - 2 - sage: [i.effective_div() for i in ns] # optional - 4ti2 - [[], [], [], [], [], []] + sage: S = sandpiles.Complete(4) + sage: ns = S.nonspecial_divisors() + sage: D = ns[0] + sage: D.values() + [-1, 0, 1, 2] + sage: D.deg() + 2 + sage: [i.effective_div() for i in ns] + [[], [], [], [], [], []] ---- +.. NOTE:: -.. _num_edges(): + The "nonspecial divisors" are those divisors of degree `g-1` with + empty linear system. The term is only defined for undirected graphs. + Here, `g = |E| - |V| + 1` is the genus of the graph (not counted loops + as part of `|E|`). If ``verbose`` is ``False``, the divisors are converted + to lists of integers. -**num_edges()** +.. WARNING:: - The number of edges. + The underlying graph must be undirected. - EXAMPLES:: +--- - sage: G = graphs.PetersenGraph() - sage: G.size() - 15 +.. _out_degree: ---- +**out_degree(v=None)** -.. _num_verts(): +The out-degree of a vertex or a list of all out-degrees. -**num_verts()** +INPUT: - The number of vertices. Note that len(G) returns the number - of vertices in G also. +``v`` - (optional) vertex name - EXAMPLES:: +OUTPUT: - sage: G = graphs.PetersenGraph() - sage: G.order() - 10 +integer or dict - sage: G = graphs.TetrahedralGraph() - sage: len(G) - 4 +EXAMPLES:: + + sage: s = sandpiles.House() + sage: s.out_degree() + {0: 2, 1: 2, 2: 3, 3: 3, 4: 2} + sage: s.out_degree(2) + 3 --- -.. _out_degree(v=None): +.. _picard_representatives: -**out_degree(v=None)** +**picard_representatives(d, verbose=True)** - The out-degree of a vertex or a list of all out-degrees. +Representatives of the divisor classes of degree `d` in the Picard group. (Also +see the documentation for ``jacobian_representatives``.) - INPUT: +INPUT: - ``v`` (optional) - vertex name +- ``d`` -- integer - OUTPUT: +- ``verbose`` -- (default: ``True``) boolean - integer or dict +OUTPUT: - EXAMPLES:: +list of SandpileDivisors (or lists representing divisors) - sage: S = sandlib('generic') - sage: S.out_degree(2) - 3 - sage: S.out_degree() - {0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2} +EXAMPLES:: + + sage: s = sandpiles.Complete(3) + sage: s.superstables(False) + [[0, 0], [0, 1], [1, 0]] + sage: s.jacobian_representatives(False) + [[0, 0, 0], [-1, 0, 1], [-1, 1, 0]] + sage: s.picard_representatives(3,False) + [[3, 0, 0], [2, 0, 1], [2, 1, 0]] --- -.. _points(): +.. _points: **points()** - Generators for the multiplicative group of zeros of the sandpile - ideal. - - INPUT: - - None +Generators for the multiplicative group of zeros of the sandpile +ideal. - OUTPUT: +OUTPUT: - list of complex numbers +list of complex numbers - EXAMPLES: +EXAMPLES: - The sandpile group in this example is cyclic, and hence there is a - single generator for the group of solutions. +The sandpile group in this example is cyclic, and hence there is a +single generator for the group of solutions. - :: +:: - sage: S = sandlib('generic') - sage: S.points() - [[e^(4/5*I*pi), 1, e^(2/3*I*pi), e^(-34/15*I*pi), e^(-2/3*I*pi)]] + sage: S = sandpiles.Complete(4) + sage: S.points() + [[1, I, -I], [I, 1, -I]] --- -.. _postulation(): +.. _postulation: **postulation()** - The postulation number of the sandpile ideal. This is the largest - weight of a superstable configuration of the graph. - - INPUT: - - None +The postulation number of the toppling ideal. This is the +largest weight of a superstable configuration of the graph. - OUTPUT: +OUTPUT: - nonnegative integer +nonnegative integer - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.postulation() - 3 + sage: s = sandpiles.Complete(4) + sage: s.postulation() + 3 --- -.. _recurrents(verbose=True): +.. _recurrents: **recurrents(verbose=True)** - The list of recurrent configurations. If ``verbose`` is ``False``, - the configurations are converted to lists of integers. +The recurrent configurations. If ``verbose`` is ``False``, the +configurations are converted to lists of integers. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list (of recurrent configurations) +list of recurrent configurations - EXAMPLES:: - sage: S = sandlib('generic') - sage: S.recurrents() - [{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 1, 5: 1}, - {1: 0, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 0, 2: 2, 3: 1, 4: 1, 5: 1}, - {1: 1, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 2, 3: 0, 4: 1, 5: 1}, - {1: 2, 2: 2, 3: 1, 4: 0, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 0, 5: 1}, - {1: 2, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 1, 5: 0}, - {1: 1, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 0, 5: 1}, - {1: 0, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0}, - {1: 1, 2: 2, 3: 0, 4: 0, 5: 1}] - sage: S.recurrents(verbose=False) - [[2, 2, 1, 1, 1], [2, 2, 0, 1, 1], [0, 2, 1, 1, 0], [0, 2, 1, 1, 1], - [1, 2, 1, 1, 1], [1, 2, 0, 1, 1], [2, 2, 1, 0, 1], [2, 2, 0, 0, 1], - [2, 2, 1, 0, 0], [1, 2, 1, 1, 0], [1, 2, 1, 0, 0], [1, 2, 1, 0, 1], - [0, 2, 0, 1, 1], [2, 2, 1, 1, 0], [1, 2, 0, 0, 1]] +EXAMPLES:: + + sage: r = Sandpile(graphs.HouseXGraph(),0).recurrents() + sage: r[:3] + [{1: 2, 2: 3, 3: 3, 4: 1}, {1: 1, 2: 3, 3: 3, 4: 0}, {1: 1, 2: 3, 3: 3, 4: 1}] + sage: sandpiles.Complete(4).recurrents(False) + [[2, 2, 2], + [2, 2, 1], + [2, 1, 2], + [1, 2, 2], + [2, 2, 0], + [2, 0, 2], + [0, 2, 2], + [2, 1, 1], + [1, 2, 1], + [1, 1, 2], + [2, 1, 0], + [2, 0, 1], + [1, 2, 0], + [1, 0, 2], + [0, 2, 1], + [0, 1, 2]] + sage: sandpiles.Cycle(4).recurrents(False) + [[1, 1, 1], [0, 1, 1], [1, 0, 1], [1, 1, 0]] --- -.. _reduced_laplacian(): +.. _reduced_laplacian: **reduced_laplacian()** - The reduced Laplacian matrix of the graph. - - INPUT: +The reduced Laplacian matrix of the graph. - None +OUTPUT: - OUTPUT: +matrix - matrix - EXAMPLES:: +EXAMPLES:: - sage: G = sandlib('generic') - sage: G.laplacian() - [ 0 0 0 0 0 0] - [-1 3 0 -1 -1 0] - [-1 0 3 -1 0 -1] - [ 0 0 -1 2 0 -1] - [ 0 -1 0 -1 2 0] - [ 0 0 -1 -1 0 2] - sage: G.reduced_laplacian() - [ 3 0 -1 -1 0] - [ 0 3 -1 0 -1] - [ 0 -1 2 0 -1] - [-1 0 -1 2 0] - [ 0 -1 -1 0 2] + sage: S = sandpiles.Diamond() + sage: S.laplacian() + [ 2 -1 -1 0] + [-1 3 -1 -1] + [-1 -1 3 -1] + [ 0 -1 -1 2] + sage: S.reduced_laplacian() + [ 3 -1 -1] + [-1 3 -1] + [-1 -1 2] - NOTES: +.. NOTE:: - This is the Laplacian matrix with the row and column indexed by the - sink vertex removed. + This is the Laplacian matrix with the row and column indexed by the + sink vertex removed. --- -.. _reorder_vertices(): +.. _reorder_vertices: **reorder_vertices()** - Create a copy of the sandpile but with the vertices ordered - according to their distance from the sink, from greatest to least. +A copy of the sandpile with vertex names permuted. After reordering, +vertex `u` comes before vertex `v` in the list of vertices if `u` is +closer to the sink. - INPUT: +OUTPUT: - None +Sandpile - OUTPUT: +EXAMPLES:: - Sandpile + sage: S = Sandpile({0:[1], 2:[0,1], 1:[2]}) + sage: S.dict() + {0: {1: 1}, 1: {2: 1}, 2: {0: 1, 1: 1}} + sage: T = S.reorder_vertices() - EXAMPLES:: +The vertices 1 and 2 have been swapped:: - sage: S = sandlib('kite') - sage: S.dict() - {0: {}, 1: {0: 1, 2: 1, - 3: 1}, 2: {1: 1, 3: 1, 4: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {2: 1, - 3: 1}} - sage: T = S.reorder_vertices() - sage: T.dict() - {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1, 3: 1}, 2: {0: 1, 1: 1, 3: 1}, - 3: {1: 1, 2: 1, 4: 1}, 4: {}} + sage: T.dict() + {0: {1: 1}, 1: {0: 1, 2: 1}, 2: {0: 1}} --- -.. _resolution(verbose=False): +.. _resolution: **resolution(verbose=False)** - This function computes a minimal free resolution of the homogeneous - sandpile ideal. If ``verbose`` is ``True``, then all of the - mappings are returned. Otherwise, the resolution is summarized. +A minimal free resolution of the homogeneous toppling ideal. If +``verbose`` is ``True``, then all of the mappings are returned. +Otherwise, the resolution is summarized. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``verbose`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - free resolution of the sandpile ideal +free resolution of the toppling ideal - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('gor') - sage: S.resolution() - 'R^1 <-- R^5 <-- R^5 <-- R^1' - sage: S.resolution(True) - [ - [ x1^2 - x3*x0 x3*x1 - x2*x0 x3^2 - x2*x1 x2*x3 - x0^2 x2^2 - x1*x0], - [ x3 x2 0 x0 0] [ x2^2 - x1*x0] - [-x1 -x3 x2 0 -x0] [-x2*x3 + x0^2] - [ x0 x1 0 x2 0] [-x3^2 + x2*x1] - [ 0 0 -x1 -x3 x2] [x3*x1 - x2*x0] - [ 0 0 x0 x1 -x3], [ x1^2 - x3*x0] - ] - sage: r = S.resolution(True) - sage: r[0]*r[1] - [0 0 0 0 0] - sage: r[1]*r[2] - [0] - [0] - [0] - [0] - [0] - ---- - -.. _ring(): + sage: S = Sandpile({0: {}, 1: {0: 1, 2: 1, 3: 4}, 2: {3: 5}, 3: {1: 1, 2: 1}},0) + sage: S.resolution() # a Gorenstein sandpile graph + 'R^1 <-- R^5 <-- R^5 <-- R^1' + sage: S.resolution(True) + [ + [ x1^2 - x3*x0 x3*x1 - x2*x0 x3^2 - x2*x1 x2*x3 - x0^2 x2^2 - x1*x0], + + [ x3 x2 0 x0 0] [ x2^2 - x1*x0] + [-x1 -x3 x2 0 -x0] [-x2*x3 + x0^2] + [ x0 x1 0 x2 0] [-x3^2 + x2*x1] + [ 0 0 -x1 -x3 x2] [x3*x1 - x2*x0] + [ 0 0 x0 x1 -x3], [ x1^2 - x3*x0] + ] + sage: r = S.resolution(True) + sage: r[0]*r[1] + [0 0 0 0 0] + sage: r[1]*r[2] + [0] + [0] + [0] + [0] + [0] -**ring()** +--- - The ring containing the homogeneous sandpile ideal. +.. _ring: - INPUT: +**ring()** - None +The ring containing the homogeneous toppling ideal. - OUTPUT: +OUTPUT: - ring +ring - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.ring() - Multivariate Polynomial Ring in x5, x4, x3, x2, x1, x0 over Rational Field - sage: S.ring().gens() - (x5, x4, x3, x2, x1, x0) + sage: S = sandpiles.Diamond() + sage: S.ring() + Multivariate Polynomial Ring in x3, x2, x1, x0 over Rational Field + sage: S.ring().gens() + (x3, x2, x1, x0) - NOTES: +.. NOTE:: - The indeterminate xi corresponds to the i-th vertex as listed my - the method ``vertices``. The term-ordering is degrevlex with - indeterminates ordered according to their distance from the sink - (larger indeterminates are further from the sink). + The indeterminate ``xi`` corresponds to the `i`-th vertex as listed my + the method ``vertices``. The term-ordering is degrevlex with + indeterminates ordered according to their distance from the sink (larger + indeterminates are further from the sink). --- -.. _show(kwds): +.. _show: -**show(kwds)** +**show(**kwds)** - Draws the graph. +Draw the underlying graph. - INPUT: +INPUT: - ``kwds`` - arguments passed to the show method for Graph or DiGraph +``kwds`` -- (optional) arguments passed to the show method for Graph or DiGraph - OUTPUT: +EXAMPLES:: - None + sage: S = Sandpile({0:[], 1:[0,3,4], 2:[0,3,5], 3:[2,5], 4:[1,1], 5:[2,4]}) + sage: S.show() + sage: S.show(graph_border=True, edge_labels=True) - EXAMPLES:: +--- - sage: S = sandlib('generic') - sage: S.show() - sage: S.show(graph_border=True, edge_labels=True) +.. _show3d: ---- +**show3d(**kwds)** -.. _show3d(kwds): +Draw the underlying graph. -**show3d(kwds)** +INPUT: - Draws the graph. +``kwds`` -- (optional) arguments passed to the show method for Graph or DiGraph - INPUT: +EXAMPLES:: - ``kwds`` - arguments passed to the show method for Graph or DiGraph + sage: S = sandpiles.House() + sage: S.show3d() - OUTPUT: +--- - None +.. _sink: - EXAMPLES:: +**sink()** - sage: S = sandlib('generic') - sage: S.show3d() +The sink vertex. ---- +OUTPUT: -.. _sink(): +sink vertex -**sink()** +EXAMPLES:: - The identifier for the sink vertex. + sage: G = sandpiles.House() + sage: G.sink() + 0 + sage: H = sandpiles.Grid(2,2) + sage: H.sink() + (0, 0) + sage: type(H.sink()) + - INPUT: +--- - None +.. _smith_form: - OUTPUT: +**smith_form()** - Object (name for the sink vertex) +The Smith normal form for the Laplacian. In detail: a list of integer +matrices `D, U, V` such that `ULV = D` where `L` is the transpose of the +Laplacian, `D` is diagonal, and `U` and `V` are invertible over the +integers. - EXAMPLES:: +OUTPUT: + +list of integer matrices - sage: G = sandlib('generic') - sage: G.sink() - 0 - sage: H = grid_sandpile(2,2) - sage: H.sink() - 'sink' - sage: type(H.sink()) - +EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D,U,V = s.smith_form() + sage: D + [1 0 0 0] + [0 4 0 0] + [0 0 4 0] + [0 0 0 0] + sage: U*s.laplacian()*V == D # laplacian symmetric => tranpose not necessary + True --- -.. _solve(): +.. _solve: **solve()** - Approximations of the complex affine zeros of the sandpile ideal. +Approximations of the complex affine zeros of the sandpile +ideal. - INPUT: +OUTPUT: - None +list of complex numbers - OUTPUT: +EXAMPLES:: - list of complex numbers + sage: S = Sandpile({0: {}, 1: {2: 2}, 2: {0: 4, 1: 1}}, 0) + sage: S.solve() + [[-0.707107 + 0.707107*I, 0.707107 - 0.707107*I], [-0.707107 - 0.707107*I, 0.707107 + 0.707107*I], [-I, -I], [I, I], [0.707107 + 0.707107*I, -0.707107 - 0.707107*I], [0.707107 - 0.707107*I, -0.707107 + 0.707107*I], [1, 1], [-1, -1]] + sage: len(_) + 8 + sage: S.group_order() + 8 - EXAMPLES:: +.. NOTE:: - sage: S = Sandpile({0: {}, 1: {2: 2}, 2: {0: 4, 1: 1}}, 0) - sage: S.solve() - [[-0.707107 + 0.707107*I, 0.707107 - 0.707107*I], - [-0.707107 - 0.707107*I, 0.707107 + 0.707107*I], - [-I, -I], [I, I], [0.707107 + 0.707107*I, -0.707107 - 0.707107*I], - [0.707107 - 0.707107*I, -0.707107 + 0.707107*I], [1, 1], [-1, -1]] - sage: len(_) - 8 - sage: S.group_order() - 8 + The solutions form a multiplicative group isomorphic to the sandpile + group. Generators for this group are given exactly by ``points()``. - NOTES: +--- - The solutions form a multiplicative group isomorphic to the - sandpile group. Generators for this group are given exactly by - ``points()``. +.. _stable_configs: ---- +**stable_configs(smax=None)** -.. _superstables(verbose=True): +Generator for all stable configurations. If ``smax`` is provided, then +the generator gives all stable configurations less than or equal to +``smax``. If ``smax`` does not represent a stable configuration, then each +component of ``smax`` is replaced by the corresponding component of the +maximal stable configuration. -**superstables(verbose=True)** +INPUT: - The list of superstable configurations as dictionaries if - ``verbose`` is ``True``, otherwise as lists of integers. The - superstables are also known as G-parking functions. +``smax`` -- (optional) SandpileConfig or list representing a SandpileConfig - INPUT: - ``verbose`` (optional) - boolean +OUTPUT: - OUTPUT: +generator for all stable configurations - list (of superstable elements) +EXAMPLES:: - EXAMPLES:: + sage: s = sandpiles.Complete(3) + sage: a = s.stable_configs() + sage: a.next() + {1: 0, 2: 0} + sage: [i.values() for i in a] + [[0, 1], [1, 0], [1, 1]] + sage: b = s.stable_configs([1,0]) + sage: list(b) + [{1: 0, 2: 0}, {1: 1, 2: 0}] - sage: S = sandlib('generic') - sage: S.superstables() - [{1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 2, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 2, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 1, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 1, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 1, 5: 0}, - {1: 0, 2: 0, 3: 1, 4: 1, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 1, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 1, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 1, 5: 0}, - {1: 2, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 1, 2: 0, 3: 1, 4: 1, 5: 0}] - sage: S.superstables(False) - [[0, 0, 0, 0, 0], - [0, 0, 1, 0, 0], - [2, 0, 0, 0, 1], - [2, 0, 0, 0, 0], - [1, 0, 0, 0, 0], - [1, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 1, 1, 0], - [0, 0, 0, 1, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 1, 1], - [1, 0, 0, 1, 0], - [2, 0, 1, 0, 0], - [0, 0, 0, 0, 1], - [1, 0, 1, 1, 0]] - ---- - -.. _symmetric_recurrents(orbits): +--- -**symmetric_recurrents(orbits)** +.. _stationary_density: - The list of symmetric recurrent configurations. +**stationary_density()** - INPUT: +The stationary density of the sandpile. - ``orbits`` - list of lists partitioning the vertices +OUTPUT: - OUTPUT: +rational number - list of recurrent configurations +EXAMPLES:: - EXAMPLES:: + sage: s = sandpiles.Complete(3) + sage: s.stationary_density() + 10/9 + sage: s = Sandpile(digraphs.DeBruijn(2,2),'00') + sage: s.stationary_density() + 9/8 + +.. NOTE:: - sage: S = sandlib('kite') - sage: S.dict() - {0: {}, - 1: {0: 1, 2: 1, 3: 1}, - 2: {1: 1, 3: 1, 4: 1}, - 3: {1: 1, 2: 1, 4: 1}, - 4: {2: 1, 3: 1}} - sage: S.symmetric_recurrents([[1],[2,3],[4]]) - [{1: 2, 2: 2, 3: 2, 4: 1}, {1: 2, 2: 2, 3: 2, 4: 0}] - sage: S.recurrents() - [{1: 2, 2: 2, 3: 2, 4: 1}, - {1: 2, 2: 2, 3: 2, 4: 0}, - {1: 2, 2: 1, 3: 2, 4: 0}, - {1: 2, 2: 2, 3: 0, 4: 1}, - {1: 2, 2: 0, 3: 2, 4: 1}, - {1: 2, 2: 2, 3: 1, 4: 0}, - {1: 2, 2: 1, 3: 2, 4: 1}, - {1: 2, 2: 2, 3: 1, 4: 1}] + The stationary density of a sandpile is the sum `\sum_c (\deg(c) + \deg(s))` + where `\deg(s)` is the degree of the sink and the sum is over all + recurrent configurations. - NOTES: +REFERENCES: - The user is responsible for ensuring that the list of orbits comes - from a group of symmetries of the underlying graph. +.. [Levine2014]_ Lionel Levine. Threshold state and a conjecture of Poghosyan, Poghosyan, + Priezzhev and Ruelle, Communications in Mathematical Physics. --- -.. _unsaturated_ideal(): +.. _superstables: -**unsaturated_ideal()** +**superstables(verbose=True)** - The unsaturated, homogeneous sandpile ideal. +The superstable configurations. If ``verbose`` is ``False``, the +configurations are converted to lists of integers. Superstables for +undirected graphs are also known as ``G-parking functions``. - INPUT: +INPUT: - None +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - ideal +list of SandpileConfig - EXAMPLES:: - sage: S = sandlib('generic') - sage: S.unsaturated_ideal().gens() - [x1^3 - x4*x3*x0, x2^3 - x5*x3*x0, x3^2 - x5*x2, x4^2 - x3*x1, x5^2 - x3*x2] - sage: S.ideal().gens() - [x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, - x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2] +EXAMPLES:: + + sage: sp = Sandpile(graphs.HouseXGraph(),0).superstables() + sage: sp[:3] + [{1: 0, 2: 0, 3: 0, 4: 0}, {1: 1, 2: 0, 3: 0, 4: 1}, {1: 1, 2: 0, 3: 0, 4: 0}] + sage: sandpiles.Complete(4).superstables(False) + [[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [1, 0, 0], + [0, 0, 2], + [0, 2, 0], + [2, 0, 0], + [0, 1, 1], + [1, 0, 1], + [1, 1, 0], + [0, 1, 2], + [0, 2, 1], + [1, 0, 2], + [1, 2, 0], + [2, 0, 1], + [2, 1, 0]] + sage: sandpiles.Cycle(4).superstables(False) + [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]] --- -.. _version(): +.. _symmetric_recurrents: -**version()** +**symmetric_recurrents(orbits)** - The version number of Sage Sandpiles. +The symmetric recurrent configurations. - INPUT: +INPUT: - None +``orbits`` - list of lists partitioning the vertices - OUTPUT: +OUTPUT: - string +list of recurrent configurations - EXAMPLES:: +EXAMPLES:: + + sage: S = Sandpile({0: {}, + ....: 1: {0: 1, 2: 1, 3: 1}, + ....: 2: {1: 1, 3: 1, 4: 1}, + ....: 3: {1: 1, 2: 1, 4: 1}, + ....: 4: {2: 1, 3: 1}}) + sage: S.symmetric_recurrents([[1],[2,3],[4]]) + [{1: 2, 2: 2, 3: 2, 4: 1}, {1: 2, 2: 2, 3: 2, 4: 0}] + sage: S.recurrents() + [{1: 2, 2: 2, 3: 2, 4: 1}, + {1: 2, 2: 2, 3: 2, 4: 0}, + {1: 2, 2: 1, 3: 2, 4: 0}, + {1: 2, 2: 2, 3: 0, 4: 1}, + {1: 2, 2: 0, 3: 2, 4: 1}, + {1: 2, 2: 2, 3: 1, 4: 0}, + {1: 2, 2: 1, 3: 2, 4: 1}, + {1: 2, 2: 2, 3: 1, 4: 1}] - sage: S = sandlib('generic') - sage: S.version() - Sage Sandpiles Version 2.3 +.. NOTE:: + + The user is responsible for ensuring that the list of orbits comes from + a group of symmetries of the underlying graph. --- -.. _vertices(key=None,boundary_first=False): +.. _tutte_polynomial: -**vertices(key=None, boundary_first=False)** +**tutte_polynomial()** - A list of the vertices. +The Tutte polynomial. Only defined for undirected sandpile graphs. - INPUT: +OUTPUT: - * ``key`` - default: ``None`` - a function that takes a vertex as - its one argument and returns a value that can be used for - comparisons in the sorting algorithm. +polynomial - * ``boundary_first`` - default: ``False`` - if ``True``, return - the boundary vertices first. +EXAMPLES:: - OUTPUT: + sage: s = sandpiles.Complete(4) + sage: s.tutte_polynomial() + x^3 + y^3 + 3*x^2 + 4*x*y + 3*y^2 + 2*x + 2*y + sage: s.tutte_polynomial().subs(x=1) + y^3 + 3*y^2 + 6*y + 6 + sage: s.tutte_polynomial().subs(x=1).coefficients() == s.h_vector() + True - The vertices of the list. +--- - Warning: There is always an attempt to sort the list before returning the result. However, since any object may be a vertex, there is no guarantee that any two vertices will be comparable. With default objects for vertices (all integers), or when all the vertices are of the same simple type, then there should not be a problem with how the vertices will be sorted. However, if you need to guarantee a total order for the sort, use the ``key`` argument, as illustrated in the examples below. +.. _unsaturated_ideal: - EXAMPLES:: +**unsaturated_ideal()** - sage: P = graphs.PetersenGraph() - sage: P.vertices() - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +The unsaturated, homogeneous toppling ideal. ---- +OUTPUT: -.. _zero_config(): +ideal -**zero_config()** +EXAMPLES:: - The all-zero configuration. + sage: S = sandpiles.Diamond() + sage: S.unsaturated_ideal().gens() + [x1^3 - x3*x2*x0, x2^3 - x3*x1*x0, x3^2 - x2*x1] + sage: S.ideal().gens() + [x2*x1 - x0^2, x3^2 - x0^2, x1^3 - x3*x2*x0, x3*x1^2 - x2^2*x0, x2^3 - x3*x1*x0, x3*x2^2 - x1^2*x0] - INPUT: +--- - None +.. _version: - OUTPUT: +**version()** - SandpileConfig +The version number of Sage Sandpiles. - EXAMPLES:: +OUTPUT: - sage: S = sandlib('generic') - sage: S.zero_config() - {1: 0, 2: 0, 3: 0, 4: 0, 5: 0} +string + + +EXAMPLES:: + + sage: Sandpile.version() + Sage Sandpiles Version 2.4 + sage: S = sandpiles.Complete(3) + sage: S.version() + Sage Sandpiles Version 2.4 --- -.. _zero_div(): +.. _zero_config: -**zero_div()** +**zero_config()** - The all-zero divisor. +The all-zero configuration. - INPUT: +OUTPUT: - None +SandpileConfig - OUTPUT: +EXAMPLES:: - SandpileDivisor + sage: s = sandpiles.Diamond() + sage: s.zero_config() + {1: 0, 2: 0, 3: 0} - EXAMPLES:: +--- + +.. _zero_div: + +**zero_div()** + +The all-zero divisor. + +OUTPUT: - sage: S = sandlib('generic') - sage: S.zero_div() - {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0} +SandpileDivisor + +EXAMPLES:: + + sage: S = sandpiles.House() + sage: S.zero_div() + {0: 0, 1: 0, 2: 0, 3: 0, 4: 0} --- @@ -2328,43 +2591,47 @@ SandpileConfig - :ref:`- <->` --- Subtraction of configurations. -- :ref:`add_random() ` --- Add one grain of sand to a random nonsink vertex. +- :ref:`add_random ` --- Add one grain of sand to a random vertex. + +- :ref:`burst_size ` --- The burst size of the configuration with respect to the given vertex. -- :ref:`deg() ` --- The degree of the configuration. +- :ref:`deg ` --- The degree of the configuration. -- :ref:`dualize() ` --- The difference between the maximal stable configuration and the configuration. +- :ref:`dualize ` --- The difference with the maximal stable configuration. -- :ref:`equivalent_recurrent(with_firing_vector=False) ` --- The equivalent recurrent configuration. +- :ref:`equivalent_recurrent ` --- The recurrent configuration equivalent to the given configuration. -- :ref:`equivalent_superstable(with_firing_vector=False) ` --- The equivalent superstable configuration. +- :ref:`equivalent_superstable ` --- The equivalent superstable configuration. -- :ref:`fire_script(sigma) ` --- Fire the script ``sigma``, i.e., fire each vertex the indicated number of times. +- :ref:`fire_script ` --- Fire the given script. -- :ref:`fire_unstable() ` --- Fire all unstable vertices. +- :ref:`fire_unstable ` --- Fire all unstable vertices. -- :ref:`fire_vertex(v) ` --- Fire the vertex ``v``. +- :ref:`fire_vertex ` --- Fire the given vertex. -- :ref:`is_recurrent() ` --- ``True`` if the configuration is recurrent. +- :ref:`help ` --- List of SandpileConfig methods. -- :ref:`is_stable() ` --- ``True`` if stable. +- :ref:`is_recurrent ` --- Is the configuration recurrent? -- :ref:`is_superstable() ` --- ``True`` if ``config`` is superstable. +- :ref:`is_stable ` --- Is the configuration stable? -- :ref:`is_symmetric(orbits) ` --- Is the configuration constant over the vertices in each sublist of ``orbits``? +- :ref:`is_superstable ` --- Is the configuration superstable? -- :ref:`order() ` --- The order of the recurrent element equivalent to ``config``. +- :ref:`is_symmetric ` --- Is the configuration symmetric? -- :ref:`sandpile() ` --- The configuration's underlying sandpile. +- :ref:`order ` --- The order of the equivalent recurrent element. -- :ref:`show(sink=True,colors=True,heights=False,directed=None,kwds) ` --- Show the configuration. +- :ref:`sandpile ` --- The configuration's underlying sandpile. -- :ref:`stabilize(with_firing_vector=False) ` --- The stabilized configuration. Optionally returns the corresponding firing vector. +- :ref:`show ` --- Show the configuration. -- :ref:`support() ` --- Keys of the nonzero values of the dictionary. +- :ref:`stabilize ` --- The stabilized configuration. -- :ref:`unstable() ` --- List of the unstable vertices. +- :ref:`support ` --- The vertices containing sand. -- :ref:`values() ` --- The values of the configuration as a list. +- :ref:`unstable ` --- The unstable vertices. + +- :ref:`values ` --- The values of the configuration as a list. -------- @@ -2380,7 +2647,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2388,7 +2655,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [3,2]) sage: c + d @@ -2404,7 +2671,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2412,7 +2679,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,0,0]) sage: c + c # ordinary addition {1: 2, 2: 0, 3: 0} @@ -2434,7 +2701,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2442,7 +2709,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) sage: e = SandpileConfig(S, [2,0]) @@ -2468,7 +2735,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2476,7 +2743,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [1,3]) sage: c > c @@ -2494,20 +2761,14 @@ SandpileConfig The stabilized configuration. - INPUT: - - None - OUTPUT: ``SandpileConfig`` EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: c = S.max_stable() + S.identity() - sage: ~c - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} sage: ~c == c.stabilize() True @@ -2522,7 +2783,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2530,7 +2791,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) sage: e = SandpileConfig(S, [2,0]) @@ -2556,7 +2817,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2564,6 +2825,15 @@ SandpileConfig EXAMPLES:: + sage: S = sandpiles.Cycle(3) + sage: c = SandpileConfig(S, [1,2]) + sage: d = SandpileConfig(S, [2,3]) + sage: c < c + False + sage: c < d + True + sage: d < c + False sage: S = Sandpile(graphs.CycleGraph(3), 0) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) @@ -2580,11 +2850,13 @@ SandpileConfig **\*** - The recurrent element equivalent to the sum. + If ``other`` is an configuration, the recurrent element equivalent + to the sum. If ``other`` is an integer, the sum of configuration with + itself ``other`` times. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig or Integer OUTPUT: @@ -2592,7 +2864,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,0,0]) sage: c + c # ordinary addition {1: 2, 2: 0, 3: 0} @@ -2604,6 +2876,10 @@ SandpileConfig True sage: c*(-c) == S.identity() True + sage: c + {1: 1, 2: 0, 3: 0} + sage: c*3 + {1: 3, 2: 0, 3: 0} --- @@ -2611,14 +2887,14 @@ SandpileConfig **^** - The recurrent element equivalent to the sum of the configuration - with itself ``k`` times. If ``k`` is negative, do the same for the - negation of the configuration. If ``k`` is zero, return the - identity of the sandpile group. + The recurrent element equivalent to the sum of the + configuration with itself `k` times. If `k` is negative, do the + same for the negation of the configuration. If `k` is zero, return + the identity of the sandpile group. INPUT: - ``k`` - SandpileConfig + ``k`` -- SandpileConfig OUTPUT: @@ -2626,18 +2902,18 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: c = SandpileConfig(S, [1,0,0]) - sage: c^3 - {1: 1, 2: 1, 3: 0} - sage: (c + c + c) == c^3 - False - sage: (c + c + c).equivalent_recurrent() == c^3 - True - sage: c^(-1) - {1: 1, 2: 1, 3: 0} - sage: c^0 == S.identity() - True + sage: S = sandpiles.Cycle(4) + sage: c = SandpileConfig(S, [1,0,0]) + sage: c^3 + {1: 1, 2: 1, 3: 0} + sage: (c + c + c) == c^3 + False + sage: (c + c + c).equivalent_recurrent() == c^3 + True + sage: c^(-1) + {1: 1, 2: 1, 3: 0} + sage: c^0 == S.identity() + True --- @@ -2647,17 +2923,13 @@ SandpileConfig The additive inverse of the configuration. - INPUT: - - None - OUTPUT: SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: -c {1: -1, 2: -2} @@ -2672,7 +2944,7 @@ SandpileConfig INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2680,7 +2952,7 @@ SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [3,2]) sage: c - d @@ -2688,536 +2960,608 @@ SandpileConfig --- -.. _add_random(): +.. _add_random-config: -**add_random()** +**add_random(distrib=None)** - Add one grain of sand to a random nonsink vertex. +Add one grain of sand to a random vertex. Optionally, a probability +distribution, ``distrib``, may be placed on the vertices or the nonsink vertices. +See NOTE for details. - INPUT: +INPUT: - None +``distrib`` -- (optional) list of nonnegative numbers summing to 1 (representing a prob. dist.) - OUTPUT: +OUTPUT: - SandpileConfig +SandpileConfig + +EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: c = s.zero_config() + sage: c.add_random() # random + {1: 0, 2: 1, 3: 0} + sage: c + {1: 0, 2: 0, 3: 0} + sage: c.add_random([0.1,0.1,0.8]) # random + {1: 0, 2: 0, 3: 1} + sage: c.add_random([0.7,0.1,0.1,0.1]) # random + {1: 0, 2: 0, 3: 0} + +We compute the "sizes" of the avalanches caused by adding random grains +of sand to the maximal stable configuration on a grid graph. The +function ``stabilize()`` returns the firing vector of the +stabilization, a dictionary whose values say how many times each vertex +fires in the stabilization.:: + + sage: S = sandpiles.Grid(10,10) + sage: m = S.max_stable() + sage: a = [] + sage: for i in range(1000): + ... m = m.add_random() + ... m, f = m.stabilize(True) + ... a.append(sum(f.values())) + ... + sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)]) + sage: p.axes_labels(['log(N)','log(D(N))']) + sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0)) + sage: show(p+t,axes_labels=['log(N)','log(D(N))']) - EXAMPLES: +.. NOTE:: - We compute the 'sizes' of the avalanches caused by adding random - grains of sand to the maximal stable configuration on a grid graph. - The function ``stabilize()`` returns the firing vector of the - stabilization, a dictionary whose values say how many times each - vertex fires in the stabilization. + If ``distrib`` is ``None``, then the probability is the uniform probability on the nonsink + vertices. Otherwise, there are two possibilities: - :: + (i) the length of ``distrib`` is equal to the number of vertices, and ``distrib`` represents + a probability distribution on all of the vertices. In that case, the sink may be chosen + at random, in which case, the configuration is unchanged. - sage: S = grid_sandpile(10,10) - sage: m = S.max_stable() - sage: a = [] - sage: for i in range(1000): - ....: m = m.add_random() - ....: m, f = m.stabilize(True) - ....: a.append(sum(f.values())) - ... - sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)]) - sage: p.axes_labels(['log(N)','log(D(N))']) - sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0)) - sage: show(p+t,axes_labels=['log(N)','log(D(N))']) + (ii) Otherwise, the length of ``distrib`` must be equal to the number of nonsink vertices, + and ``distrib`` represents a probability distribution on the nonsink vertices. + +.. WARNING:: + + If ``distrib != None``, the user is responsible for assuring the sum of its entries is + 1 and that its length is equal to the number of sink vertices or the number of nonsink vertices. --- -.. _deg(): +.. _burst_size-config: -**deg()** +**burst_size(v)** - The degree of the configuration. +The burst size of the configuration with respect to the given vertex. - INPUT: +INPUT: - None +``v`` -- vertex - OUTPUT: +OUTPUT: - integer +integer - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: c = SandpileConfig(S, [1,2]) - sage: c.deg() - 3 + sage: s = sandpiles.Diamond() + sage: [i.burst_size(0) for i in s.recurrents()] + [1, 1, 1, 1, 1, 1, 1, 1] + sage: [i.burst_size(1) for i in s.recurrents()] + [0, 0, 1, 2, 1, 2, 0, 2] + +.. NOTE:: + + To define ``c.burst(v)``, if `v` is not the sink, let `c'` be the unique + recurrent for which the the stabilization of `c' + v` is `c`. The + burst size is then the amount of sand that goes into the sink during this + stabilization. If `v` is the sink, the burst size is defined to be 1. + +REFERENCES: + +.. [Levine2014]_ Lionel Levine. Threshold state and a conjecture of Poghosyan, Poghosyan, + Priezzhev and Ruelle, Communications in Mathematical Physics. --- -.. _dualize(): +.. _deg-config: -**dualize()** +**deg()** - The difference between the maximal stable configuration and the - configuration. +The degree of the configuration. - INPUT: +OUTPUT: - None +integer - OUTPUT: +EXAMPLES:: - SandpileConfig + sage: S = sandpiles.Complete(3) + sage: c = SandpileConfig(S, [1,2]) + sage: c.deg() + 3 - EXAMPLES:: +--- - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: c = SandpileConfig(S, [1,2]) - sage: S.max_stable() - {1: 1, 2: 1} - sage: c.dualize() - {1: 0, 2: -1} - sage: S.max_stable() - c == c.dualize() - True +.. _dualize-config: + +**dualize()** + +The difference with the maximal stable configuration. + +OUTPUT: + +SandpileConfig + +EXAMPLES:: + + sage: S = sandpiles.Cycle(3) + sage: c = SandpileConfig(S, [1,2]) + sage: S.max_stable() + {1: 1, 2: 1} + sage: c.dualize() + {1: 0, 2: -1} + sage: S.max_stable() - c == c.dualize() + True --- -.. _equivalent_recurrent(with_firing_vector=False): +.. _equivalent_recurrent-config: **equivalent_recurrent(with_firing_vector=False)** - The recurrent configuration equivalent to the given configuration. - Optionally returns the corresponding firing vector. +The recurrent configuration equivalent to the given configuration. +Optionally, return the corresponding firing vector. - INPUT: +INPUT: - ``with_firing_vector`` (optional) - boolean +``with_firing_vector`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - ``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` +SandpileConfig or [SandpileConfig, firing_vector] - EXAMPLES:: - sage: S = sandlib('generic') - sage: c = SandpileConfig(S, [0,0,0,0,0]) - sage: c.equivalent_recurrent() == S.identity() - True - sage: x = c.equivalent_recurrent(True) - sage: r = vector([x[0][v] for v in S.nonsink_vertices()]) - sage: f = vector([x[1][v] for v in S.nonsink_vertices()]) - sage: cv = vector(c.values()) - sage: r == cv - f*S.reduced_laplacian() - True +EXAMPLES:: + + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S, [0,0,0]) + sage: c.equivalent_recurrent() == S.identity() + True + sage: x = c.equivalent_recurrent(True) + sage: r = vector([x[0][v] for v in S.nonsink_vertices()]) + sage: f = vector([x[1][v] for v in S.nonsink_vertices()]) + sage: cv = vector(c.values()) + sage: r == cv - f*S.reduced_laplacian() + True - NOTES: +.. NOTE:: - Let L be the reduced laplacian, c the initial configuration, r the - returned configuration, and f the firing vector. Then r = c - - f * L. + Let `L` be the reduced Laplacian, `c` the initial configuration, `r` the + returned configuration, and `f` the firing vector. Then `r = c - f\cdot + L`. --- -.. _equivalent_superstable(with_firing_vector=False): +.. _equivalent_superstable-config: **equivalent_superstable(with_firing_vector=False)** - The equivalent superstable configuration. Optionally returns the - corresponding firing vector. +The equivalent superstable configuration. Optionally, return the +corresponding firing vector. - INPUT: +INPUT: - ``with_firing_vector`` (optional) - boolean +``with_firing_vector`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - ``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` +SandpileConfig or [SandpileConfig, firing_vector] - EXAMPLES:: - sage: S = sandlib('generic') - sage: m = S.max_stable() - sage: m.equivalent_superstable().is_superstable() - True - sage: x = m.equivalent_superstable(True) - sage: s = vector(x[0].values()) - sage: f = vector(x[1].values()) - sage: mv = vector(m.values()) - sage: s == mv - f*S.reduced_laplacian() - True +EXAMPLES:: + + sage: S = sandpiles.Diamond() + sage: m = S.max_stable() + sage: m.equivalent_superstable().is_superstable() + True + sage: x = m.equivalent_superstable(True) + sage: s = vector(x[0].values()) + sage: f = vector(x[1].values()) + sage: mv = vector(m.values()) + sage: s == mv - f*S.reduced_laplacian() + True - NOTES: +.. NOTE:: - Let L be the reduced laplacian, c the initial configuration, s the - returned configuration, and f the firing vector. Then s = c - - f * L. + Let `L` be the reduced Laplacian, `c` the initial configuration, `s` the + returned configuration, and `f` the firing vector. Then `s = c - f\cdot + L`. --- -.. _fire_script(sigma): +.. _fire_script-config: **fire_script(sigma)** - Fire the script ``sigma``, i.e., fire each vertex the indicated - number of times. +Fire the given script. In other words, fire each vertex the number of +times indicated by ``sigma``. - INPUT: +INPUT: - ``sigma`` - SandpileConfig or (list or dict representing a SandpileConfig) +``sigma`` -- SandpileConfig or (list or dict representing a SandpileConfig) - OUTPUT: +OUTPUT: - SandpileConfig +SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: c = SandpileConfig(S, [1,2,3]) - sage: c.unstable() - [2, 3] - sage: c.fire_script(SandpileConfig(S,[0,1,1])) - {1: 2, 2: 1, 3: 2} - sage: c.fire_script(SandpileConfig(S,[2,0,0])) == c.fire_vertex(1).fire_vertex(1) - True + sage: S = sandpiles.Cycle(4) + sage: c = SandpileConfig(S, [1,2,3]) + sage: c.unstable() + [2, 3] + sage: c.fire_script(SandpileConfig(S,[0,1,1])) + {1: 2, 2: 1, 3: 2} + sage: c.fire_script(SandpileConfig(S,[2,0,0])) == c.fire_vertex(1).fire_vertex(1) + True --- -.. _fire_unstable(): +.. _fire_unstable-config: **fire_unstable()** - Fire all unstable vertices. - - INPUT: - - None +Fire all unstable vertices. - OUTPUT: +OUTPUT: - SandpileConfig +SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: c = SandpileConfig(S, [1,2,3]) - sage: c.fire_unstable() - {1: 2, 2: 1, 3: 2} + sage: S = sandpiles.Cycle(4) + sage: c = SandpileConfig(S, [1,2,3]) + sage: c.fire_unstable() + {1: 2, 2: 1, 3: 2} --- -.. _fire_vertex(v): +.. _fire_vertex-config: **fire_vertex(v)** - Fire the vertex ``v``. +Fire the given vertex. - INPUT: +INPUT: - ``v`` - vertex +``v`` -- vertex - OUTPUT: +OUTPUT: - SandpileConfig +SandpileConfig - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: c = SandpileConfig(S, [1,2]) - sage: c.fire_vertex(2) - {1: 2, 2: 0} + sage: S = sandpiles.Cycle(3) + sage: c = SandpileConfig(S, [1,2]) + sage: c.fire_vertex(2) + {1: 2, 2: 0} --- -.. _is_recurrent(): +.. _help-config: + +**help(verbose=True)** + +List of SandpileConfig methods. If ``verbose``, include short descriptions. + +INPUT: + +``verbose`` -- (default: ``True``) boolean + +OUTPUT: + +printed string + +EXAMPLES:: + + sage: SandpileConfig.help() + Shortcuts for SandpileConfig operations: + ~c -- stabilize + c & d -- add and stabilize + c * c -- add and find equivalent recurrent + c^k -- add k times and find equivalent recurrent + (taking inverse if k is negative) + + For detailed help with any method FOO listed below, + enter "SandpileConfig.FOO?" or enter "c.FOO?" for any SandpileConfig c. + + add_random -- Add one grain of sand to a random vertex. + burst_size -- The burst size of the configuration with respect to the given vertex. + deg -- The degree of the configuration. + dualize -- The difference with the maximal stable configuration. + equivalent_recurrent -- The recurrent configuration equivalent to the given configuration. + equivalent_superstable -- The equivalent superstable configuration. + fire_script -- Fire the given script. + fire_unstable -- Fire all unstable vertices. + fire_vertex -- Fire the given vertex. + help -- List of SandpileConfig methods. + is_recurrent -- Is the configuration recurrent? + is_stable -- Is the configuration stable? + is_superstable -- Is the configuration superstable? + is_symmetric -- Is the configuration symmetric? + order -- The order of the equivalent recurrent element. + sandpile -- The configuration's underlying sandpile. + show -- Show the configuration. + stabilize -- The stabilized configuration. + support -- The vertices containing sand. + unstable -- The unstable vertices. + values -- The values of the configuration as a list. -**is_recurrent()** +--- - ``True`` if the configuration is recurrent. +.. _is_recurrent-config: - INPUT: +**is_recurrent()** - None +Is the configuration recurrent? - OUTPUT: +OUTPUT: - boolean +boolean - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.identity().is_recurrent() - True - sage: S.zero_config().is_recurrent() - False + sage: S = sandpiles.Diamond() + sage: S.identity().is_recurrent() + True + sage: S.zero_config().is_recurrent() + False --- -.. _is_stable(): +.. _is_stable-config: **is_stable()** - ``True`` if stable. - - INPUT: - - None +Is the configuration stable? - OUTPUT: +OUTPUT: - boolean +boolean - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.max_stable().is_stable() - True - sage: (S.max_stable() + S.max_stable()).is_stable() - False - sage: (S.max_stable() & S.max_stable()).is_stable() - True + sage: S = sandpiles.Diamond() + sage: S.max_stable().is_stable() + True + sage: (2*S.max_stable()).is_stable() + False + sage: (S.max_stable() & S.max_stable()).is_stable() + True --- -.. _is_superstable(): +.. _is_superstable-config: **is_superstable()** - ``True`` if ``config`` is superstable, i.e., whether its dual is - recurrent. +Is the configuration superstable? - INPUT: - - None - - OUTPUT: +OUTPUT: - boolean +boolean - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: S.zero_config().is_superstable() - True + sage: S = sandpiles.Diamond() + sage: S.zero_config().is_superstable() + True --- -.. _is_symmetric(orbits): +.. _is_symmetric-config: **is_symmetric(orbits)** - This function checks if the values of the configuration are - constant over the vertices in each sublist of ``orbits``. +Is the configuration symmetric? Return ``True`` if the values of the +configuration are constant over the vertices in each sublist of +``orbits``. - INPUT: +INPUT: - ``orbits`` - list of lists of vertices + ``orbits`` -- list of lists of vertices - OUTPUT: +OUTPUT: - boolean +boolean - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('kite') - sage: S.dict() - {0: {}, - 1: {0: 1, 2: 1, 3: 1}, - 2: {1: 1, 3: 1, 4: 1}, - 3: {1: 1, 2: 1, 4: 1}, - 4: {2: 1, 3: 1}} - sage: c = SandpileConfig(S, [1, 2, 2, 3]) - sage: c.is_symmetric([[2,3]]) - True + sage: S = Sandpile({0: {}, + ....: 1: {0: 1, 2: 1, 3: 1}, + ....: 2: {1: 1, 3: 1, 4: 1}, + ....: 3: {1: 1, 2: 1, 4: 1}, + ....: 4: {2: 1, 3: 1}}) + sage: c = SandpileConfig(S, [1, 2, 2, 3]) + sage: c.is_symmetric([[2,3]]) + True --- -.. _order(): +.. _order-config: **order()** - The order of the recurrent element equivalent to ``config``. - - INPUT: - - ``config`` - configuration +The order of the equivalent recurrent element. - OUTPUT: +OUTPUT: - integer +integer - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: [r.order() for r in S.recurrents()] - [3, 3, 5, 15, 15, 15, 5, 15, 15, 5, 15, 5, 15, 1, 15] + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S,[2,0,1]) + sage: c.order() + 4 + sage: ~(c + c + c + c) == S.identity() + True + sage: c = SandpileConfig(S,[1,1,0]) + sage: c.order() + 1 + sage: c.is_recurrent() + False + sage: c.equivalent_recurrent() == S.identity() + True --- -.. _sandpile(): +.. _sandpile-config: **sandpile()** - The configuration's underlying sandpile. - - INPUT: - - None +The configuration's underlying sandpile. - OUTPUT: +OUTPUT: - Sandpile +Sandpile - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('genus2') - sage: c = S.identity() - sage: c.sandpile() - Digraph on 4 vertices - sage: c.sandpile() == S - True + sage: S = sandpiles.Diamond() + sage: c = S.identity() + sage: c.sandpile() + Diamond sandpile graph: 4 vertices, sink = 0 + sage: c.sandpile() == S + True --- -.. _show(sink=True,colors=True,heights=False,directed=None,kwds): - -**show(sink=True,colors=True,heights=False,directed=None,kwds)** - - Show the configuration. +.. _show-config: - INPUT: +**show(sink=True, colors=True, heights=False, directed=None, **kwds)** - * ``sink`` - whether to show the sink +Show the configuration. - * ``colors`` - whether to color-code the amount of sand on each - vertex +INPUT: - * ``heights`` - whether to label each vertex with the amount of - sand +- ``sink`` -- (default: ``True``) whether to show the sink - * ``kwds`` - arguments passed to the show method for Graph +- ``colors`` -- (default: ``True``) whether to color-code the amount of sand on each vertex - * ``directed`` - whether to draw directed edges +- ``heights`` -- (default: ``False``) whether to label each vertex with the amount of sand - OUTPUT: +- ``directed`` -- (optional) whether to draw directed edges - None +- ``kwds`` -- (optional) arguments passed to the show method for Graph - EXAMPLES:: +EXAMPLES:: - sage: S=sandlib('genus2') - sage: c=S.identity() - sage: S=sandlib('genus2') - sage: c=S.identity() - sage: c.show() - sage: c.show(directed=False) - sage: c.show(sink=False,colors=False,heights=True) + sage: S = sandpiles.Diamond() + sage: c = S.identity() + sage: c.show() + sage: c.show(directed=False) + sage: c.show(sink=False,colors=False,heights=True) --- -.. _stabilize(with_firing_vector=False): +.. _stabilize-config: **stabilize(with_firing_vector=False)** - The stabilized configuration. Optionally returns the corresponding - firing vector. +The stabilized configuration. Optionally returns the +corresponding firing vector. - INPUT: +INPUT: - ``with_firing_vector`` (optional) - boolean +``with_firing_vector`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - ``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` +``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: c = S.max_stable() + S.identity() - sage: c.stabilize(True) - [{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 5, 3: 7, 4: 1, 5: 6}] - sage: S.max_stable() & S.identity() - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} - sage: S.max_stable() & S.identity() == c.stabilize() - True - sage: ~c - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} + sage: S = sandpiles.House() + sage: c = 2*S.max_stable() + sage: c._set_stabilize() + sage: '_stabilize' in c.__dict__ + True + sage: S = sandpiles.House() + sage: c = S.max_stable() + S.identity() + sage: c.stabilize(True) + [{1: 1, 2: 2, 3: 2, 4: 1}, {1: 2, 2: 2, 3: 3, 4: 3}] + sage: S.max_stable() & S.identity() == c.stabilize() + True + sage: ~c == c.stabilize() + True --- -.. _support(): +.. _support-config: **support()** - The input is a dictionary of integers. The output is a list of - keys of nonzero values of the dictionary. +The vertices containing sand. - INPUT: +OUTPUT: - None +list - support of the configuration - OUTPUT: - - list - support of the config +EXAMPLES:: - EXAMPLES:: - - sage: S = sandlib('generic') - sage: c = S.identity() - sage: c.values() - [2, 2, 1, 1, 0] - sage: c.support() - [1, 2, 3, 4] - sage: S.vertices() - [0, 1, 2, 3, 4, 5] + sage: S = sandpiles.Diamond() + sage: c = S.identity() + sage: c + {1: 2, 2: 2, 3: 0} + sage: c.support() + [1, 2] --- -.. _unstable(): +.. _unstable-config: **unstable()** - List of the unstable vertices. +The unstable vertices. - INPUT: - - None +OUTPUT: - OUTPUT: +list of vertices - list of vertices - - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: c = SandpileConfig(S, [1,2,3]) - sage: c.unstable() - [2, 3] + sage: S = sandpiles.Cycle(4) + sage: c = SandpileConfig(S, [1,2,3]) + sage: c.unstable() + [2, 3] --- -.. _values(): +.. _values-config: **values()** - The values of the configuration as a list, sorted in the order of - the vertices. +The values of the configuration as a list. The list is sorted in the +order of the vertices. - INPUT: - - None +OUTPUT: - OUTPUT: +list of integers - list of integers - - boolean +boolean - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a') - sage: c = SandpileConfig(S, {'b':1, 1:2}) - sage: c - {1: 2, 'b': 1} - sage: c.values() - [2, 1] - sage: S.nonsink_vertices() - [1, 'b'] + sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a') + sage: c = SandpileConfig(S, {'b':1, 1:2}) + sage: c + {1: 2, 'b': 1} + sage: c.values() + [2, 1] + sage: S.nonsink_vertices() + [1, 'b'] --- @@ -3240,41 +3584,65 @@ SandpileDivisor - :ref:`- ` --- Subtraction of divisors. -- :ref:`add_random() ` --- Add one grain of sand to a random vertex. +- :ref:`Dcomplex ` --- The support-complex. + +- :ref:`add_random ` --- Add one grain of sand to a random vertex. + +- :ref:`betti ` --- The Betti numbers for the support-complex. -- :ref:`betti() ` --- The Betti numbers for the simplicial complex associated with the divisor. +- :ref:`deg ` --- The degree of the divisor. -- :ref:`Dcomplex() ` --- The simplicial complex determined by the supports of the linearly equivalent effective divisors. +- :ref:`dualize ` --- The difference with the maximal stable divisor. -- :ref:`deg() ` --- The degree of the divisor. +- :ref:`effective_div ` --- All linearly equivalent effective divisors. -- :ref:`dualize() ` --- The difference between the maximal stable divisor and the divisor. +- :ref:`fire_script ` --- Fire the given script. -- :ref:`effective_div(verbose=True) ` --- All linearly equivalent effective divisors. +- :ref:`fire_unstable ` --- Fire all unstable vertices. -- :ref:`fire_script(sigma) ` --- Fire the script ``sigma``, i.e., fire each vertex the indicated number of times. +- :ref:`fire_vertex ` --- Fire the given vertex. -- :ref:`fire_unstable() ` --- Fire all unstable vertices. +- :ref:`help ` --- List of SandpileDivisor methods. -- :ref:`fire_vertex(v) ` --- Fire the vertex ``v``. +- :ref:`is_alive ` --- Is the divisor stabilizable? -- :ref:`is_alive(cycle=False) ` --- Will the divisor stabilize under repeated firings of all unstable vertices? +- :ref:`is_linearly_equivalent ` --- Is the given divisor linearly equivalent? -- :ref:`is_symmetric(orbits) ` --- Is the divisor constant over the vertices in each sublist of ``orbits``? +- :ref:`is_q_reduced ` --- Is the divisor q-reduced? -- :ref:`linear_system() ` --- The complete linear system of a divisor. +- :ref:`is_symmetric ` --- Is the divisor symmetric? -- :ref:`r_of_D(verbose=False) ` --- Returns ``r(D)``. +- :ref:`is_weierstrass_pt ` --- Is the given vertex a Weierstrass point? -- :ref:`sandpile() ` --- The divisor's underlying sandpile. +- :ref:`polytope ` --- The polytope determinining the complete linear system. -- :ref:`show(heights=True,directed=None,kwds) ` --- Show the divisor. +- :ref:`polytope_integer_pts ` --- The integer points inside divisor's polytope. -- :ref:`support() ` --- List of keys of the nonzero values of the divisor. +- :ref:`q_reduced ` --- The linearly equivalent q-reduced divisor. -- :ref:`unstable() ` --- List of the unstable vertices. +- :ref:`rank ` --- The rank of the divisor. -- :ref:`values() ` --- The values of the divisor as a list, sorted in the order of the vertices. +- :ref:`sandpile ` --- The divisor's underlying sandpile. + +- :ref:`show ` --- Show the divisor. + +- :ref:`simulate_threshold ` --- The first unstabilizable divisor in the closed Markov chain. + +- :ref:`stabilize ` --- The stabilization of the divisor. + +- :ref:`support ` --- List of vertices at which the divisor is nonzero. + +- :ref:`unstable ` --- The unstable vertices. + +- :ref:`values ` --- The values of the divisor as a list. + +- :ref:`weierstrass_div ` --- The Weierstrass divisor. + +- :ref:`weierstrass_gap_seq ` --- The Weierstrass gap sequence at the given vertex. + +- :ref:`weierstrass_pts ` --- The Weierstrass points (vertices). + +- :ref:`weierstrass_rank_seq ` --- The Weierstrass rank sequence at the given vertex. -------- @@ -3290,7 +3658,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3298,7 +3666,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [3,2,1]) sage: D + E @@ -3315,7 +3683,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3323,7 +3691,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: F = SandpileDivisor(S, [2,0,4]) @@ -3349,7 +3717,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3357,7 +3725,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [1,3,4]) sage: D > D @@ -3378,7 +3746,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3386,7 +3754,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: F = SandpileDivisor(S, [2,0,4]) @@ -3412,7 +3780,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3420,7 +3788,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: D < D @@ -3438,17 +3806,13 @@ SandpileDivisor The additive inverse of the divisor. - INPUT: - - None - OUTPUT: SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: -D {0: -1, 1: -2, 2: -3} @@ -3463,7 +3827,7 @@ SandpileDivisor INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3471,7 +3835,7 @@ SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [3,2,1]) sage: D - E @@ -3479,908 +3843,1010 @@ SandpileDivisor --- -.. _add_random()-divisor: +.. _Dcomplex-divisor: -**add_random()** +**Dcomplex()** - Add one grain of sand to a random vertex. +The support-complex. (See NOTE.) - INPUT: +OUTPUT: - None +simplicial complex - OUTPUT: +EXAMPLES:: - SandpileDivisor + sage: S = sandpiles.House() + sage: p = SandpileDivisor(S, [1,2,1,0,0]).Dcomplex() + sage: p.homology() + {0: 0, 1: Z x Z, 2: 0} + sage: p.f_vector() + [1, 5, 10, 4] + sage: p.betti() + {0: 1, 1: 2, 2: 0} - EXAMPLES:: +.. NOTE:: - sage: S = sandlib('generic') - sage: S.zero_div().add_random() #random - {0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0} + The "support-complex" is the simplicial complex determined by the + supports of the linearly equivalent effective divisors. --- -.. _betti()-divisor: +.. _add_random-divisor: -**betti()** +**add_random(distrib=None)** - The Betti numbers for the simplicial complex associated with the - divisor. +Add one grain of sand to a random vertex. - INPUT: +INPUT: - None +``distrib`` -- (optional) list of nonnegative numbers representing a probability distribution on the vertices - OUTPUT: +OUTPUT: - dictionary of integers +SandpileDivisor - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [2,0,1]) - sage: D.betti() # optional - 4ti2 - {0: 1, 1: 1} + sage: s = sandpiles.Complete(4) + sage: D = s.zero_div() + sage: D.add_random() # random + {0: 0, 1: 0, 2: 1, 3: 0} + sage: D.add_random([0.1,0.1,0.1,0.7]) # random + {0: 0, 1: 0, 2: 0, 3: 1} + +.. WARNING:: + + If ``distrib`` is not ``None``, the user is responsible for assuring the sum of its entries is 1. --- -.. _Dcomplex(): +.. _betti-divisor: -**Dcomplex()** +**betti()** - The simplicial complex determined by the supports of the linearly - equivalent effective divisors. +The Betti numbers for the support-complex. (See NOTE.) - INPUT: +OUTPUT: - None +dictionary of integers - OUTPUT: +EXAMPLES:: - simplicial complex + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [2,0,1]) + sage: D.betti() + {0: 1, 1: 1} - EXAMPLES:: +.. NOTE:: - sage: S = sandlib('generic') - sage: p = SandpileDivisor(S, [0,1,2,0,0,1]).Dcomplex() # optional - 4ti2 - sage: p.homology() # optional - 4ti2 - {0: 0, 1: Z x Z, 2: 0, 3: 0} - sage: p.f_vector() # optional - 4ti2 - [1, 6, 15, 9, 1] - sage: p.betti() # optional - 4ti2 - {0: 1, 1: 2, 2: 0, 3: 0} + The "support-complex" is the simplicial complex determined by the + supports of the linearly equivalent effective divisors. --- -.. _deg()-divisor: +.. _deg-divisor: **deg()** - The degree of the divisor. +The degree of the divisor. - INPUT: - - None +OUTPUT: - OUTPUT: +integer - integer - - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.deg() - 6 + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.deg() + 6 --- -.. _dualize()-divisor: +.. _dualize-divisor: **dualize()** - The difference between the maximal stable divisor and the divisor. +The difference with the maximal stable divisor. - INPUT: +OUTPUT: - None - - OUTPUT: +SandpileDivisor - SandpileDivisor +EXAMPLES:: + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.dualize() + {0: 0, 1: -1, 2: -2} + sage: S.max_stable_div() - D == D.dualize() + True - EXAMPLES:: +--- - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.dualize() - {0: 0, 1: -1, 2: -2} - sage: S.max_stable_div() - D == D.dualize() - True +.. _effective_div-divisor: + +**effective_div(verbose=True, with_firing_vectors=False)** + +All linearly equivalent effective divisors. If ``verbose`` +is ``False``, the divisors are converted to lists of integers. +If ``with_firing_vectors`` is ``True`` then a list of firing vectors +is also given, each of which prescribes the vertices to be fired +in order to obtain an effective divisor. + +INPUT: + +- ``verbose`` -- (default: ``True``) boolean + +- ``with_firing_vectors`` -- (default: ``False``) boolean + +OUTPUT: + +list (of divisors) + +EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D.effective_div() + [{0: 0, 1: 6, 2: 0, 3: 0}, + {0: 0, 1: 2, 2: 4, 3: 0}, + {0: 0, 1: 2, 2: 0, 3: 4}, + {0: 1, 1: 3, 2: 1, 3: 1}, + {0: 2, 1: 0, 2: 2, 3: 2}, + {0: 4, 1: 2, 2: 0, 3: 0}] + sage: D.effective_div(False) + [[0, 6, 0, 0], + [0, 2, 4, 0], + [0, 2, 0, 4], + [1, 3, 1, 1], + [2, 0, 2, 2], + [4, 2, 0, 0]] + sage: D.effective_div(with_firing_vectors=True) + [({0: 0, 1: 6, 2: 0, 3: 0}, (0, -2, -1, -1)), + ({0: 0, 1: 2, 2: 4, 3: 0}, (0, -1, -2, -1)), + ({0: 0, 1: 2, 2: 0, 3: 4}, (0, -1, -1, -2)), + ({0: 1, 1: 3, 2: 1, 3: 1}, (0, -1, -1, -1)), + ({0: 2, 1: 0, 2: 2, 3: 2}, (0, 0, -1, -1)), + ({0: 4, 1: 2, 2: 0, 3: 0}, (0, 0, 0, 0))] + sage: a = _[0] + sage: a[0].values() + [0, 6, 0, 0] + sage: vector(D.values()) - s.laplacian()*a[1] + (0, 6, 0, 0) + sage: D.effective_div(False, True) + [([0, 6, 0, 0], (0, -2, -1, -1)), + ([0, 2, 4, 0], (0, -1, -2, -1)), + ([0, 2, 0, 4], (0, -1, -1, -2)), + ([1, 3, 1, 1], (0, -1, -1, -1)), + ([2, 0, 2, 2], (0, 0, -1, -1)), + ([4, 2, 0, 0], (0, 0, 0, 0))] + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.effective_div(False,True) + [] --- -.. _effective_div(verbose=True): +.. _fire_script-divisor: -**effective_div(verbose=True)** +**fire_script(sigma)** - All linearly equivalent effective divisors. If ``verbose`` is - ``False``, the divisors are converted to lists of integers. +Fire the given script. In other words, fire each vertex the number of +times indicated by ``sigma``. - INPUT: +INPUT: - ``verbose`` (optional) - boolean +``sigma`` -- SandpileDivisor or (list or dict representing a SandpileDivisor) - OUTPUT: +OUTPUT: - list (of divisors) +SandpileDivisor - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) # optional - 4ti2 - sage: D.effective_div() # optional - 4ti2 - [{0: 1, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0}, - {0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}, - {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 2}] - sage: D.effective_div(False) # optional - 4ti2 - [[1, 0, 0, 1, 0, 0], [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 2]] + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.unstable() + [1, 2] + sage: D.fire_script([0,1,1]) + {0: 3, 1: 1, 2: 2} + sage: D.fire_script(SandpileDivisor(S,[2,0,0])) == D.fire_vertex(0).fire_vertex(0) + True --- -.. _fire_script(sigma)-divisor: +.. _fire_unstable-divisor: -**fire_script(sigma)** +**fire_unstable()** - Fire the script ``sigma``, i.e., fire each vertex the indicated - number of times. +Fire all unstable vertices. - INPUT: +OUTPUT: - ``sigma`` - SandpileDivisor or (list or dict representing a SandpileDivisor) +SandpileDivisor - OUTPUT: +EXAMPLES:: - SandpileDivisor + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.fire_unstable() + {0: 3, 1: 1, 2: 2} - EXAMPLES:: +--- - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.unstable() - [1, 2] - sage: D.fire_script([0,1,1]) - {0: 3, 1: 1, 2: 2} - sage: D.fire_script(SandpileDivisor(S,[2,0,0])) == D.fire_vertex(0).fire_vertex(0) - True +.. _fire_vertex-divisor: ---- +**fire_vertex(v)** -.. _fire_unstable()-divisor: +Fire the given vertex. -**fire_unstable()** +INPUT: - Fire all unstable vertices. +``v`` -- vertex - INPUT: +OUTPUT: - None +SandpileDivisor - OUTPUT: +EXAMPLES:: - SandpileDivisor + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.fire_vertex(1) + {0: 2, 1: 0, 2: 4} - EXAMPLES:: +--- - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.fire_unstable() - {0: 3, 1: 1, 2: 2} +.. _help-divisor: + +**help(verbose=True)** + +List of SandpileDivisor methods. If ``verbose``, include short descriptions. + +INPUT: + +``verbose`` -- (default: ``True``) boolean + +OUTPUT: + +printed string + +EXAMPLES:: + + sage: SandpileDivisor.help() + For detailed help with any method FOO listed below, + enter "SandpileDivisor.FOO?" or enter "D.FOO?" for any SandpileDivisor D. + + Dcomplex -- The support-complex. + add_random -- Add one grain of sand to a random vertex. + betti -- The Betti numbers for the support-complex. + deg -- The degree of the divisor. + dualize -- The difference with the maximal stable divisor. + effective_div -- All linearly equivalent effective divisors. + fire_script -- Fire the given script. + fire_unstable -- Fire all unstable vertices. + fire_vertex -- Fire the given vertex. + help -- List of SandpileDivisor methods. + is_alive -- Is the divisor stabilizable? + is_linearly_equivalent -- Is the given divisor linearly equivalent? + is_q_reduced -- Is the divisor q-reduced? + is_symmetric -- Is the divisor symmetric? + is_weierstrass_pt -- Is the given vertex a Weierstrass point? + linear_system -- The complete linear system (deprecated: use "polytope_integer_pts"). + polytope -- The polytope determinining the complete linear system. + polytope_integer_pts -- The integer points inside divisor's polytope. + q_reduced -- The linearly equivalent q-reduced divisor. + r_of_D -- The rank of the divisor (deprecated: use "rank", instead). + rank -- The rank of the divisor. + sandpile -- The divisor's underlying sandpile. + show -- Show the divisor. + simulate_threshold -- The first unstabilizable divisor in the closed Markov chain. + stabilize -- The stabilization of the divisor. + support -- List of vertices at which the divisor is nonzero. + unstable -- The unstable vertices. + values -- The values of the divisor as a list. + weierstrass_div -- The Weierstrass divisor. + weierstrass_gap_seq -- The Weierstrass gap sequence at the given vertex. + weierstrass_pts -- The Weierstrass points (vertices). + weierstrass_rank_seq -- The Weierstrass rank sequence at the given vertex. --- -.. _fire_vertex(v)-divisor: +.. _is_alive-divisor: -**fire_vertex(v)** +**is_alive(cycle=False)** - Fire the vertex ``v``. +Is the divisor stabilizable? In other words, will the divisor stabilize +under repeated firings of all unstable vertices? Optionally returns the +resulting cycle. - INPUT: +INPUT: - ``v`` - vertex +``cycle`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - SandpileDivisor +boolean or optionally, a list of SandpileDivisors - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.fire_vertex(1) - {0: 2, 1: 0, 2: 4} + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2}) + sage: D.is_alive() + True + sage: D.is_alive(True) + [{0: 4, 1: 3, 2: 3, 3: 2}, {0: 3, 1: 2, 2: 2, 3: 5}, {0: 1, 1: 4, 2: 4, 3: 3}] --- -.. _is_alive(cycle=False): +.. _is_linearly_equivalent-divisor: -**is_alive(cycle=False)** +**is_linearly_equivalent(D, with_firing_vector=False)** - Will the divisor stabilize under repeated firings of all unstable - vertices? Optionally returns the resulting cycle. +Is the given divisor linearly equivalent? Optionally, returns the +firing vector. (See NOTE.) - INPUT: +INPUT: - ``cycle`` (optional) - boolean +- ``D`` -- SandpileDivisor or list, tuple, etc. representing a divisor - OUTPUT: +- ``with_firing_vector`` -- (default: ``False``) boolean - boolean or optionally, a list of SandpileDivisors +OUTPUT: - EXAMPLES:: +boolean or integer vector - sage: S = complete_sandpile(4) - sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2}) - sage: D.is_alive() - True - sage: D.is_alive(True) - [{0: 4, 1: 3, 2: 3, 3: 2}, {0: 3, 1: 2, 2: 2, 3: 5}, {0: 1, 1: 4, 2: 4, 3: 3}] +EXAMPLES:: ---- + sage: s = sandpiles.Complete(3) + sage: D = SandpileDivisor(s,[2,0,0]) + sage: D.is_linearly_equivalent([0,1,1]) + True + sage: D.is_linearly_equivalent([0,1,1],True) + (1, 0, 0) + sage: v = vector(D.is_linearly_equivalent([0,1,1],True)) + sage: vector(D.values()) - s.laplacian()*v + (0, 1, 1) + sage: D.is_linearly_equivalent([0,0,0]) + False + sage: D.is_linearly_equivalent([0,0,0],True) + () -.. _is_symmetric(orbits)-divisor: +.. NOTE:: -**is_symmetric(orbits)** + - If ``with_firing_vector`` is ``False``, returns either ``True`` or ``False``. - This function checks if the values of the divisor are constant over - the vertices in each sublist of ``orbits``. + - If ``with_firing_vector`` is ``True`` then: (i) if ``self`` is linearly + equivalent to `D`, returns a vector `v` such that ``self - v*self.laplacian().transpose() = D``. + Otherwise, (ii) if ``self`` is not linearly equivalent to `D`, the output is the empty vector, ``()``. - INPUT: +--- - * ``orbits`` - list of lists of vertices +.. _is_q_reduced-divisor: - OUTPUT: +**is_q_reduced()** - boolean +Is the divisor `q`-reduced? This would mean that `self = c + kq` where +`c` is superstable, `k` is an integer, and `q` is the sink vertex. - EXAMPLES:: +OUTPUT: - sage: S = sandlib('kite') - sage: S.dict() - {0: {}, - 1: {0: 1, 2: 1, 3: 1}, - 2: {1: 1, 3: 1, 4: 1}, - 3: {1: 1, 2: 1, 4: 1}, - 4: {2: 1, 3: 1}} - sage: D = SandpileDivisor(S, [2,1, 2, 2, 3]) - sage: D.is_symmetric([[0,2,3]]) - True +boolean ---- +EXAMPLES:: -.. _linear_system(): + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[2,-3,2,0]) + sage: D.is_q_reduced() + False + sage: SandpileDivisor(s,[10,0,1,2]).is_q_reduced() + True -**linear_system()** +For undirected or, more generally, Eulerian graphs, `q`-reduced divisors are +linearly equivalent if and only if they are equal. The same does not hold for +general directed graphs: - The complete linear system of a divisor. +:: - INPUT: None + sage: s = Sandpile({0:[1],1:[1,1]}) + sage: D = SandpileDivisor(s,[-1,1]) + sage: Z = s.zero_div() + sage: D.is_q_reduced() + True + sage: Z.is_q_reduced() + True + sage: D == Z + False + sage: D.is_linearly_equivalent(Z) + True - OUTPUT: +--- - dict - ``{num_homog: int, homog:list, num_inhomog:int, - inhomog:list}`` +.. _is_symmetric-divisor: - EXAMPLES:: +**is_symmetric(orbits)** - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) - sage: D.linear_system() # optional - 4ti2 - {'inhomog': [[0, 0, -1, -1, 0, -2], [0, 0, 0, 0, 0, -1], [0, 0, 0, 0, 0, 0]], - 'num_inhomog': 3, 'num_homog': 2, 'homog': [[1, 0, 0, 0, 0, 0], - [-1, 0, 0, 0, 0, 0]]} +Is the divisor symmetric? Return ``True`` if the values of the +configuration are constant over the vertices in each sublist of +``orbits``. - NOTES: +INPUT: - If L is the Laplacian, an arbitrary v such that v * L>= -D has - the form v = w + t where w is in ``inhomg`` and t is in the integer - span of ``homog`` in the output of ``linear_system(D)``. +``orbits`` -- list of lists of vertices - WARNING: +OUTPUT: - This method requires 4ti2. +boolean ---- +EXAMPLES:: -.. _r_of_D(verbose=False): + sage: S = sandpiles.House() + sage: S.dict() + {0: {1: 1, 2: 1}, + 1: {0: 1, 3: 1}, + 2: {0: 1, 3: 1, 4: 1}, + 3: {1: 1, 2: 1, 4: 1}, + 4: {2: 1, 3: 1}} + sage: D = SandpileDivisor(S, [0,0,1,1,3]) + sage: D.is_symmetric([[2,3], [4]]) + True -**r_of_D(verbose=False)** +--- - Returns ``r(D)`` and, if ``verbose`` is ``True``, an effective - divisor ``F`` such that ``|D - F|`` is empty. +.. _is_weierstrass_pt-divisor: - INPUT: +**is_weierstrass_pt(v='sink')** - ``verbose`` (optional) - boolean +Is the given vertex a Weierstrass point? - OUTPUT: +INPUT: - integer ``r(D)`` or tuple (integer ``r(D)``, divisor ``F``) +``v`` -- (default: ``sink``) vertex - EXAMPLES:: +OUTPUT: - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2 - sage: E = D.r_of_D(True) # optional - 4ti2 - sage: E # optional - 4ti2 - (1, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 0}) - sage: F = E[1] # optional - 4ti2 - sage: (D - F).values() # optional - 4ti2 - [0, -1, 0, -1, 0, 4] - sage: (D - F).effective_div() # optional - 4ti2 - [] - sage: SandpileDivisor(S, [0,0,0,0,0,-4]).r_of_D(True) # optional - 4ti2 - (-1, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: -4}) +boolean ---- +EXAMPLES:: -.. _sandpile()-divisor: + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: K.weierstrass_rank_seq() # sequence at the sink vertex, 0 + (1, 0, -1) + sage: K.is_weierstrass_pt() + False + sage: K.weierstrass_rank_seq(4) + (1, 0, 0, -1) + sage: K.is_weierstrass_pt(4) + True -**sandpile()** +.. NOTE:: - The divisor's underlying sandpile. + The vertex `v` is a (generalized) Weierstrass point for divisor `D` if the sequence of ranks `r(D - nv)` + for `n = 0, 1, 2, \dots` is not `r(D), r(D)-1, \dots, 0, -1, -1, \dots` - INPUT: +--- - None +.. _polytope-divisor: - OUTPUT: +**polytope()** - Sandpile +The polytope determinining the complete linear system. - EXAMPLES:: +OUTPUT: - sage: S = sandlib('genus2') - sage: D = SandpileDivisor(S,[1,-2,0,3]) - sage: D.sandpile() - Digraph on 4 vertices - sage: D.sandpile() == S - True +polytope ---- +EXAMPLES:: -.. _show(heights=True,directed=None,kwds)-divisor: + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: p = D.polytope() + sage: p.inequalities() + (An inequality (-3, 1, 1) x + 2 >= 0, + An inequality (1, 1, 1) x + 4 >= 0, + An inequality (1, -3, 1) x + 0 >= 0, + An inequality (1, 1, -3) x + 0 >= 0) + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.polytope() + The empty polyhedron in QQ^3 -**show(heights=True,directed=None,kwds)** +.. NOTE:: - Show the divisor. + For a divisor `D`, this is the intersection of (i) the polyhedron + determined by the system of inequalities `L^t x \leq D` where `L^t` + is the transpose of the Laplacian with (ii) the hyperplane + `x_{\mathrm{sink\_vertex}} = 0`. The polytope is thought of as sitting in + `(n-1)`-dimensional Euclidean space where `n` is the number of + vertices. - INPUT: +--- - * ``heights`` - whether to label each vertex with the amount of - sand +.. _polytope_integer_pts-divisor: - * ``kwds`` - arguments passed to the show method for Graph +**polytope_integer_pts()** - * ``directed`` - whether to draw directed edges +The integer points inside divisor's polytope. The polytope referred to +here is the one determining the divisor's complete linear system (see the +documentation for ``polytope``). - OUTPUT: +OUTPUT: - None +tuple of integer vectors - EXAMPLES:: +EXAMPLES:: - sage: S = sandlib('genus2') - sage: D = SandpileDivisor(S,[1,-2,0,2]) - sage: D.show(graph_border=True,vertex_size=700,directed=False) + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D.polytope_integer_pts() + ((-2, -1, -1), + (-1, -2, -1), + (-1, -1, -2), + (-1, -1, -1), + (0, -1, -1), + (0, 0, 0)) + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.polytope_integer_pts() + () --- -.. _support()-divisor: +.. _q_reduced-divisor: -**support()** +**q_reduced(verbose=True)** - List of keys of the nonzero values of the divisor. +The linearly equivalent `q`-reduced divisor. - INPUT: +INPUT: - None +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - list - support of the divisor +SandpileDivisor or list representing SandpileDivisor - EXAMPLES:: +EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[2,-3,2,0]) + sage: D.q_reduced() + {0: -2, 1: 1, 2: 2, 3: 0} + sage: D.q_reduced(False) + [-2, 1, 2, 0] - sage: S = sandlib('generic') - sage: c = S.identity() - sage: c.values() - [2, 2, 1, 1, 0] - sage: c.support() - [1, 2, 3, 4] - sage: S.vertices() - [0, 1, 2, 3, 4, 5] +.. NOTE:: + + The divisor `D` is `qreduced if `D = c + kq` where `c` + is superstable, `k` is an integer, and `q` is the sink. --- -.. _unstable()-divisor: +.. _rank-divisor: -**unstable()** +**rank(with_witness=False)** - List of the unstable vertices. +The rank of the divisor. Optionally returns an effective divisor `E` such +that `D - E` is not winnable (has an empty complete linear system). - INPUT: +INPUT: - None +``with_witness`` -- (default: ``False``) boolean - OUTPUT: +OUTPUT: - list of vertices +integer or (integer, SandpileDivisor) - EXAMPLES:: +EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.unstable() - [1, 2] + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S,[4,2,0,0]) + sage: D.rank() + 3 + sage: D.rank(True) + (3, {0: 3, 1: 0, 2: 1, 3: 0}) + sage: E = _[1] + sage: (D - E).rank() + -1 ---- + Riemann-Roch theorem:: -.. _values()-divisor: + sage: D.rank() - (S.canonical_divisor()-D).rank() == D.deg() + 1 - S.genus() + True -**values()** + Riemann-Roch theorem:: - The values of the divisor as a list, sorted in the order of the - vertices. + sage: D.rank() - (S.canonical_divisor()-D).rank() == D.deg() + 1 - S.genus() + True + sage: S = Sandpile({0:[1,1,1,2],1:[0,0,0,1,1,1,2,2],2:[2,2,1,1,0]},0) # multigraph with loops + sage: D = SandpileDivisor(S,[4,2,0]) + sage: D.rank(True) + (2, {0: 1, 1: 1, 2: 1}) + sage: S = Sandpile({0:[1,2], 1:[0,2,2], 2: [0,1]},0) # directed graph + sage: S.is_undirected() + False + sage: D = SandpileDivisor(S,[0,2,0]) + sage: D.effective_div() + [{0: 0, 1: 2, 2: 0}, {0: 2, 1: 0, 2: 0}] + sage: D.rank(True) + (0, {0: 0, 1: 0, 2: 1}) + sage: E = D.rank(True)[1] + sage: (D - E).effective_div() + [] + +.. NOTE:: + + The rank of a divisor `D` is -1 if `D` is not linearly equivalent to an effective divisor + (i.e., the dollar game represented by `D` is unwinnable). Otherwise, the rank of `D` is + the largest integer `r` such that `D - E` is linearly equivalent to an effective divisor + for all effective divisors `E` with `\deg(E) = r`. - INPUT: +--- - None +.. _sandpile-divisor: - OUTPUT: +**sandpile()** - list of integers +The divisor's underlying sandpile. - boolean +OUTPUT: - EXAMPLES:: +Sandpile - sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a') - sage: D = SandpileDivisor(S, {'a':0, 'b':1, 1:2}) - sage: D - {'a': 0, 1: 2, 'b': 1} - sage: D.values() - [2, 0, 1] - sage: S.vertices() - [1, 'a', 'b'] +EXAMPLES:: -Other -~~~~~ + sage: S = sandpiles.Diamond() + sage: D = SandpileDivisor(S,[1,-2,0,3]) + sage: D.sandpile() + Diamond sandpile graph: 4 vertices, sink = 0 + sage: D.sandpile() == S + True -- :ref:`admissible_partitions(S, k) ` --- - Partitions of the vertices into ``k`` parts, each of which is connected. +--- -- :ref:`aztec_sandpile(n) ` --- The aztec diamond graph. +.. _show-divisor: -- :ref:`complete_sandpile(n) ` --- Sandpile on the complete graph. +**show(heights=True, directed=None, **kwds)** -- :ref:`firing_graph(S, eff) ` --- The - firing graph. +Show the divisor. -- :ref:`firing_vector(S,D,E) ` --- The firing vector - taking divisor ``D`` to divisor ``E``. +INPUT: -- :ref:`glue_graphs(g,h,glue_g,glue_h) ` --- Glue two sandpiles - together. +- ``heights`` -- (default: ``True``) whether to label each vertex with the amount of sand -- :ref:`grid_sandpile(m,n) ` --- The `m\times n` grid sandpile. +- ``directed`` -- (optional) whether to draw directed edges -- :ref:`min_cycles(G,v) ` --- The minimal length cycles in - the digraph ``G`` starting at vertex ``v``. +- ``kwds`` -- (optional) arguments passed to the show method for Graph -- :ref:`parallel_firing_graph(S,eff) ` --- The - parallel-firing graph. +EXAMPLES:: -- :ref:`partition_sandpile(S,p) ` --- Sandpile formed - with vertices consisting of parts of an admissible partition. + sage: S = sandpiles.Diamond() + sage: D = SandpileDivisor(S,[1,-2,0,2]) + sage: D.show(graph_border=True,vertex_size=700,directed=False) -- :ref:`random_digraph(num_verts,p=1/2,directed=True,weight_max=1) ` --- A - random directed graph. +--- -- :ref:`random_DAG(num_verts,p=1/2,weight_max=1) ` --- A random directed acyclic graph. +.. _simulate_threshold-divisor: -- :ref:`random_tree(n,d) ` --- Random tree sandpile. +**simulate_threshold(distrib=None)** -- :ref:`sandlib(selector=None) ` --- A collection of sandpiles. +The first unstabilizable divisor in the closed Markov chain. +(See NOTE.) -- :ref:`triangle_sandpile(n) ` --- The triangle sandpile. +INPUT: -- :ref:`wilmes_algorithm(M) ` --- Find matrix with the - same integer row span as ``M`` that is the reduced Laplacian of a digraph. +``distrib`` -- (optional) list of nonnegative numbers representing a probability distribution on the vertices --------- +OUTPUT: -**Complete descriptions of methods.** +SandpileDivisor -.. _admissible_partitions(S,k): +EXAMPLES:: -**admissible_partitions(S, k)** + sage: s = sandpiles.Complete(4) + sage: D = s.zero_div() + sage: D.simulate_threshold() # random + {0: 2, 1: 3, 2: 1, 3: 2} + sage: n(mean([D.simulate_threshold().deg() for _ in range(10)])) # random + 7.10000000000000 + sage: n(s.stationary_density()*s.num_verts()) + 6.93750000000000 - The partitions of the vertices of ``S`` into ``k`` parts, - each of which is connected. +.. NOTE:: - INPUT: + Starting at ``self``, repeatedly choose a vertex and add a grain of + sand to it. Return the first unstabilizable divisor that is + reached. Also see the ``markov_chain`` method for the underlying + sandpile. - ``S`` - Sandpile - ``k`` - integer +--- - OUTPUT: +.. _stabilize-divisor: - list of partitions +**stabilize(with_firing_vector=False)** - EXAMPLES:: +The stabilization of the divisor. If not stabilizable, return an error. - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: P = [admissible_partitions(S, i) for i in [2,3,4]] - sage: P - [[{{0}, {1, 2, 3}}, - {{0, 2, 3}, {1}}, - {{0, 1, 3}, {2}}, - {{0, 1, 2}, {3}}, - {{0, 1}, {2, 3}}, - {{0, 3}, {1, 2}}], - [{{0}, {1}, {2, 3}}, - {{0}, {1, 2}, {3}}, - {{0, 3}, {1}, {2}}, - {{0, 1}, {2}, {3}}], - [{{0}, {1}, {2}, {3}}]] - sage: for p in P: # long time - ....: sum([partition_sandpile(S, i).betti(verbose=false)[-1] for i in p]) # long time - 6 - 8 - 3 - sage: S.betti() # long time - 0 1 2 3 - ------------------------------ - 0: 1 - - - - 1: - 6 8 3 - ------------------------------ - total: 1 6 8 3 - ---- - -.. _aztec_sandpile(n): - -**aztec(n)** - - The aztec diamond graph. +INPUT: - INPUT: +``with_firing_vector`` -- (default: ``False``) boolean - n - integer +EXAMPLES:: - OUTPUT: + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[0,3,0,0]) + sage: D.stabilize() + {0: 1, 1: 0, 2: 1, 3: 1} + sage: D.stabilize(with_firing_vector=True) + [{0: 1, 1: 0, 2: 1, 3: 1}, {0: 0, 1: 1, 2: 0, 3: 0}] - dictionary for the aztec diamond graph +--- - EXAMPLES:: +.. _support-divisor: - sage: aztec_sandpile(2) - {'sink': {(-3/2, -1/2): 2, - (-3/2, 1/2): 2, - (-1/2, -3/2): 2, - (-1/2, 3/2): 2, - (1/2, -3/2): 2, - (1/2, 3/2): 2, - (3/2, -1/2): 2, - (3/2, 1/2): 2}, - (-3/2, -1/2): {'sink': 2, (-3/2, 1/2): 1, (-1/2, -1/2): 1}, - (-3/2, 1/2): {'sink': 2, (-3/2, -1/2): 1, (-1/2, 1/2): 1}, - (-1/2, -3/2): {'sink': 2, (-1/2, -1/2): 1, (1/2, -3/2): 1}, - (-1/2, -1/2): {(-3/2, -1/2): 1, - (-1/2, -3/2): 1, - (-1/2, 1/2): 1, - (1/2, -1/2): 1}, - (-1/2, 1/2): {(-3/2, 1/2): 1, (-1/2, -1/2): 1, (-1/2, 3/2): 1, (1/2, 1/2): 1}, - (-1/2, 3/2): {'sink': 2, (-1/2, 1/2): 1, (1/2, 3/2): 1}, - (1/2, -3/2): {'sink': 2, (-1/2, -3/2): 1, (1/2, -1/2): 1}, - (1/2, -1/2): {(-1/2, -1/2): 1, (1/2, -3/2): 1, (1/2, 1/2): 1, (3/2, -1/2): 1}, - (1/2, 1/2): {(-1/2, 1/2): 1, (1/2, -1/2): 1, (1/2, 3/2): 1, (3/2, 1/2): 1}, - (1/2, 3/2): {'sink': 2, (-1/2, 3/2): 1, (1/2, 1/2): 1}, - (3/2, -1/2): {'sink': 2, (1/2, -1/2): 1, (3/2, 1/2): 1}, - (3/2, 1/2): {'sink': 2, (1/2, 1/2): 1, (3/2, -1/2): 1}} - sage: Sandpile(aztec_sandpile(2),'sink').group_order() - 4542720 +**support()** - NOTES: +List of vertices at which the divisor is nonzero. - This is the aztec diamond graph with a sink vertex added. Boundary - vertices have edges to the sink so that each vertex has degree 4. +OUTPUT: ---- +list representing the support of the divisor -.. _complete_sandpile(n): +EXAMPLES:: -**complete_sandpile(n)** + sage: S = sandpiles.Cycle(4) + sage: D = SandpileDivisor(S, [0,0,1,1]) + sage: D.support() + [2, 3] + sage: S.vertices() + [0, 1, 2, 3] - The sandpile on the complete graph with n vertices. +--- - INPUT: +.. _unstable-divisor: - ``n`` - positive integer +**unstable()** - OUTPUT: +The unstable vertices. - Sandpile +OUTPUT: - EXAMPLES:: +list of vertices + +EXAMPLES:: - sage: K = complete_sandpile(5) - sage: K.betti(verbose=False) # long time - [1, 15, 50, 60, 24] + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.unstable() + [1, 2] --- -.. _firing_graph(S,eff): +.. _values-divisor: -**firing_graph(S, eff)** +**values()** - Creates a digraph with divisors as vertices and edges between two - divisors ``D`` and ``E`` if firing a single vertex in ``D`` gives - ``E``. +The values of the divisor as a list. The list is sorted in the order of +the vertices. - INPUT: +OUTPUT: - ``S`` - sandpile - ``eff`` - list of divisors +list of integers - OUTPUT: +boolean - DiGraph +EXAMPLES:: - EXAMPLES:: + sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a') + sage: D = SandpileDivisor(S, {'a':0, 'b':1, 1:2}) + sage: D + {'a': 0, 1: 2, 'b': 1} + sage: D.values() + [2, 0, 1] + sage: S.vertices() + [1, 'a', 'b'] - sage: S = Sandpile(graphs.CycleGraph(6),0) - sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional 4ti2 --- -.. _firing_vector(S,D,E): +.. _weierstrass_div-divisor: -**firing_vector(S,D,E)** +**weierstrass_div(verbose=True)** - If ``D`` and ``E`` are linearly equivalent divisors, find the firing vector - taking ``D`` to ``E``. +The Weierstrass divisor. Its value at a vertex is the weight of that +vertex as a Weierstrass point. (See +``SandpileDivisor.weierstrass_gap_seq``.) - INPUT: +INPUT: - - ``S`` -Sandpile - - ``D``, ``E`` - tuples (representing linearly equivalent divisors) +``verbose`` -- (default: ``True``) boolean - OUTPUT: +OUTPUT: - tuple (representing a firing vector from ``D`` to ``E``) +SandpileDivisor - EXAMPLES:: +EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: D = SandpileDivisor(s,[4,2,1,0]) + sage: [D.weierstrass_rank_seq(v) for v in s] + [(5, 4, 3, 2, 1, 0, 0, -1), + (5, 4, 3, 2, 1, 0, -1), + (5, 4, 3, 2, 1, 0, 0, 0, -1), + (5, 4, 3, 2, 1, 0, 0, -1)] + sage: D.weierstrass_div() + {0: 1, 1: 0, 2: 2, 3: 1} + sage: k5 = sandpiles.Complete(5) + sage: K = k5.canonical_divisor() + sage: K.weierstrass_div() + {0: 9, 1: 9, 2: 9, 3: 9, 4: 9} - sage: S = complete_sandpile(4) - sage: D = SandpileDivisor(S, {0: 0, 1: 0, 2: 8, 3: 0}) - sage: E = SandpileDivisor(S, {0: 2, 1: 2, 2: 2, 3: 2}) - sage: v = firing_vector(S, D, E) - sage: v - (0, 0, 2, 0) - sage: vector(D.values()) - S.laplacian()*vector(v) == vector(E.values()) - True +--- - The divisors must be linearly equivalent:: +.. _weierstrass_gap_seq-divisor: - sage: S = complete_sandpile(4) - sage: D = SandpileDivisor(S, {0: 0, 1: 0, 2: 8, 3: 0}) - sage: firing_vector(S, D, S.zero_div()) - Error. Are the divisors linearly equivalent? +**weierstrass_gap_seq(v='sink', weight=True)** ---- +The Weierstrass gap sequence at the given vertex. If ``weight`` is +``True``, then also compute the weight of each gap value. -.. _glue_graphs(g,h,glue_g,glue_h): +INPUT: -**glue_graphs(g,h,glue_g,glue_h)** +- ``v`` -- (default: ``sink``) vertex - Glue two graphs together. +- ``weight`` -- (default: ``True``) boolean - INPUT: +OUTPUT: - - ``g``, ``h`` - dictionaries for directed multigraphs - - ``glue_h``, ``glue_g`` - dictionaries for a vertex +list or (list of list) of integers - OUTPUT: +EXAMPLES:: - dictionary for a directed multigraph + sage: s = sandpiles.Cycle(4) + sage: D = SandpileDivisor(s,[2,0,0,0]) + sage: [D.weierstrass_gap_seq(v,False) for v in s.vertices()] + [(1, 3), (1, 2), (1, 3), (1, 2)] + sage: [D.weierstrass_gap_seq(v) for v in s.vertices()] + [((1, 3), 1), ((1, 2), 0), ((1, 3), 1), ((1, 2), 0)] + sage: D.weierstrass_gap_seq() # gap sequence at sink vertex, 0 + ((1, 3), 1) + sage: D.weierstrass_rank_seq() # rank sequence at the sink vertex + (1, 0, 0, -1) - EXAMPLES:: +.. NOTE:: - sage: x = {0: {}, 1: {0: 1}, 2: {0: 1, 1: 1}, 3: {0: 1, 1: 1, 2: 1}} - sage: y = {0: {}, 1: {0: 2}, 2: {1: 2}, 3: {0: 1, 2: 1}} - sage: glue_x = {1: 1, 3: 2} - sage: glue_y = {0: 1, 1: 2, 3: 1} - sage: z = glue_graphs(x,y,glue_x,glue_y) - sage: z - {0: {}, - 'x0': {0: 1, 'x1': 1, 'x3': 2, 'y1': 2, 'y3': 1}, - 'x1': {'x0': 1}, - 'x2': {'x0': 1, 'x1': 1}, - 'x3': {'x0': 1, 'x1': 1, 'x2': 1}, - 'y1': {0: 2}, - 'y2': {'y1': 2}, - 'y3': {0: 1, 'y2': 1}} - sage: S = Sandpile(z,0) - sage: S.h_vector() - [1, 6, 17, 31, 41, 41, 31, 17, 6, 1] - sage: S.resolution() # long time - 'R^1 <-- R^7 <-- R^21 <-- R^35 <-- R^35 <-- R^21 <-- R^7 <-- R^1' + The integer `k` is a Weierstrass gap for the divisor `D` at vertex `v` if the rank + of `D - (k-1)v` does not equal the rank of `D - kv`. Let `r` be the rank of `D` and + let `k_i` be the `i`-th gap at `v`. The Weierstrass weight of `v` for `D` is the + sum of `(k_i - i)` as `i` ranges from `1` to `r + 1`. It measure the difference + between the sequence `r, r - 1, ..., 0, -1, -1, ...` and the rank sequence + `\mathrm{rank}(D), \mathrm{rank}(D - v), \mathrm{rank}(D - 2v), \dots` - NOTES: +--- - This method makes a dictionary for a graph by combining those for ``g`` and - ``h``. The sink of ``g`` is replaced by a vertex that is connected to the - vertices of ``g`` as specified by ``glue_g`` the vertices of ``h`` as - specified in ``glue_h``. The sink of the glued graph is `0`. +.. _weierstrass_pts-divisor: - Both ``glue_g`` and ``glue_h`` are dictionaries with entries of the form - ``v:w`` where ``v`` is the vertex to be connected to and ``w`` is the weight - of the connecting edge. +**weierstrass_pts(with_rank_seq=False)** ---- +The Weierstrass points (vertices). Optionally, return the corresponding rank sequences. -.. _grid_sandpile(m,n): +INPUT: -**grid_sandpile(m,n)** +``with_rank_seq`` -- (default: ``False``) boolean - The mxn grid sandpile. Each nonsink vertex has degree 4. +OUTPUT: - INPUT: - ``m``, ``n`` - positive integers +tuple of vertices or list of (vertex, rank sequence) - OUTPUT: - dictionary for a sandpile with sink named ``sink``. +EXAMPLES:: - EXAMPLES:: + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: K.weierstrass_pts() + (4,) + sage: K.weierstrass_pts(True) + [(4, (1, 0, 0, -1))] - sage: grid_sandpile(3,4).dict() - {'sink': {}, - (1, 1): {'sink': 2, (1, 2): 1, (2, 1): 1}, - (1, 2): {'sink': 1, (1, 1): 1, (1, 3): 1, (2, 2): 1}, - (1, 3): {'sink': 1, (1, 2): 1, (1, 4): 1, (2, 3): 1}, - (1, 4): {'sink': 2, (1, 3): 1, (2, 4): 1}, - (2, 1): {'sink': 1, (1, 1): 1, (2, 2): 1, (3, 1): 1}, - (2, 2): {(1, 2): 1, (2, 1): 1, (2, 3): 1, (3, 2): 1}, - (2, 3): {(1, 3): 1, (2, 2): 1, (2, 4): 1, (3, 3): 1}, - (2, 4): {'sink': 1, (1, 4): 1, (2, 3): 1, (3, 4): 1}, - (3, 1): {'sink': 2, (2, 1): 1, (3, 2): 1}, - (3, 2): {'sink': 1, (2, 2): 1, (3, 1): 1, (3, 3): 1}, - (3, 3): {'sink': 1, (2, 3): 1, (3, 2): 1, (3, 4): 1}, - (3, 4): {'sink': 2, (2, 4): 1, (3, 3): 1}} - sage: grid_sandpile(3,4).group_order() - 4140081 +.. NOTE:: + + The vertex `v` is a (generalized) Weierstrass point for divisor `D` if the sequence of ranks `r(D - nv)` + for `n = 0, 1, 2, \dots`` is not `r(D), r(D)-1, \dots, 0, -1, -1, \dots` --- -.. _min_cycles(G,v): +.. _weierstrass_rank_seq-divisor: -**min_cycles(G,v)** +**weierstrass_rank_seq(v='sink')** - Minimal length cycles in the digraph ``G`` starting at vertex ``v``. +The Weierstrass rank sequence at the given vertex. Computes the rank of +the divisor `D - nv` starting with `n=0` and ending when the rank is +`-1`. - INPUT: +INPUT: - ``G`` - DiGraph - ``v`` - vertex of ``G`` +``v`` -- (default: ``sink``) vertex - OUTPUT: +OUTPUT: - list of lists of vertices +tuple of int - EXAMPLES:: +EXAMPLES:: - sage: T = sandlib('gor') - sage: [min_cycles(T, i) for i in T.vertices()] - [[], [[1, 3]], [[2, 3, 1], [2, 3]], [[3, 1], [3, 2]]] + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: [K.weierstrass_rank_seq(v) for v in s.vertices()] + [(1, 0, -1), (1, 0, -1), (1, 0, -1), (1, 0, -1), (1, 0, 0, -1)] --- -.. _parallel_firing_graph(S,eff): - -**parallel_firing_graph(S,eff)** - - Creates a digraph with divisors as vertices and edges between two - divisors ``D`` and ``E`` if firing all unstable vertices in ``D`` gives - ``E``. +Other +~~~~~ - INPUT: +- :ref:`firing_graph ` --- The firing graph. - ``S`` - Sandpile - ``eff`` - list of divisors +- :ref:`parallel_firing_graph ` --- The parallel-firing graph. - OUTPUT: +- :ref:`random_DAG ` --- A random directed acyclic graph. - DiGraph +- :ref:`sandpiles ` --- Some examples of sandpiles. - EXAMPLES:: +- :ref:`wilmes_algorithm ` --- Find matrix with the + same integer row span as ``M`` that is the reduced Laplacian of a digraph. - sage: S = Sandpile(graphs.CycleGraph(6),0) - sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2 +-------- ---- +**Complete descriptions of methods.** -.. _partition_sandpile(S,p): +.. _firing_graph: -**partition_sandpile(S,p)** +**firing_graph(S, eff)** - Each set of vertices in ``p`` is regarded as a single vertex, with and edge - between ``A`` and ``B`` if some element of ``A`` is connected by an edge - to some element of ``B`` in ``S``. + Creates a digraph with divisors as vertices and edges between two divisors + `D` and `E` if firing a single vertex in `D` gives `E`. INPUT: - ``S`` - Sandpile - ``p`` - partition of the vertices of ``S`` + ``S`` -- Sandpile + ``eff`` -- list of divisors OUTPUT: - Sandpile + DiGraph EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) - sage: P = [admissible_partitions(S, i) for i in [2,3,4]] - sage: for p in P: #long time - ....: sum([partition_sandpile(S, i).betti(verbose=false)[-1] for i in p]) # long time - 6 - 8 - 3 - sage: S.betti() # long time - 0 1 2 3 - ------------------------------ - 0: 1 - - - - 1: - 6 8 3 - ------------------------------ - total: 1 6 8 3 + sage: S = sandpiles.Cycle(6) + sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) + sage: eff = D.effective_div() + sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) --- -.. _random_digraph(num_verts,p=1/2,directed=True,weight_max=1): +.. _parallel_firing_graph: -**random_digraph(num_verts,p=1/2,directed=True,weight_max=1)** +**parallel_firing_graph(S,eff)** - A random weighted digraph with a directed spanning tree rooted at `0`. If - ``directed = False``, the only difference is that if `(i,j,w)` is an edge with - tail `i`, head `j`, and weight `w`, then `(j,i,w)` appears also. The result - is returned as a Sage digraph. + Creates a digraph with divisors as vertices and edges between two + divisors ``D`` and ``E`` if firing all unstable vertices in ``D`` gives + ``E``. INPUT: - - ``num_verts`` - number of vertices - - ``p`` - probability edges occur - - ``directed`` - True if directed - - ``weight_max`` - integer maximum for random weights + ``S`` - Sandpile + ``eff`` - list of divisors OUTPUT: - random graph + DiGraph EXAMPLES:: - sage: g = random_digraph(6,0.2,True,3) - sage: S = Sandpile(g,0) - sage: S.show(edge_labels=True) + sage: S = Sandpile(graphs.CycleGraph(6),0) + sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) + sage: eff = D.effective_div() + sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) --- -.. _random_DAG(num_verts,p=1/2,weight_max=1): +.. _random_DAG: **random_DAG(num_verts,p=1/2,weight_max=1)** @@ -4407,84 +4873,39 @@ Other --- -.. _random_tree(n,d): - -**random_tree(n,d)** - - Returns a random undirected tree with ``n`` nodes, no node having - degree higher than ``d``. - - INPUT: - - ``n``, ``d`` - integers - - OUTPUT: - - Graph - - EXAMPLES:: - - sage: T = random_tree(15,3) - sage: T.show() - sage: S = Sandpile(T,0) - sage: U = S.reorder_vertices() - sage: Graph(U).show() - ---- - -.. _sandlib(selector=None): - -**sandlib(selector=None)** - - The sandpile identified by ``selector``. If no argument is - given, a description of the sandpiles in the sandlib is printed. - - INPUT: +.. _sandpiles: - ``selector`` - identifier or None +**sandpiles** - OUTPUT: + Some examples of sandpiles. - sandpile or description + Here are the available examples; you can also type "sandpiles." + and hit tab to get a list: - EXAMPLES:: + * "Complete()" - sage: sandlib() - Sandpiles in the sandlib: - kite : generic undirected graphs with 5 vertices - generic : generic digraph with 6 vertices - genus2 : Undirected graph of genus 2 - ci1 : complete intersection, non-DAG but equivalent to a DAG - riemann-roch1 : directed graph with postulation 9 and 3 maximal weight superstables - riemann-roch2 : directed graph with a superstable not majorized by a maximal superstable - gor : Gorenstein but not a complete intersection - ---- + * "Cycle()" -.. _triangle_sandpile(n): + * "Diamond()" -**triangle(n)** + * "Grid()" - A triangular sandpile. Each nonsink vertex has out-degree six. The - vertices on the boundary of the triangle are connected to the sink. + * "House()" - INPUT: - - ``n`` - int - - OUTPUT: - - Sandpile - - EXAMPLES:: + EXAMPLES:: - sage: T = triangle_sandpile(5) - sage: T.group_order() - 135418115000 + sage: s = sandpiles.Complete(4) + sage: s.invariant_factors() + [1, 4, 4] + sage: s.laplacian() + [ 3 -1 -1 -1] + [-1 3 -1 -1] + [-1 -1 3 -1] + [-1 -1 -1 3] --- -.. _wilmes_algorithm(M): +.. _wilmes_algorithm: **wilmes_algorithm(M)** @@ -4548,6 +4969,8 @@ Documentation for each method is available through the Sage online help system: An alternative to ``SandpileConfig.fire_vertex?`` in the preceding code example would be ``c.fire_vertex?``, if ``c`` is any SandpileConfig. +Enter ``Sandpile.help()``, ``SandpileConfig.help()``, and ``SandpileDivisor.help()`` for lists of available Sandpile-specific methods. + General Sage documentation can be found at http://sagemath.org/doc/. Contact diff --git a/src/module_list.py b/src/module_list.py index 1d66323f446..19262fbbd20 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -495,6 +495,16 @@ def uname_specific(name, value, alternative): Extension('*', ['sage/interfaces/*.pyx']), + ################################ + ## + ## sage.lfunctions + ## + ################################ + + Extension('sage.lfunctions.zero_sums', + sources = ['sage/lfunctions/zero_sums.pyx'], + libraries = ["m","flint"]), + ################################ ## ## sage.libs diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 7d245d096c1..b419d985be3 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -172,7 +172,7 @@ def list(self): sage: elt.list() [((0,), 5), ((1,), 1)] """ - return sorted(self._monomial_coefficients.items(), key=lambda (m,c) : (-len(m), m)) + return sorted(self._monomial_coefficients.items(), key=lambda m_c : (-len(m_c[0]), m_c[0])) def support(self): """ diff --git a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx index 98f7918e64c..eb4b291ab1c 100644 --- a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx +++ b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx @@ -448,40 +448,18 @@ cdef class FreeAlgebraElement_letterplace(AlgebraElement): return True return False - def __richcmp__(left, right, int op): - """ - TEST:: - - sage: F. = FreeAlgebra(QQ, implementation='letterplace') - sage: p = ((2*x+3*y-4*z)^2*(5*y+6*z)) - sage: p-p.lt()

left)._richcmp(right, op) - def __cmp__(left, right): - """ - TEST:: - - sage: F. = FreeAlgebra(QQ, implementation='letterplace') - sage: p = ((2*x+3*y-4*z)^2*(5*y+6*z)) - sage: cmp(p,p-p.lt()) # indirect doctest - 1 - - """ - return (left)._cmp(right) - cpdef int _cmp_(self, Element other) except -2: """ Auxiliary method for comparison. - TEST:: + TESTS:: sage: F. = FreeAlgebra(QQ, implementation='letterplace') sage: p = ((2*x+3*y-4*z)^2*(5*y+6*z)) - sage: p-p.lt()

1/4*(4*x^2 - 5)*x^2 + 5/16 sage: f(a).numerical_approx(100) 0.00000000000000000000000000000 @@ -921,15 +913,12 @@ def minpoly(ex, var='x', algorithm=None, bits=None, degree=None, epsilon=0): sage: a.minpoly(algorithm='numerical', bits=100, degree=10) x^4 - 10*x^2 + 1 - There is a difference between algorithm='algebraic' and - algorithm='numerical':: + :: sage: cos(pi/33).minpoly(algorithm='algebraic') x^10 + 1/2*x^9 - 5/2*x^8 - 5/4*x^7 + 17/8*x^6 + 17/16*x^5 - 43/64*x^4 - 43/128*x^3 + 3/64*x^2 + 3/128*x + 1/1024 sage: cos(pi/33).minpoly(algorithm='numerical') - Traceback (most recent call last): - ... - NotImplementedError: Could not prove minimal polynomial x^10 + 1/2*x^9 - 5/2*x^8 - 5/4*x^7 + 17/8*x^6 + 17/16*x^5 - 43/64*x^4 - 43/128*x^3 + 3/64*x^2 + 3/128*x + 1/1024 (epsilon ...) + x^10 + 1/2*x^9 - 5/2*x^8 - 5/4*x^7 + 17/8*x^6 + 17/16*x^5 - 43/64*x^4 - 43/128*x^3 + 3/64*x^2 + 3/128*x + 1/1024 Sometimes it fails, as it must given that some numbers aren't algebraic:: @@ -1843,7 +1832,7 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): cursor = 0 l = [] for m in maxima_var.finditer(s): - if symtable.has_key(m.group(0)): + if m.group(0) in symtable: l.append(s[cursor:m.start()]) l.append(symtable.get(m.group(0))) cursor = m.end() diff --git a/src/sage/calculus/wester.py b/src/sage/calculus/wester.py index fb1b4c13f07..833fcbe9a44 100644 --- a/src/sage/calculus/wester.py +++ b/src/sage/calculus/wester.py @@ -75,13 +75,13 @@ :: - sage: # (NOT REALLY) Sqrt(14+3*Sqrt(3+2*Sqrt(5-12*Sqrt(3-2*Sqrt(2)))))=3+Sqrt(2). + sage: # (YES) Sqrt(14+3*Sqrt(3+2*Sqrt(5-12*Sqrt(3-2*Sqrt(2)))))=3+Sqrt(2). sage: a = sqrt(14+3*sqrt(3+2*sqrt(5-12*sqrt(3-2*sqrt(2))))) sage: b = 3+sqrt(2) sage: a, b (sqrt(3*sqrt(2*sqrt(-12*sqrt(-2*sqrt(2) + 3) + 5) + 3) + 14), sqrt(2) + 3) sage: bool(a==b) - False + True sage: abs(float(a-b)) < 1e-10 True sage: # 2*Infinity-3=Infinity. diff --git a/src/sage/categories/associative_algebras.py b/src/sage/categories/associative_algebras.py index c05d06d3d9f..d3bc9262f22 100644 --- a/src/sage/categories/associative_algebras.py +++ b/src/sage/categories/associative_algebras.py @@ -67,7 +67,7 @@ class ElementMethods: + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] """ - __mul__ = Magmas.ElementMethods.__mul__.im_func + __mul__ = Magmas.ElementMethods.__mul__.__func__ # __imul__ = __mul__ diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 8558e84c159..252f2e7bf84 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -8,14 +8,20 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.misc.cachefunc import CachedFunction, cached_method +from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method from sage.misc.lazy_import import LazyImport from sage.categories.category_singleton import Category_singleton from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.tensor import TensorProductsCategory +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom, Homset from sage.misc.latex import latex +from sage.combinat import ranker from sage.graphs.dot2tex_utils import have_dot2tex +from sage.rings.integer import Integer +from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet +from sage.sets.family import Family class Crystals(Category_singleton): r""" @@ -123,12 +129,96 @@ def example(self, choice="highwt", **kwds): if choice == "naive": return examples.NaiveCrystal(**kwds) else: - from sage.rings.integer import Integer if isinstance(choice, Integer): return examples.HighestWeightCrystalOfTypeA(n=choice, **kwds) else: return examples.HighestWeightCrystalOfTypeA(**kwds) + class MorphismMethods: + @cached_method + def is_isomorphism(self): + """ + Check if ``self`` is a crystal isomorphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['C',2], shape=[1,1]) + sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1])) + sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C) + sage: psi.is_isomorphism() + False + """ + if self.domain().cardinality() != self.codomain().cardinality(): + return False + if self.domain().cardinality() == float('inf'): + raise NotImplementedError("unable to determine if an isomorphism") + + index_set = self._cartan_type.index_set() + G = self.domain().digraph(index_set=index_set) + if self.codomain().cardinality() != G.num_verts(): + return False + H = self.codomain().digraph(index_set=index_set) + return G.is_isomorphic(H, edge_labels=True) + + # TODO: This could be moved to sets + @cached_method + def is_embedding(self): + """ + Check if ``self`` is an injective crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['C',2], shape=[1,1]) + sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1])) + sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C) + sage: psi.is_embedding() + True + + sage: C = crystals.Tableaux(['A',2], shape=[2,1]) + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights() + sage: W = crystals.elementary.T(['A',2], La[1]+La[2]) + sage: T = W.tensor(B) + sage: mg = T(W.module_generators[0], B.module_generators[0]) + sage: psi = Hom(C,T)([mg]) + sage: psi.is_embedding() + True + """ + if self.domain().cardinality() > self.codomain().cardinality(): + return False + if self.domain().cardinality() == float('inf'): + raise NotImplementedError("unable to determine if an embedding") + S = set() + for x in self.domain(): + y = self(x) + if y is None or y in S: + return False + S.add(y) + return True + + @cached_method + def is_strict(self): + """ + Check if ``self`` is a strict crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['C',2], shape=[1,1]) + sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1])) + sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C) + sage: psi.is_strict() + True + """ + if self.domain().cardinality() == float('inf'): + raise NotImplementedError("unable to determine if strict") + index_set = self._cartan_type.index_set() + for x in self.domain(): + y = self(x) + if any(self(x.f(i)) != y.f(i) or self(x.e(i)) != y.e(i) + for i in index_set): + return False + return True + class ParentMethods: def an_element(self): @@ -209,10 +299,10 @@ def __iter__(self, index_set=None, max_depth=float('inf')): INPUT: - - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + - ``index_set`` -- (Default: ``None``) the index set; if ``None`` then use the index set of the crystal - - ``max_depth`` -- (Default: infinity) The maximum depth to build + - ``max_depth`` -- (Default: infinity) the maximum depth to build The iteration order is not specified except that, if ``max_depth`` is finite, then the iteration goes depth by @@ -242,35 +332,50 @@ def __iter__(self, index_set=None, max_depth=float('inf')): (Lambda[1] - Lambda[2],)] """ - from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet if index_set is None: index_set = self.index_set() succ = lambda x: [x.f(i) for i in index_set] + [x.e(i) for i in index_set] R = RecursivelyEnumeratedSet(self.module_generators, succ, structure=None) return R.breadth_first_search_iterator(max_depth) + def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"), - direction="both"): + direction="both", contained=None, + virtualization=None, scaling_factors=None, + cartan_type=None, category=None): r""" Construct the subcrystal from ``generators`` using `e_i` and/or `f_i` for all `i` in ``index_set``. INPUT: - - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + - ``index_set`` -- (default: ``None``) the index set; if ``None`` then use the index set of the crystal - - ``generators`` -- (Default: ``None``) The list of generators; if + - ``generators`` -- (default: ``None``) the list of generators; if ``None`` then use the module generators of the crystal - - ``max_depth`` -- (Default: infinity) The maximum depth to build + - ``max_depth`` -- (default: infinity) the maximum depth to build + + - ``direction`` -- (default: ``'both'``) the direction to build + the subcrystal; it can be one of the following: + + - ``'both'`` - using both `e_i` and `f_i` + - ``'upper'`` - using `e_i` + - ``'lower'`` - using `f_i` + + - ``contained`` -- (optional) a set or function defining the + containment in the subcrystal - - ``direction`` -- (Default: ``'both'``) The direction to build - the subcrystal. It can be one of the following: + - ``virtualization``, ``scaling_factors`` -- (optional) + dictionaries whose key `i` corresponds to the sets `\sigma_i` + and `\gamma_i` respectively used to define virtual crystals; see + :class:`~sage.combinat.crystals.virtual_crystal.VirtualCrystal` - - ``'both'`` - Using both `e_i` and `f_i` - - ``'upper'`` - Using `e_i` - - ``'lower'`` - Using `f_i` + - ``cartan_type`` -- (optional) specify the Cartan type of the + subcrystal + + - ``category`` -- (optional) specify the category of the subcrystal EXAMPLES:: @@ -289,13 +394,72 @@ def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"), [[[1, 4]], [[1, 3]]] sage: list(C.subcrystal(index_set=[1,3], generators=[C(1,4)], direction='lower')) [[[1, 4]], [[2, 4]]] + + sage: G = C.subcrystal(index_set=[1,2,3]).digraph() + sage: GA = crystals.Tableaux('A3', shape=[2]).digraph() + sage: G.is_isomorphic(GA, edge_labels=True) + True + + We construct the subcrystal which contains the necessary data + to construct the corresponding dual equivalence graph:: + + sage: C = crystals.Tableaux(['A',5], shape=[3,3]) + sage: is_wt0 = lambda x: all(x.epsilon(i) == x.phi(i) for i in x.parent().index_set()) + sage: def check(x): + ....: if is_wt0(x): + ....: return True + ....: for i in x.parent().index_set()[:-1]: + ....: L = [x.e(i), x.e_string([i,i+1]), x.f(i), x.f_string([i,i+1])] + ....: if any(y is not None and is_wt0(y) for y in L): + ....: return True + ....: return False + sage: wt0 = [x for x in C if is_wt0(x)] + sage: S = C.subcrystal(contained=check, generators=wt0) + sage: S.module_generators[0] + [[1, 3, 5], [2, 4, 6]] + sage: S.module_generators[0].e(2).e(3).f(2).f(3) + [[1, 2, 5], [3, 4, 6]] + + An example of a type `B_2` virtual crystal inside of a + type `A_3` ambient crystal:: + + sage: A = crystals.Tableaux(['A',3], shape=[2,1,1]) + sage: S = A.subcrystal(virtualization={1:[1,3], 2:[2]}, + ....: scaling_factors={1:1,2:1}, cartan_type=['B',2]) + sage: B = crystals.Tableaux(['B',2], shape=[1]) + sage: S.digraph().is_isomorphic(B.digraph(), edge_labels=True) + True """ + from sage.combinat.crystals.subcrystal import Subcrystal + from sage.categories.finite_crystals import FiniteCrystals + + if cartan_type is None: + cartan_type = self.cartan_type() + else: + from sage.combinat.root_system.cartan_type import CartanType + cartan_type = CartanType(cartan_type) if index_set is None: - index_set = self.index_set() + index_set = cartan_type.index_set() if generators is None: generators = self.module_generators - from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet + if max_depth == float('inf'): + if self not in FiniteCrystals(): + if (contained is None and index_set == self.index_set() + and generators == self.module_generators + and scaling_factors is None and virtualization is None): + return self + return Subcrystal(self, contained, generators, + virtualization, scaling_factors, + cartan_type, index_set, category) + + if direction == 'both': + category = FiniteCrystals().or_subcategory(category) + return Subcrystal(self, contained, generators, + virtualization, scaling_factors, + cartan_type, index_set, category) + + # TODO: Make this work for virtual crystals as well if direction == 'both': succ = lambda x: [x.f(i) for i in index_set] + [x.e(i) for i in index_set] elif direction == 'upper': @@ -304,154 +468,269 @@ def subcrystal(self, index_set=None, generators=None, max_depth=float("inf"), succ = lambda x: [x.f(i) for i in index_set] else: raise ValueError("direction must be either 'both', 'upper', or 'lower'") - return RecursivelyEnumeratedSet(generators, succ, - structure=None, enumeration='breadth', - max_depth=max_depth) - def crystal_morphism(self, g, index_set = None, automorphism = lambda i : i, direction = 'down', direction_image = 'down', - similarity_factor = None, similarity_factor_domain = None, cached = False, acyclic = True): + subset = RecursivelyEnumeratedSet(generators, succ, + structure=None, enumeration='breadth', + max_depth=max_depth) + + # We perform the filtering here since checking containment + # in a frozenset should be fast + if contained is not None: + try: + subset = frozenset(x for x in subset if x in contained) + except TypeError: # It does not have a containment test + subset = frozenset(x for x in subset if contained(x)) + else: + subset = frozenset(subset) + + if category is None: + category = FiniteCrystals() + else: + category = FiniteCrystals().join(category) + + if self in FiniteCrystals() and len(subset) == self.cardinality(): + if index_set == self.index_set(): + return self + + return Subcrystal(self, subset, generators, + virtualization, scaling_factors, + cartan_type, index_set, category) + + def _Hom_(self, Y, category=None, **options): r""" - Constructs a morphism from the crystal ``self`` to another crystal. - The input `g` can either be a function of a (sub)set of elements of self to - element in another crystal or a dictionary between certain elements. - Usually one would map highest weight elements or crystal generators to each - other using g. - Specifying index_set gives the opportunity to define the morphism as `I`-crystals - where `I =` index_set. If index_set is not specified, the index set of self is used. - It is also possible to define twisted-morphisms by specifying an automorphism on the - nodes in te Dynkin diagram (or the index_set). - The option direction and direction_image indicate whether to use `f_i` or `e_i` in - self or the image crystal to construct the morphism, depending on whether the direction - is set to 'down' or 'up'. - It is also possible to set a similarity_factor. This should be a dictionary between - the elements in the index set and positive integers. The crystal operator `f_i` then gets - mapped to `f_i^{m_i}` where `m_i =` similarity_factor[i]. - Setting similarity_factor_domain to a dictionary between the index set and positive integers - has the effect that `f_i^{m_i}` gets mapped to `f_i` where `m_i =` similarity_factor_domain[i]. - Finally, it is possible to set the option `acyclic = False`. This calculates an isomorphism - for cyclic crystals (for example finite affine crystals). In this case the input function `g` - is supposed to be given as a dictionary. + Return the homset from ``self`` to ``Y`` in the + category ``category``. - EXAMPLES:: + INPUT: - sage: C2 = crystals.Letters(['A',2]) - sage: C3 = crystals.Letters(['A',3]) - sage: g = {C2.module_generators[0] : C3.module_generators[0]} - sage: g_full = C2.crystal_morphism(g) - sage: g_full(C2(1)) - 1 - sage: g_full(C2(2)) - 2 - sage: g = {C2(1) : C2(3)} - sage: g_full = C2.crystal_morphism(g, automorphism = lambda i : 3-i, direction_image = 'up') - sage: [g_full(b) for b in C2] - [3, 2, 1] - sage: T = crystals.Tableaux(['A',2], shape = [2]) - sage: g = {C2(1) : T(rows=[[1,1]])} - sage: g_full = C2.crystal_morphism(g, similarity_factor = {1:2, 2:2}) - sage: [g_full(b) for b in C2] - [[[1, 1]], [[2, 2]], [[3, 3]]] - sage: g = {T(rows=[[1,1]]) : C2(1)} - sage: g_full = T.crystal_morphism(g, similarity_factor_domain = {1:2, 2:2}) - sage: g_full(T(rows=[[2,2]])) - 2 + - ``Y`` -- a crystal + - ``category`` -- a subcategory of :class:`Crystals`() or ``None`` + + The sole purpose of this method is to construct the homset + as a :class:`~sage.categories.crystals.CrystalHomset`. If + ``category`` is specified and is not a subcategory of + :class:`Crystals`, a ``TypeError`` is raised instead. + + This method is not meant to be called directly. Please use + :func:`sage.categories.homset.Hom` instead. - sage: B1 = crystals.KirillovReshetikhin(['A',2,1],1,1) - sage: B2 = crystals.KirillovReshetikhin(['A',2,1],1,2) - sage: T = crystals.TensorProduct(B1,B2) - sage: T1 = crystals.TensorProduct(B2,B1) - sage: La = T.weight_lattice_realization().fundamental_weights() - sage: t = [b for b in T if b.weight() == -3*La[0] + 3*La[1]][0] - sage: t1 = [b for b in T1 if b.weight() == -3*La[0] + 3*La[1]][0] - sage: g={t:t1} - sage: f=T.crystal_morphism(g,acyclic = False) - sage: [[b,f(b)] for b in T] - [[[[[1]], [[1, 1]]], [[[1, 1]], [[1]]]], - [[[[1]], [[1, 2]]], [[[1, 1]], [[2]]]], - [[[[1]], [[2, 2]]], [[[1, 2]], [[2]]]], - [[[[1]], [[1, 3]]], [[[1, 1]], [[3]]]], - [[[[1]], [[2, 3]]], [[[1, 2]], [[3]]]], - [[[[1]], [[3, 3]]], [[[1, 3]], [[3]]]], - [[[[2]], [[1, 1]]], [[[1, 2]], [[1]]]], - [[[[2]], [[1, 2]]], [[[2, 2]], [[1]]]], - [[[[2]], [[2, 2]]], [[[2, 2]], [[2]]]], - [[[[2]], [[1, 3]]], [[[2, 3]], [[1]]]], - [[[[2]], [[2, 3]]], [[[2, 2]], [[3]]]], - [[[[2]], [[3, 3]]], [[[2, 3]], [[3]]]], - [[[[3]], [[1, 1]]], [[[1, 3]], [[1]]]], - [[[[3]], [[1, 2]]], [[[1, 3]], [[2]]]], - [[[[3]], [[2, 2]]], [[[2, 3]], [[2]]]], - [[[[3]], [[1, 3]]], [[[3, 3]], [[1]]]], - [[[[3]], [[2, 3]]], [[[3, 3]], [[2]]]], - [[[[3]], [[3, 3]]], [[[3, 3]], [[3]]]]] + EXAMPLES:: + + sage: B = crystals.elementary.B(['A',2], 1) + sage: H = B._Hom_(B); H + Set of Crystal Morphisms from The 1-elementary crystal of type ['A', 2] + to The 1-elementary crystal of type ['A', 2] """ - if index_set is None: - index_set = self.index_set() - if similarity_factor is None: - similarity_factor = dict( (i,1) for i in index_set ) - if similarity_factor_domain is None: - similarity_factor_domain = dict( (i,1) for i in index_set ) - if direction == 'down': - e_string = 'e_string' - else: - e_string = 'f_string' - if direction_image == 'down': - f_string = 'f_string' - else: - f_string = 'e_string' - - if acyclic: - if isinstance(g, dict): - g = g.__getitem__ - - def morphism(b): - for i in index_set: - c = getattr(b, e_string)([i for k in range(similarity_factor_domain[i])]) - if c is not None: - d = getattr(morphism(c), f_string)([automorphism(i) for k in range(similarity_factor[i])]) - if d is not None: - return d - else: - raise ValueError("This is not a morphism!") - #now we know that b is hw - return g(b) + if category is None: + category = self.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 CrystalHomset(self, Y, category=category, **options) + + def crystal_morphism(self, on_gens, codomain=None, + cartan_type=None, index_set=None, generators=None, + automorphism=None, + virtualization=None, scaling_factors=None, + category=None, check=True): + r""" + Construct a crystal morphism from ``self`` to another crystal + ``codomain``. - else: - import copy - morphism = copy.copy(g) - known = set( g.keys() ) - todo = copy.copy(known) - images = set( [g[x] for x in known] ) - # Invariants: - # - images contains all known morphism(x) - # - known contains all elements x for which we know morphism(x) - # - todo contains all elements x for which we haven't propagated to each child - while todo != set( [] ): - x = todo.pop() - for i in index_set: - eix = getattr(x, f_string)([i for k in range(similarity_factor_domain[i])]) - eigx = getattr(morphism[x], f_string)([automorphism(i) for k in range(similarity_factor[i])]) - if bool(eix is None) != bool(eigx is None): - # This is not a crystal morphism! - raise ValueError("This is not a morphism!") #, print("x="x,"g(x)="g(x),"i="i) - if (eix is not None) and (eix not in known): - todo.add(eix) - known.add(eix) - morphism[eix] = eigx - images.add(eigx) - # Check that the module generators are indeed module generators - assert(len(known) == self.cardinality()) - # We may not want to systematically run those tests, - # to allow for non bijective crystal morphism - # Add an option CheckBijective? - if not ( len(known) == len(images) and len(images) == images.pop().parent().cardinality() ): - return(None) - return morphism.__getitem__ - - if cached: - return morphism - else: - return CachedFunction(morphism) + INPUT: + + - ``on_gens`` -- a function or list that determines the image + of the generators (if given a list, then this uses the order + of the generators of the domain) of ``self`` under the + crystal morphism + - ``codomain`` -- (default: ``self``) the codomain of the morphism + - ``cartan_type`` -- (optional) the Cartan type of the morphism; + the default is the Cartan type of ``self`` + - ``index_set`` -- (optional) the index set of the morphism; + the default is the index set of the Cartan type + - ``generators`` -- (optional) the generators to define the + morphism; the default is the generators of ``self`` + - ``automorphism`` -- (optional) the automorphism to perform the + twisting + - ``virtualization`` -- (optional) a dictionary whose keys are + in the index set of the domain and whose values are lists of + entries in the index set of the codomain; the default is the + identity dictionary + - ``scaling_factors`` -- (optional) a dictionary whose keys are + in the index set of the domain and whose values are scaling + factors for the weight, `\varepsilon` and `\varphi`; the + default are all scaling factors to be one + - ``category`` -- (optional) the category for the crystal morphism; + the default is the category of :class:`Crystals`. + - ``check`` -- (default: ``True``) check if the crystal morphism + is valid + + .. SEEALSO:: + + For more examples, see + :class:`sage.categories.crystals.CrystalHomset`. + + EXAMPLES: + + We construct the natural embedding of a crystal using tableaux + into the tensor product of single boxes via the reading word:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: F = crystals.Tableaux(['A',2], shape=[1]) + sage: T = crystals.TensorProduct(F, F, F) + sage: mg = T.highest_weight_vectors()[2]; mg + [[[1]], [[2]], [[1]]] + sage: psi = B.crystal_morphism([mg], codomain=T); psi + ['A', 2] Crystal morphism: + From: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + To: Full tensor product of the crystals + [The crystal of tableaux of type ['A', 2] and shape(s) [[1]], + The crystal of tableaux of type ['A', 2] and shape(s) [[1]], + The crystal of tableaux of type ['A', 2] and shape(s) [[1]]] + Defn: [2, 1, 1] |--> [[[1]], [[2]], [[1]]] + sage: b = B.module_generators[0] + sage: b.pp() + 1 1 + 2 + sage: psi(b) + [[[1]], [[2]], [[1]]] + sage: psi(b.f(2)) + [[[1]], [[3]], [[1]]] + sage: psi(b.f_string([2,1,1])) + [[[2]], [[3]], [[2]]] + sage: lw = b.to_lowest_weight()[0] + sage: lw.pp() + 2 3 + 3 + sage: psi(lw) + [[[3]], [[3]], [[2]]] + sage: psi(lw) == mg.to_lowest_weight()[0] + True + + We now take the other isomorphic highest weight component + in the tensor product:: + + sage: mg = T.highest_weight_vectors()[1]; mg + [[[2]], [[1]], [[1]]] + sage: psi = B.crystal_morphism([mg], codomain=T) + sage: psi(lw) + [[[3]], [[2]], [[3]]] + + We construct a crystal morphism of classical crystals using a + Kirillov-Reshetikhin crystal:: + + sage: B = crystals.Tableaux(['D', 4], shape=[1,1]) + sage: K = crystals.KirillovReshetikhin(['D',4,1], 2,2) + sage: K.module_generators + [[], [[1], [2]], [[1, 1], [2, 2]]] + sage: v = K.module_generators[1] + sage: psi = B.crystal_morphism([v], codomain=K, category=FiniteCrystals()) + sage: psi + ['D', 4] -> ['D', 4, 1] Virtual Crystal morphism: + From: The crystal of tableaux of type ['D', 4] and shape(s) [[1, 1]] + To: Kirillov-Reshetikhin crystal of type ['D', 4, 1] with (r,s)=(2,2) + Defn: [2, 1] |--> [[1], [2]] + sage: b = B.module_generators[0] + sage: psi(b) + [[1], [2]] + sage: psi(b.to_lowest_weight()[0]) + [[-2], [-1]] + + We can define crystal morphisms using a different set of + generators. For example, we construct an example using the + lowest weight vector:: + + sage: B = crystals.Tableaux(['A',2], shape=[1]) + sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights() + sage: T = crystals.elementary.T(['A',2], La[2]) + sage: Bp = T.tensor(B) + sage: C = crystals.Tableaux(['A',2], shape=[2,1]) + sage: x = C.module_generators[0].f_string([1,2]) + sage: psi = Bp.crystal_morphism([x], generators=Bp.lowest_weight_vectors()) + sage: psi(Bp.highest_weight_vector()) + [[1, 1], [2]] + + We can also use a dictonary to specify the generators and + their images:: + + sage: psi = Bp.crystal_morphism({Bp.lowest_weight_vectors()[0]: x}) + sage: psi(Bp.highest_weight_vector()) + [[1, 1], [2]] + + We construct a twisted crystal morphism induced from the diagram + automorphism of type `A_3^{(1)}`:: + + sage: La = RootSystem(['A',3,1]).weight_lattice(extended=True).fundamental_weights() + sage: B0 = crystals.GeneralizedYoungWalls(3, La[0]) + sage: B1 = crystals.GeneralizedYoungWalls(3, La[1]) + sage: phi = B0.crystal_morphism(B1.module_generators, automorphism={0:1, 1:2, 2:3, 3:0}) + sage: phi + ['A', 3, 1] Twisted Crystal morphism: + From: Highest weight crystal of generalized Young walls of Cartan type ['A', 3, 1] and highest weight Lambda[0] + To: Highest weight crystal of generalized Young walls of Cartan type ['A', 3, 1] and highest weight Lambda[1] + Defn: [] |--> [] + sage: x = B0.module_generators[0].f_string([0,1,2,3]); x + [[0, 3], [1], [2]] + sage: phi(x) + [[], [1, 0], [2], [3]] + + We construct a virtual crystal morphism from type `G_2` into + type `D_4`:: + + sage: D = crystals.Tableaux(['D',4], shape=[1,1]) + sage: G = crystals.Tableaux(['G',2], shape=[1]) + sage: psi = G.crystal_morphism(D.module_generators, + ....: virtualization={1:[2],2:[1,3,4]}, + ....: scaling_factors={1:1, 2:1}) + sage: for x in G: + ....: ascii_art(x, psi(x), sep=' |--> ') + ....: print "" + 1 + 1 |--> 2 + + 1 + 2 |--> 3 + + 2 + 3 |--> -3 + + 3 + 0 |--> -3 + + 3 + -3 |--> -2 + + -3 + -2 |--> -1 + + -2 + -1 |--> -1 + """ + # Determine the codomain + if codomain is None: + if hasattr(on_gens, 'codomain'): + codomain = on_gens.codomain() + elif isinstance(on_gens, (list, tuple)): + if on_gens: + codomain = on_gens[0].parent() + elif isinstance(on_gens, dict): + if on_gens: + codomain = on_gens.values()[0].parent() + else: + for x in self.module_generators: + y = on_gens(x) + if y is not None: + codomain = y.parent() + break + if codomain is None: + codomain = self + elif codomain not in Crystals(): + raise ValueError("the codomain must be a crystal") + + homset = Hom(self, codomain, category=category) + return homset(on_gens, cartan_type, index_set, generators, + automorphism, virtualization, scaling_factors, check) def digraph(self, subset=None, index_set=None): """ @@ -459,10 +738,10 @@ def digraph(self, subset=None, index_set=None): INPUT: - - ``subset`` -- (Optional) A subset of vertices for + - ``subset`` -- (optional) a subset of vertices for which the digraph should be constructed - - ``index_set`` -- (Optional) The index set to draw arrows + - ``index_set`` -- (optional) the index set to draw arrows EXAMPLES:: @@ -470,29 +749,29 @@ def digraph(self, subset=None, index_set=None): sage: C.digraph() Digraph on 6 vertices - The edges of the crystal graph are by default colored using blue for edge 1, red for edge 2, - and green for edge 3:: + The edges of the crystal graph are by default colored using + blue for edge 1, red for edge 2, and green for edge 3:: sage: C = Crystals().example(3) sage: G = C.digraph() - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) + sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) One may also overwrite the colors:: sage: C = Crystals().example(3) sage: G = C.digraph() sage: G.set_latex_options(color_by_label = {1:"red", 2:"purple", 3:"blue"}) - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) + sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) Or one may add colors to yet unspecified edges:: sage: C = Crystals().example(4) sage: G = C.digraph() sage: C.cartan_type()._index_set_coloring[4]="purple" - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) + sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) - Here is an example of how to take the top part up to a given depth of an infinite dimensional - crystal:: + Here is an example of how to take the top part up to a + given depth of an infinite dimensional crystal:: sage: C = CartanType(['C',2,1]) sage: La = C.root_system().weight_lattice().fundamental_weights() @@ -511,15 +790,12 @@ def digraph(self, subset=None, index_set=None): the ``subset`` option:: sage: B = crystals.Tableaux(['A',2], shape=[2,1]) - sage: C = CombinatorialFreeModule(QQ,B) sage: t = B.highest_weight_vector() - sage: b = C(t) - sage: D = B.demazure_operator(b,[2,1]); D - B[[[1, 1], [2]]] + B[[[1, 2], [2]]] + B[[[1, 3], [2]]] + B[[[1, 1], [3]]] + B[[[1, 3], [3]]] - sage: G = B.digraph(subset=D.support()) - sage: G.vertices() - [[[1, 1], [2]], [[1, 2], [2]], [[1, 3], [2]], [[1, 1], [3]], [[1, 3], [3]]] - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) + sage: D = B.demazure_subcrystal(t, [2,1]) + sage: list(D) + [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]], + [[1, 3], [2]], [[1, 3], [3]]] + sage: view(D, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) We can also choose to display particular arrows using the ``index_set`` option:: @@ -528,9 +804,9 @@ def digraph(self, subset=None, index_set=None): sage: G = C.digraph(index_set=[1,3]) sage: len(G.edges()) 20 - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) + sage: view(G, tightpage=True) # optional - dot2tex graphviz, not tested (opens external window) - TODO: add more tests + .. TODO:: Add more tests. """ from sage.graphs.all import DiGraph from sage.categories.highest_weight_crystals import HighestWeightCrystals @@ -602,7 +878,7 @@ def _latex_(self, **options): sage: T = crystals.Tableaux(['A',2],shape=[1]) sage: T._latex_() # optional - dot2tex graphviz '...tikzpicture...' - sage: view(T, pdflatex = True, tightpage = True) # optional - dot2tex graphviz, not tested (opens external window) + sage: view(T, tightpage = True) # optional - dot2tex graphviz, not tested (opens external window) One can for example also color the edges using the following options:: @@ -758,7 +1034,7 @@ def metapost(self, filename, thicklines=False, labels=True, scaling_factor=1.0, def dot_tex(self): r""" - Returns a dot_tex string representation of ``self``. + Return a dot_tex string representation of ``self``. EXAMPLES:: @@ -767,7 +1043,6 @@ def dot_tex(self): 'digraph G { \n node [ shape=plaintext ];\n N_0 [ label = " ", texlbl = "$1$" ];\n N_1 [ label = " ", texlbl = "$2$" ];\n N_2 [ label = " ", texlbl = "$3$" ];\n N_0 -> N_1 [ label = " ", texlbl = "1" ];\n N_1 -> N_2 [ label = " ", texlbl = "2" ];\n}' """ import re - from sage.combinat import ranker rank = ranker.from_list(self.list())[0] vertex_key = lambda x: "N_"+str(rank(x)) @@ -798,7 +1073,7 @@ def dot_tex(self): def plot(self, **options): """ - Returns the plot of self as a directed graph. + Return the plot of ``self`` as a directed graph. EXAMPLES:: @@ -810,7 +1085,7 @@ def plot(self, **options): def plot3d(self, **options): """ - Returns the 3-dimensional plot of self as a directed graph. + Return the 3-dimensional plot of ``self`` as a directed graph. EXAMPLES:: @@ -849,6 +1124,101 @@ def tensor(self, *crystals, **options): from sage.combinat.crystals.tensor_product import TensorProductOfCrystals return TensorProductOfCrystals(self, *crystals, **options) + def direct_sum(self, X): + """ + Return the direct sum of ``self`` with ``X``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: B.direct_sum(C) + Direct sum of the crystals Family + (The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]], + The crystal of letters for type ['A', 2]) + + As a shorthand, we can use ``+``:: + + sage: B + C + Direct sum of the crystals Family + (The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]], + The crystal of letters for type ['A', 2]) + """ + if X not in Crystals(): + raise ValueError("{} is not a crystal".format(X)) + from sage.combinat.crystals.direct_sum import DirectSumOfCrystals + return DirectSumOfCrystals([self, X]) + + __add__ = direct_sum + + @abstract_method(optional=True) + def connected_components_generators(self): + """ + Return a tuple of generators for each of the connected components + of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: T = crystals.TensorProduct(B,C) + sage: T.connected_components_generators() + ([[[1, 1], [2]], 1], [[[1, 2], [2]], 1], [[[1, 2], [3]], 1]) + """ + + def connected_components(self): + """ + Return the connected components of ``self`` as subcrystals. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: T = crystals.TensorProduct(B,C) + sage: T.connected_components() + [Subcrystal of Full tensor product of the crystals + [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]], + The crystal of letters for type ['A', 2]], + Subcrystal of Full tensor product of the crystals + [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]], + The crystal of letters for type ['A', 2]], + Subcrystal of Full tensor product of the crystals + [The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]], + The crystal of letters for type ['A', 2]]] + """ + return [self.subcrystal(generators=[mg]) + for mg in self.connected_components_generators()] + + def number_of_connected_components(self): + """ + Return the number of connected components of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: T = crystals.TensorProduct(B,C) + sage: T.number_of_connected_components() + 3 + """ + return len(self.connected_components_generators()) + + def is_connected(self): + """ + Return ``True`` if ``self`` is a connected crystal. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: T = crystals.TensorProduct(B,C) + sage: B.is_connected() + True + sage: T.is_connected() + False + """ + return self.number_of_connected_components() == 1 + class ElementMethods: @cached_method @@ -877,7 +1247,7 @@ def cartan_type(self): @abstract_method def e(self, i): r""" - Returns `e_i(x)` if it exists or ``None`` otherwise. + Return `e_i` of ``self`` if it exists or ``None`` otherwise. This method should be implemented by the element class of the crystal. @@ -894,7 +1264,7 @@ def e(self, i): @abstract_method def f(self, i): r""" - Returns `f_i(x)` if it exists or ``None`` otherwise. + Return `f_i` of ``self`` if it exists or ``None`` otherwise. This method should be implemented by the element class of the crystal. @@ -994,7 +1364,8 @@ def Phi(self): def f_string(self, list): r""" - Applies `f_{i_r} ... f_{i_1}` to self for `list = [i_1, ..., i_r]` + Applies `f_{i_r} \cdots f_{i_1}` to self for ``list`` as + `[i_1, ..., i_r]` EXAMPLES:: @@ -1013,7 +1384,8 @@ def f_string(self, list): def e_string(self, list): r""" - Applies `e_{i_r} ... e_{i_1}` to self for `list = [i_1, ..., i_r]` + Applies `e_{i_r} \cdots e_{i_1}` to self for ``list`` as + `[i_1, ..., i_r]` EXAMPLES:: @@ -1032,19 +1404,19 @@ def e_string(self, list): def s(self, i): r""" - Returns the reflection of ``self`` along its `i`-string + Return the reflection of ``self`` along its `i`-string. EXAMPLES:: sage: C = crystals.Tableaux(['A',2], shape=[2,1]) - sage: b=C(rows=[[1,1],[3]]) + sage: b = C(rows=[[1,1],[3]]) sage: b.s(1) [[2, 2], [3]] - sage: b=C(rows=[[1,2],[3]]) + sage: b = C(rows=[[1,2],[3]]) sage: b.s(2) [[1, 2], [3]] - sage: T=crystals.Tableaux(['A',2],shape=[4]) - sage: t=T(rows=[[1,2,2,2]]) + sage: T = crystals.Tableaux(['A',2],shape=[4]) + sage: t = T(rows=[[1,2,2,2]]) sage: t.s(1) [[1, 1, 1, 2]] """ @@ -1226,28 +1598,37 @@ def all_paths_to_highest_weight(self, index_set=None): if hw: yield [] - def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both"): + def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both", + contained=None, cartan_type=None, category=None): r""" Construct the subcrystal generated by ``self`` using `e_i` and/or `f_i` for all `i` in ``index_set``. INPUT: - - ``index_set`` -- (Default: ``None``) The index set; if ``None`` + - ``index_set`` -- (default: ``None``) the index set; if ``None`` then use the index set of the crystal - - ``max_depth`` -- (Default: infinity) The maximum depth to build + - ``max_depth`` -- (default: infinity) the maximum depth to build + + - ``direction`` -- (default: ``'both'``) the direction to build + the subcrystal; it can be one of the following: + + - ``'both'`` - using both `e_i` and `f_i` + - ``'upper'`` - using `e_i` + - ``'lower'`` - using `f_i` + + - ``contained`` -- (optional) a set (or function) defining the + containment in the subcrystal - - ``direction`` -- (Default: ``'both'``) The direction to build - the subcrystal. It can be one of the following: + - ``cartan_type`` -- (optional) specify the Cartan type of the + subcrystal - - ``'both'`` - Using both `e_i` and `f_i` - - ``'upper'`` - Using `e_i` - - ``'lower'`` - Using `f_i` + - ``category`` -- (optional) specify the category of the subcrystal .. SEEALSO:: - - :meth:`Crystals.ParentMethods.subcrystal()`. + - :meth:`Crystals.ParentMethods.subcrystal()` EXAMPLES:: @@ -1302,3 +1683,806 @@ def extra_super_categories(self): Finite = LazyImport('sage.categories.finite_crystals', 'FiniteCrystals') +############################################################################### +## Morphisms + +class CrystalMorphism(Morphism): + r""" + A crystal morphism. + + INPUT: + + - ``parent`` -- a homset + - ``cartan_type`` -- (optional) a Cartan type; the default is the + Cartan type of the domain + - ``virtualization`` -- (optional) a dictionary whose keys are in + the index set of the domain and whose values are lists of entries + in the index set of the codomain + - ``scaling_factors`` -- (optional) a dictionary whose keys are in + the index set of the domain and whose values are scaling factors + for the weight, `\varepsilon` and `\varphi` + """ + def __init__(self, parent, cartan_type=None, + virtualization=None, scaling_factors=None): + """ + Initialize ``self``. + + TESTS:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: H = Hom(B, B) + sage: psi = H.an_element() + """ + if cartan_type is None: + cartan_type = parent.domain().cartan_type() + self._cartan_type = cartan_type + + index_set = cartan_type.index_set() + if scaling_factors is None: + scaling_factors = {i: 1 for i in index_set} + if virtualization is None: + virtualization = {i: (i,) for i in index_set} + elif not isinstance(virtualization, dict): + try: + virtualization = dict(virtualization) + except (TypeError, ValueError): + virtualization = {i: (virtualization(i),) for i in index_set} + self._virtualization = Family(virtualization) + self._scaling_factors = Family(scaling_factors) + + Morphism.__init__(self, parent) + + def _repr_type(self): + """ + Used internally in printing this morphism. + + TESTS:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: H = Hom(B, B) + sage: psi = H.an_element() + sage: psi._repr_type() + "['A', 2] Crystal" + + sage: psi = H(lambda x: None, index_set=[1]) + sage: psi._repr_type() + "['A', 1] -> ['A', 2] Virtual Crystal" + + sage: B = crystals.Tableaux(['A',3], shape=[1]) + sage: BT = crystals.Tableaux(['A',3], shape=[1,1,1]) + sage: psi = B.crystal_morphism(BT.module_generators, automorphism={1:3, 2:2, 3:1}) + sage: psi._repr_type() + "['A', 3] Twisted Crystal" + + sage: KD = crystals.KirillovReshetikhin(['D',3,1], 2,1) + sage: KA = crystals.KirillovReshetikhin(['A',3,1], 2,1) + sage: psi = KD.crystal_morphism(KA.module_generators) + sage: psi._repr_type() + "['D', 3, 1] -> ['A', 3, 1] Virtual Crystal" + """ + if self.codomain().cartan_type() != self._cartan_type: + return "{} -> {} Virtual Crystal".format(self._cartan_type, self.codomain().cartan_type()) + if any(self._virtualization[i] != (i,) for i in self._cartan_type.index_set()): + return "{} Twisted Crystal".format(self._cartan_type) + return "{} Crystal".format(self._cartan_type) + + def cartan_type(self): + """ + Return the Cartan type of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: psi = Hom(B, B).an_element() + sage: psi.cartan_type() + ['A', 2] + """ + return self._cartan_type + + # This is needed because is_injective is defined in a superclass, so + # we can't overwrite it with the category + def is_injective(self): + """ + Return if ``self`` is an injective crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: psi = Hom(B, B).an_element() + sage: psi.is_injective() + False + """ + return self.is_embedding() + + # This is here because is_surjective is defined in a superclass, so + # we can't overwrite it with the category + # TODO: This could be moved to sets + @cached_method + def is_surjective(self): + """ + Check if ``self`` is a surjective crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['C',2], shape=[1,1]) + sage: C = crystals.Tableaux(['C',2], ([2,1], [1,1])) + sage: psi = B.crystal_morphism(C.module_generators[1:], codomain=C) + sage: psi.is_surjective() + False + sage: im_gens = [None, B.module_generators[0]] + sage: psi = C.crystal_morphism(im_gens, codomain=B) + sage: psi.is_surjective() + True + + sage: C = crystals.Tableaux(['A',2], shape=[2,1]) + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights() + sage: W = crystals.elementary.T(['A',2], La[1]+La[2]) + sage: T = W.tensor(B) + sage: mg = T(W.module_generators[0], B.module_generators[0]) + sage: psi = Hom(C,T)([mg]) + sage: psi.is_surjective() + False + """ + if self.domain().cardinality() == float('inf'): + raise NotImplementedError("unable to determine if surjective") + if self.domain().cardinality() < self.codomain().cardinality(): + return False + S = set(self.codomain()) + for x in self.domain(): + S.discard(self(x)) + if not S: + return True + return False + + def __call__(self, x, *args, **kwds): + """ + Apply this map to ``x``. We need to do special processing + for ``None``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: F = crystals.Tableaux(['A',2], shape=[1]) + sage: T = crystals.TensorProduct(F, F, F) + sage: H = Hom(T, B) + sage: b = B.module_generators[0] + sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors()) + sage: psi(None) + sage: [psi(v) for v in T.highest_weight_vectors()] + [None, [[1, 1], [2]], [[1, 1], [2]], None] + """ + if x is None: + return None + return super(CrystalMorphism, self).__call__(x, *args, **kwds) + + def virtualization(self): + """ + Return the virtualization sets `\sigma_i`. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: psi.virtualization() + Finite family {1: (1,), 2: (2,), 3: (3, 4)} + """ + return self._virtualization + + def scaling_factors(self): + """ + Return the scaling factors `\gamma_i`. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: psi.scaling_factors() + Finite family {1: 2, 2: 2, 3: 1} + """ + return self._scaling_factors + +class CrystalMorphismByGenerators(CrystalMorphism): + r""" + A crystal morphism defined by a set of generators which create a virtual + crystal inside the codomain. + + INPUT: + + - ``parent`` -- a homset + - ``on_gens`` -- a function or list that determines the image of the + generators (if given a list, then this uses the order of the + generators of the domain) of the domain under ``self`` + - ``cartan_type`` -- (optional) a Cartan type; the default is the + Cartan type of the domain + - ``virtualization`` -- (optional) a dictionary whose keys are in + the index set of the domain and whose values are lists of entries + in the index set of the codomain + - ``scaling_factors`` -- (optional) a dictionary whose keys are in + the index set of the domain and whose values are scaling factors + for the weight, `\varepsilon` and `\varphi` + - ``gens`` -- (optional) a finite list of generators to define the + morphism; the default is to use the highest weight vectors of the crystal + - ``check`` -- (default: ``True``) check if the crystal morphism is valid + + .. SEEALSO:: + + :meth:`sage.categories.crystals.Crystals.ParentMethods.crystal_morphism` + """ + def __init__(self, parent, on_gens, cartan_type=None, + virtualization=None, scaling_factors=None, + gens=None, check=True): + """ + Construct a virtual crystal morphism. + + TESTS:: + + sage: B = crystals.Tableaux(['D',4], shape=[1]) + sage: H = Hom(B, B) + sage: d = {1:1, 2:2, 3:4, 4:3} + sage: psi = H(B.module_generators, automorphism=d) + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + """ + CrystalMorphism.__init__(self, parent, cartan_type, + virtualization, scaling_factors) + + if gens is None: + if isinstance(on_gens, dict): + gens = on_gens.keys() + else: + gens = parent.domain().module_generators + self._gens = tuple(gens) + + # Make sure on_gens is a function + if isinstance(on_gens, dict): + f = lambda x: on_gens[x] + elif isinstance(on_gens, (list, tuple)): + if len(self._gens) != len(on_gens): + raise ValueError("invalid generator images") + d = {x: y for x,y in zip(self._gens, on_gens)} + f = lambda x: d[x] + else: + f = on_gens + self._on_gens = f + self._path_mg_cache = {x: (x, [], []) for x in self._gens} + + # Now that everything is initialized, run the check (if it is wanted) + if check: + self._check() + + def _repr_defn(self): + """ + Used in constructing string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: F = crystals.Tableaux(['A',2], shape=[1]) + sage: T = crystals.TensorProduct(F, F, F) + sage: H = Hom(T, B) + sage: b = B.highest_weight_vector() + sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors()) + sage: print psi._repr_defn() + [[[1]], [[1]], [[1]]] |--> None + [[[2]], [[1]], [[1]]] |--> [2, 1, 1] + [[[1]], [[2]], [[1]]] |--> [2, 1, 1] + [[[3]], [[2]], [[1]]] |--> None + """ + return '\n'.join(['{} |--> {}'.format(mg, im) + for mg,im in zip(self._gens, self.im_gens())]) + + def _check(self): + """ + Check if ``self`` is a valid virtual crystal morphism. + + TESTS:: + + sage: B = crystals.Tableaux(['D',4], shape=[1]) + sage: H = Hom(B, B) + sage: d = {1:1, 2:2, 3:4, 4:3} + sage: psi = H(B.module_generators, automorphism=d) # indirect doctest + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) # indirect doctest + """ + index_set = self._cartan_type.index_set() + acx = self.domain().weight_lattice_realization().simple_coroots() + acy = self.codomain().weight_lattice_realization().simple_coroots() + v = self._virtualization + sf = self._scaling_factors + for x in self._gens: + y = self._on_gens(x) + if y is None: + continue + xwt = x.weight() + ywt = y.weight() + for i in index_set: + ind = v[i] + if any(sf[i] * xwt.scalar(acx[i]) != ywt.scalar(acy[j]) for j in ind): + raise ValueError("invalid crystal morphism: weights do not match") + if any(sf[i] * x.epsilon(i) != y.epsilon(j) for j in ind): + raise ValueError("invalid crystal morphism: epsilons are not aligned") + if any(sf[i] * x.phi(i) != y.phi(j) for j in ind): + raise ValueError("invalid crystal morphism: phis are not aligned") + + def _call_(self, x): + """ + Return the image of ``x`` under ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: H = Hom(B, B) + sage: psi = H(B.module_generators) + sage: psi(B.highest_weight_vector()) + [[1, 1], [2]] + + sage: B = crystals.Tableaux(['D',4], shape=[1]) + sage: H = Hom(B, B) + sage: d = {1:1, 2:2, 3:4, 4:3} + sage: psi = H(B.module_generators, automorphism=d) + sage: b = B.highest_weight_vector() + sage: psi(b.f_string([1,2,3])) + [[-4]] + sage: psi(b.f_string([1,2,4])) + [[4]] + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + sage: psi(B.highest_weight_vector()) + [[1, 1]] + """ + mg, ef, indices = self.to_module_generator(x) + cur = self._on_gens(mg) + for op,i in reversed(zip(ef, indices)): + if cur is None: + return None + + s = [] + sf = self._scaling_factors[i] + for j in self._virtualization[i]: + s += [j]*sf + if op == 'e': + cur = cur.f_string(s) + elif op == 'f': + cur = cur.e_string(s) + return cur + + def __nonzero__(self): + """ + Return if ``self`` is a non-zero morphism. + + EXAMPLES:: + + sage: B = crystals.elementary.Elementary(['A',2], 2) + sage: H = Hom(B, B) + sage: psi = H(B.module_generators) + sage: bool(psi) + True + sage: psi = H(lambda x: None) + sage: bool(psi) + False + """ + return any(self._on_gens(mg) is not None for mg in self._gens) + + # TODO: Does this belong in the element_class of the Crystals() category? + def to_module_generator(self, x): + """ + Return a generator ``mg`` and a path of `e_i` and `f_i` operations + to ``mg``. + + OUTPUT: + + A tuple consisting of: + + - a module generator, + - a list of ``'e'`` and ``'f'`` to denote which operation, and + - a list of matching indices. + + EXAMPLES:: + + sage: B = crystals.elementary.Elementary(['A',2], 2) + sage: psi = B.crystal_morphism(B.module_generators) + sage: psi.to_module_generator(B(4)) + (0, ['f', 'f', 'f', 'f'], [2, 2, 2, 2]) + sage: psi.to_module_generator(B(-2)) + (0, ['e', 'e'], [2, 2]) + """ + if x in self._path_mg_cache: + return self._path_mg_cache[x] + + mg = set(self._path_mg_cache.keys()) + visited = set([None, x]) + index_set = self._cartan_type.index_set() + todo = [x] + ef = [[]] + indices = [[]] + + while len(todo) > 0: + cur = todo.pop(0) + cur_ef = ef.pop(0) + cur_indices = indices.pop(0) + for i in index_set: + next = cur.e(i) + if next in mg: + gen,ef,indices = self._path_mg_cache[next] + ef = cur_ef + ['e'] + ef + indices = cur_indices + [i] + indices + self._path_mg_cache[x] = (gen, ef, indices) + return (gen, ef, indices) + if next not in visited: + todo.append(next) + ef.append(cur_ef + ['e']) + indices.append(cur_indices + [i]) + visited.add(next) + + # Now for f's + next = cur.f(i) + if next in mg: + gen,ef,indices = self._path_mg_cache[next] + ef = cur_ef + ['f'] + ef + indices = cur_indices + [i] + indices + self._path_mg_cache[x] = (gen, ef, indices) + return (gen, ef, indices) + if next not in visited: + todo.append(next) + ef.append(cur_ef + ['f']) + indices.append(cur_indices + [i]) + visited.add(next) + raise ValueError("no module generator in the component of {}".format(x)) + + @cached_method + def im_gens(self): + """ + Return the image of the generators of ``self`` as a tuple. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: F = crystals.Tableaux(['A',2], shape=[1]) + sage: T = crystals.TensorProduct(F, F, F) + sage: H = Hom(T, B) + sage: b = B.highest_weight_vector() + sage: psi = H((None, b, b, None), generators=T.highest_weight_vectors()) + sage: psi.im_gens() + (None, [[1, 1], [2]], [[1, 1], [2]], None) + """ + return tuple([self._on_gens(g) for g in self._gens]) + + def image(self): + """ + Return the image of ``self`` in the codomain as a + :class:`~sage.combinat.crystals.subcrystal.Subcrystal`. + + .. WARNING:: + + This assumes that ``self`` is a strict crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + sage: psi.image() + Virtual crystal of The crystal of tableaux of type ['D', 4] and shape(s) [[2]] of type ['B', 3] + """ + #if not self.is_strict(): + # raise NotImplementedError + from sage.combinat.crystals.subcrystal import Subcrystal + return Subcrystal(self.codomain(), + virtualization=self._virtualization, + scaling_factors=self._scaling_factors, + generators=self.im_gens(), + cartan_type=self._cartan_type, + index_set=self._cartan_type.index_set(), + category=self.domain().category()) + +############################################################################### +## Homset + +class CrystalHomset(Homset): + r""" + The set of crystal morphisms from one crystal to another. + + An `U_q(\mathfrak{g})` `I`-crystal morphism `\Psi : B \to C` is a map + `\Psi : B \cup \{ 0 \} \to C \cup \{ 0 \}` such that: + + - `\Psi(0) = 0`. + - If `b \in B` and `\Psi(b) \in C`, then + `\mathrm{wt}(\Psi(b)) = \mathrm{wt}(b)`, + `\varepsilon_i(\Psi(b)) = \varepsilon_i(b)`, and + `\varphi_i(\Psi(b)) = \varphi_i(b)` for all `i \in I`. + - If `b, b^{\prime} \in B`, `\Psi(b), \Psi(b^{\prime}) \in C` and + `f_i b = b^{\prime}`, then `f_i \Psi(b) = \Psi(b^{\prime})` and + `\Psi(b) = e_i \Psi(b^{\prime})` for all `i \in I`. + + If the Cartan type is unambiguous, it is surpressed from the notation. + + We can also generalize the definition of a crystal morphism by considering + a map of `\sigma` of the (now possibly different) Dynkin diagrams + corresponding to `B` and `C` along with scaling factors + `\gamma_i \in \ZZ` for `i \in I`. Let `\sigma_i` denote the orbit of + `i` under `\sigma`. We write objects for `B` as `X` with + corresponding objects of `C` as `\widehat{X}`. + Then a *virtual* crystal morphism `\Psi` is a map such that + the following holds: + + - `\Psi(0) = 0`. + - If `b \in B` and `\Psi(b) \in C`, then for all `j \in \sigma_i`: + + .. MATH:: + + \varepsilon_i(b) = \frac{1}{\gamma_j} \widehat{\varepsilon}_j(\Psi(b)), + \quad \varphi_i(b) = \frac{1}{\gamma_j} \widehat{\varphi}_j(\Psi(b)), + \quad \mathrm{wt}(\Psi(b)) = \sum_i c_i \sum_{j \in \sigma_i} \gamma_j + \widehat{\Lambda}_j, + + where `\mathrm{wt}(b) = \sum_i c_i \Lambda_i`. + + - If `b, b^{\prime} \in B`, `\Psi(b), \Psi(b^{\prime}) \in C` and + `f_i b = b^{\prime}`, then independent of the ordering of `\sigma_i` + we have: + + .. MATH:: + + \Psi(b^{\prime}) = e_i \Psi(b) = + \prod_{j \in \sigma_i} \widehat{e}_j^{\gamma_i} \Psi(b), \quad + \Psi(b^{\prime}) = f_i \Psi(b) = + \prod_{j \in \sigma_i} \widehat{f}_j^{\gamma_i} \Psi(b). + + If `\gamma_i = 1` for all `i \in I` and the Dynkin diagrams are + the same, then we call `\Psi` a *twisted* crystal morphism. + + INPUT: + + - ``X`` -- the domain + - ``Y`` -- the codomain + - ``category`` -- (optional) the category of the crystal morphisms + + .. SEEALSO:: + + For the construction of an element of the homset, see + :class:`CrystalMorphismByGenerators` and + :meth:`~sage.categories.crystals.Crystals.ParentMethods.crystal_morphism`. + + EXAMPLES: + + We begin with the natural embedding of `B(2\Lambda_1)` into + `B(\Lambda_1) \otimes B(\Lambda_1)` in type `A_1`:: + + sage: B = crystals.Tableaux(['A',1], shape=[2]) + sage: F = crystals.Tableaux(['A',1], shape=[1]) + sage: T = crystals.TensorProduct(F, F) + sage: v = T.highest_weight_vectors()[0]; v + [[[1]], [[1]]] + sage: H = Hom(B, T) + sage: psi = H([v]) + sage: b = B.highest_weight_vector(); b + [[1, 1]] + sage: psi(b) + [[[1]], [[1]]] + sage: b.f(1) + [[1, 2]] + sage: psi(b.f(1)) + [[[1]], [[2]]] + + We now look at the decomposition of `B(\Lambda_1) \otimes B(\Lambda_1)` + into `B(2\Lambda_1) \oplus B(0)`:: + + sage: B0 = crystals.Tableaux(['A',1], shape=[]) + sage: D = crystals.DirectSum([B, B0]) + sage: H = Hom(T, D) + sage: psi = H(D.module_generators) + sage: psi + ['A', 1] Crystal morphism: + From: Full tensor product of the crystals + [The crystal of tableaux of type ['A', 1] and shape(s) [[1]], + The crystal of tableaux of type ['A', 1] and shape(s) [[1]]] + To: Direct sum of the crystals Family + (The crystal of tableaux of type ['A', 1] and shape(s) [[2]], + The crystal of tableaux of type ['A', 1] and shape(s) [[]]) + Defn: [[[1]], [[1]]] |--> [1, 1] + [[[2]], [[1]]] |--> [] + sage: psi.is_isomorphism() + True + + We can always construct the trivial morphism which sends + everything to `0`:: + + sage: Binf = crystals.infinity.Tableaux(['B', 2]) + sage: B = crystals.Tableaux(['B',2], shape=[1]) + sage: H = Hom(Binf, B) + sage: psi = H(lambda x: None) + sage: psi(Binf.highest_weight_vector()) + + For Kirillov-Reshetikhin crystals, we consider the map to the + corresponding classical crystal:: + + sage: K = crystals.KirillovReshetikhin(['D',4,1], 2,1) + sage: B = K.classical_decomposition() + sage: H = Hom(K, B) + sage: psi = H(lambda x: x.lift(), cartan_type=['D',4]) + sage: L = [psi(mg) for mg in K.module_generators]; L + [[], [[1], [2]]] + sage: all(x.parent() == B for x in L) + True + + Next we consider a type `D_4` crystal morphism where we twist by + `3 \leftrightarrow 4`:: + + sage: B = crystals.Tableaux(['D',4], shape=[1]) + sage: H = Hom(B, B) + sage: d = {1:1, 2:2, 3:4, 4:3} + sage: psi = H(B.module_generators, automorphism=d) + sage: b = B.highest_weight_vector() + sage: b.f_string([1,2,3]) + [[4]] + sage: b.f_string([1,2,4]) + [[-4]] + sage: psi(b.f_string([1,2,3])) + [[-4]] + sage: psi(b.f_string([1,2,4])) + [[4]] + + We construct the natural virtual embedding of a type `B_3` into a type + `D_4` crystal:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + sage: psi + ['B', 3] -> ['D', 4] Virtual Crystal morphism: + From: The crystal of tableaux of type ['B', 3] and shape(s) [[1]] + To: The crystal of tableaux of type ['D', 4] and shape(s) [[2]] + Defn: [1] |--> [1, 1] + sage: for b in B: print "{} |--> {}".format(b, psi(b)) + [1] |--> [1, 1] + [2] |--> [2, 2] + [3] |--> [3, 3] + [0] |--> [3, -3] + [-3] |--> [-3, -3] + [-2] |--> [-2, -2] + [-1] |--> [-1, -1] + """ + def __init__(self, X, Y, category=None): + """ + Initialize ``self``. + + TESTS:: + + sage: B = crystals.Tableaux(['A', 2], shape=[2,1]) + sage: H = Hom(B, B) + sage: Binf = crystals.infinity.Tableaux(['B',2]) + sage: H = Hom(Binf, B) + """ + if category is None: + category = Crystals() + # TODO: Should we make one of the types of morphisms into the self.Element? + Homset.__init__(self, X, Y, category) + + def _repr_(self): + """ + TESTS:: + + sage: B = crystals.Tableaux(['A', 2], shape=[2,1]) + sage: Hom(B, B) + Set of Crystal Morphisms from The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + """ + return "Set of Crystal Morphisms from {} to {}".format(self.domain(), self.codomain()) + + def _coerce_impl(self, x): + """ + Check to see if we can coerce ``x`` into a morphism with the + correct parameters. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[2,1]) + sage: H = Hom(B, B) + sage: H(H.an_element()) # indirect doctest + ['B', 3] Crystal endomorphism of The crystal of tableaux of type ['B', 3] and shape(s) [[2, 1]] + Defn: [2, 1, 1] |--> None + """ + if not isinstance(x, CrystalMorphism): + raise TypeError + + if x.parent() is self: + return x + + # Case 1: the parent fits + if x.parent() == self: + return self.element_class(self, x._on_gens, + x._virtualization, x._scaling_factors, + x._cartan_type, x._gens) + + # TODO: Should we try extraordinary measures (like twisting)? + raise ValueError + + def __call__(self, on_gens, cartan_type=None, index_set=None, generators=None, + automorphism=None, virtualization=None, scaling_factors=None, check=True): + """ + Construct a crystal morphism. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A', 2], shape=[2,1]) + sage: H = Hom(B, B) + sage: psi = H(B.module_generators) + + sage: F = crystals.Tableaux(['A',3], shape=[1]) + sage: T = crystals.TensorProduct(F, F, F) + sage: H = Hom(B, T) + sage: v = T.highest_weight_vectors()[2] + sage: psi = H([v], cartan_type=['A',2]) + """ + if isinstance(on_gens, CrystalMorphism): + return self._coerce_impl(on_gens) + + if cartan_type is None: + cartan_type = self.domain().cartan_type() + else: + from sage.combinat.root_system.cartan_type import CartanType + cartan_type = CartanType(cartan_type) + if index_set is None: + index_set = cartan_type.index_set() + else: + cartan_type = cartan_type.subtype(index_set) + + # Try as a natural folding + if cartan_type != self.codomain().cartan_type(): + fct = self.domain().cartan_type().as_folding() + if fct.folding_of() == self.codomain().cartan_type(): + if virtualization is None: + virtualization = fct.folding_orbit() + if scaling_factors is None: + scaling_factors = fct.scaling_factors() + + if automorphism is not None: + if virtualization is not None: + raise ValueError("the automorphism and virtualization cannot both be specified") + if not isinstance(automorphism, dict): + try: + automorphism = dict(automorphism) + virtualization = {i: (automorphism[i],) for i in automorphism} + except (TypeError, ValueError): + virtualization = {i: (automorphism(i),) for i in index_set} + else: + virtualization = {i: (automorphism[i],) for i in automorphism} + + return self.element_class(self, on_gens, cartan_type, + virtualization, scaling_factors, + generators, check) + + def _an_element_(self): + """ + Return an element of ``self``. Every homset has the crystal morphism + which sends all elements to ``None``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A', 2], shape=[2,1]) + sage: C = crystals.infinity.Tableaux(['A', 2]) + sage: H = Hom(B, C) + sage: H.an_element() + ['A', 2] Crystal morphism: + From: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + To: The infinity crystal of tableaux of type ['A', 2] + Defn: [2, 1, 1] |--> None + """ + return self.element_class(self, lambda x: None) + + Element = CrystalMorphismByGenerators + diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index d409da23e77..9a3264a4bc6 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -406,7 +406,7 @@ def __invert__(self): try: inv_mat = mat.parent()(~mat) except (ZeroDivisionError, TypeError): - raise RuntimeError, "morphism is not invertible" + raise RuntimeError("morphism is not invertible") return self.codomain().module_morphism( matrix=inv_mat, codomain=self.domain(), category=self.category_for()) diff --git a/src/sage/categories/finite_enumerated_sets.py b/src/sage/categories/finite_enumerated_sets.py index eac6b4bfb62..1cce141f25e 100644 --- a/src/sage/categories/finite_enumerated_sets.py +++ b/src/sage/categories/finite_enumerated_sets.py @@ -501,9 +501,9 @@ class ParentMethods: # Ambiguity resolution between methods inherited from # Sets.CartesianProducts and from EnumeratedSets.Finite. - random_element = Sets.CartesianProducts.ParentMethods.random_element.im_func - cardinality = Sets.CartesianProducts.ParentMethods.cardinality.im_func - __iter__ = EnumeratedSets.CartesianProducts.ParentMethods.__iter__.im_func + random_element = Sets.CartesianProducts.ParentMethods.random_element.__func__ + cardinality = Sets.CartesianProducts.ParentMethods.cardinality.__func__ + __iter__ = EnumeratedSets.CartesianProducts.ParentMethods.__iter__.__func__ def last(self): r""" diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index e1c96a438e9..8bba29f47bc 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -67,7 +67,8 @@ def is_lattice(self): sage: P.is_lattice() False """ - return self.is_meet_semilattice() and self.is_join_semilattice() + return (self.cardinality() == 0 or + (self.has_bottom() and self.is_join_semilattice())) def is_selfdual(self): r""" diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index af374b203db..25b64b2e993 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -10,7 +10,8 @@ from sage.misc.cachefunc import cached_method from sage.categories.category_singleton import Category_singleton -from sage.categories.crystals import Crystals +from sage.categories.crystals import Crystals, CrystalHomset, \ + CrystalMorphismByGenerators from sage.categories.tensor import TensorProductsCategory class HighestWeightCrystals(Category_singleton): @@ -150,10 +151,11 @@ def highest_weight_vector(self): else: raise RuntimeError("The crystal does not have exactly one highest weight vector") + # TODO: Not every highest weight crystal is a lowest weight crystal @cached_method def lowest_weight_vectors(self): r""" - Returns the lowest weight vectors of ``self`` + Return the lowest weight vectors of ``self``. This default implementation selects among all elements of the crystal those that are lowest weight, and cache the result. @@ -164,16 +166,16 @@ def lowest_weight_vectors(self): sage: C = crystals.Letters(['A',5]) sage: C.lowest_weight_vectors() - [6] + (6,) :: sage: C = crystals.Letters(['A',2]) sage: T = crystals.TensorProduct(C,C,C,generators=[[C(2),C(1),C(1)],[C(1),C(2),C(1)]]) sage: T.lowest_weight_vectors() - [[3, 2, 3], [3, 3, 2]] + ([3, 2, 3], [3, 3, 2]) """ - return [g for g in self if g.is_lowest_weight()] + return tuple(g for g in self if g.is_lowest_weight()) def __iter__(self, index_set=None, max_depth = float("inf")): """ @@ -394,8 +396,48 @@ def iter_by_deg(gens): ret = P(ret, prec) return ret - class ElementMethods: + # TODO: This is not correct if a factor has multiple heads (i.e., we + # should have a category for uniqueness of highest/lowest weights) + connected_components_generators = highest_weight_vectors + + def _Hom_(self, Y, category=None, **options): + r""" + Return the homset from ``self`` to ``Y`` in the + category ``category``. + + INPUT: + + - ``Y`` -- a crystal + - ``category`` -- a subcategory of :class:`HighestWeightCrysals`() + or ``None`` + + The sole purpose of this method is to construct the homset as a + :class:`~sage.categories.highest_weight_crystals.HighestWeightCrystalHomset`. + If ``category`` is specified and is not a subcategory of + :class:`HighestWeightCrystals`, a ``TypeError`` is raised instead + + This method is not meant to be called directly. Please use + :func:`sage.categories.homset.Hom` instead. + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: H = B._Hom_(B) + sage: H + Set of Crystal Morphisms from The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + sage: type(H) + + """ + if category is None: + category = self.category() + elif not category.is_subcategory(HighestWeightCrystals()): + raise TypeError("{} is not a subcategory of HighestWeightCrystals()".format(category)) + if Y not in Crystals(): + raise TypeError("{} is not a crystal".format(Y)) + return HighestWeightCrystalHomset(self, Y, category=category, **options) + + class ElementMethods: pass class TensorProducts(TensorProductsCategory): @@ -482,3 +524,147 @@ def highest_weight_vectors(self): it.append( iter(self.crystals[-len(path)-1]) ) return tuple(ret) +############################################################################### +## Morphisms + +class HighestWeightCrystalMorphism(CrystalMorphismByGenerators): + r""" + A virtual crystal morphism whose domain is a highest weight crystal. + + INPUT: + + - ``parent`` -- a homset + - ``on_gens`` -- a function or list that determines the image of the + generators (if given a list, then this uses the order of the + generators of the domain) of the domain under ``self`` + - ``cartan_type`` -- (optional) a Cartan type; the default is the + Cartan type of the domain + - ``virtualization`` -- (optional) a dictionary whose keys are + in the index set of the domain and whose values are lists of + entries in the index set of the codomain + - ``scaling_factors`` -- (optional) a dictionary whose keys are in + the index set of the domain and whose values are scaling factors + for the weight, `\varepsilon` and `\varphi` + - ``gens`` -- (optional) a list of generators to define the morphism; + the default is to use the highest weight vectors of the crystal + - ``check`` -- (default: ``True``) check if the crystal morphism is valid + """ + def __init__(self, parent, on_gens, cartan_type=None, + virtualization=None, scaling_factors=None, + gens=None, check=True): + """ + Construct a crystal morphism. + + TESTS:: + + sage: B = crystals.infinity.Tableaux(['B',2]) + sage: C = crystals.infinity.NakajimaMonomials(['B',2]) + sage: psi = B.crystal_morphism(C.module_generators) + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + """ + if cartan_type is None: + cartan_type = parent.domain().cartan_type() + if isinstance(on_gens, dict): + gens = on_gens.keys() + I = cartan_type.index_set() + if gens is None: + if cartan_type == parent.domain().cartan_type(): + gens = parent.domain().highest_weight_vectors() + else: + gens = tuple(x for x in parent.domain() if x.is_highest_weight(I)) + self._hw_gens = True + elif check: + self._hw_gens = all(x.is_highest_weight(I) for x in gens) + else: + self._hw_gens = False + CrystalMorphismByGenerators.__init__(self, parent, on_gens, cartan_type, + virtualization, scaling_factors, + gens, check) + + def _call_(self, x): + """ + Return the image of ``x`` under ``self``. + + TESTS:: + + sage: B = crystals.infinity.Tableaux(['B',2]) + sage: C = crystals.infinity.NakajimaMonomials(['B',2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: b = B.highest_weight_vector() + sage: psi(b) + 1 + sage: c = psi(b.f_string([1,1,1,2,2,1,2,2])); c + Y(1,0)^-4 Y(2,0)^4 Y(2,1)^-4 + sage: c == C.highest_weight_vector().f_string([1,1,1,2,2,1,2,2]) + True + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: H = Hom(B, C) + sage: psi = H(C.module_generators) + sage: psi(B.module_generators[0]) + [[1, 1]] + + We check with the morphism defined on the lowest weight vector:: + + sage: B = crystals.Tableaux(['A',2], shape=[1]) + sage: La = RootSystem(['A',2]).weight_lattice().fundamental_weights() + sage: T = crystals.elementary.T(['A',2], La[2]) + sage: Bp = T.tensor(B) + sage: C = crystals.Tableaux(['A',2], shape=[2,1]) + sage: H = Hom(Bp, C) + sage: x = C.module_generators[0].f_string([1,2]) + sage: psi = H({Bp.lowest_weight_vectors()[0]: x}) + sage: psi + ['A', 2] Crystal morphism: + From: Full tensor product of the crystals [The T crystal of type ['A', 2] and weight (1, 1, 0), The crystal of tableaux of type ['A', 2] and shape(s) [[1]]] + To: The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] + Defn: [(1, 1, 0), [[3]]] |--> [2, 1, 3] + sage: psi(Bp.highest_weight_vector()) + [[1, 1], [2]] + """ + if not self._hw_gens: + return CrystalMorphismByGenerators._call_(self, x) + mg, path = x.to_highest_weight(self._cartan_type.index_set()) + cur = self._on_gens(mg) + for i in reversed(path): + if cur is None: + return None + s = [] + sf = self._scaling_factors[i] + for j in self._virtualization[i]: + s += [j]*sf + cur = cur.f_string(s) + return cur + +class HighestWeightCrystalHomset(CrystalHomset): + """ + The set of crystal morphisms from a highest weight crystal to + another crystal. + + .. SEEALSO:: + + See :class:`sage.categories.crystals.CrystalHomset` for more + information. + """ + def __init__(self, X, Y, category=None): + """ + Initialize ``self``. + + TESTS:: + + sage: B = crystals.Tableaux(['A', 2], shape=[2,1]) + sage: H = Hom(B, B) + sage: B = crystals.infinity.Tableaux(['B',2]) + sage: H = Hom(B, B) + """ + if category is None: + category = HighestWeightCrystals() + CrystalHomset.__init__(self, X, Y, category) + + Element = HighestWeightCrystalMorphism + diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index f5cc1669f42..8065f50b367 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -341,9 +341,6 @@ cdef class Morphism(Map): definition = repr(self) return hash((domain, codomain, definition)) - 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/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py index cd44b4282ad..b1aecb420c9 100644 --- a/src/sage/categories/regular_crystals.py +++ b/src/sage/categories/regular_crystals.py @@ -21,6 +21,7 @@ from sage.categories.category_singleton import Category_singleton from sage.categories.crystals import Crystals from sage.categories.tensor import TensorProductsCategory +from sage.combinat.subset import Subsets from sage.graphs.dot2tex_utils import have_dot2tex class RegularCrystals(Category_singleton): @@ -122,6 +123,38 @@ def additional_structure(self): """ return None + class MorphismMethods: + def is_isomorphism(self): + """ + Check if ``self`` is a crystal isomorphism, which is true + if and only if this is a strict embedding with the same number + of connected components. + + EXAMPLES:: + + sage: La = RootSystem(['A',2,1]).weight_space(extended=True).fundamental_weights() + sage: B = crystals.LSPaths(La[0]) + sage: La = RootSystem(['A',2,1]).weight_lattice(extended=True).fundamental_weights() + sage: C = crystals.GeneralizedYoungWalls(2, La[0]) + sage: H = Hom(B, C) + sage: from sage.categories.highest_weight_crystals import HighestWeightCrystalMorphism + sage: class Psi(HighestWeightCrystalMorphism): + ....: def is_strict(self): + ....: return True + sage: psi = Psi(H, C.module_generators) + sage: psi + ['A', 2, 1] Crystal morphism: + From: The crystal of LS paths of type ['A', 2, 1] and weight Lambda[0] + To: Highest weight crystal of generalized Young walls of Cartan type ['A', 2, 1] + and highest weight Lambda[0] + Defn: (Lambda[0],) |--> [] + sage: psi.is_isomorphism() + True + """ + return (self.is_strict() + and self.domain().number_of_connected_components() == + self.codomain().number_of_connected_components()) + class ParentMethods: # TODO: this could be a method in Crystals.Algebras.ElementMethods, so that @@ -176,6 +209,64 @@ def demazure_operator(self, element, reduced_word): for c, coeff in element) return element + def demazure_subcrystal(self, element, reduced_word, only_support=True): + r""" + Return the subcrystal corresponding to the application of + Demazure operators `D_i` for `i` from ``reduced_word`` on + ``element``. + + INPUT: + + - ``element`` -- an element of a free module indexed by the + underlying crystal + - ``reduced_word`` -- a reduced word of the Weyl group of the + same type as the underlying crystal + - ``only_support`` -- (default: ``True``) only include arrows + corresponding the the support of ``reduced_word`` + + OUTPUT: + + - the Demazure subcrystal + + EXAMPLES:: + + sage: T = crystals.Tableaux(['A',2], shape=[2,1]) + sage: t = T.highest_weight_vector() + sage: S = T.demazure_subcrystal(t, [1,2]) + sage: list(S) + [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]], + [[1, 2], [3]], [[2, 2], [3]]] + sage: S = T.demazure_subcrystal(t, [2,1]) + sage: list(S) + [[[1, 1], [2]], [[1, 1], [3]], [[1, 2], [2]], + [[1, 3], [2]], [[1, 3], [3]]] + + We construct an example where we don't only want the arrows + indicated by the support of the reduced word:: + + sage: K = crystals.KirillovReshetikhin(['A',1,1], 1, 2) + sage: mg = K.module_generator() + sage: S = K.demazure_subcrystal(mg, [1]) + sage: S.digraph().edges() + [([[1, 1]], [[1, 2]], 1), ([[1, 2]], [[2, 2]], 1)] + sage: S = K.demazure_subcrystal(mg, [1], only_support=False) + sage: S.digraph().edges() + [([[1, 1]], [[1, 2]], 1), + ([[1, 2]], [[1, 1]], 0), + ([[1, 2]], [[2, 2]], 1), + ([[2, 2]], [[1, 2]], 0)] + """ + from sage.combinat.free_module import CombinatorialFreeModule + from sage.rings.all import QQ + C = CombinatorialFreeModule(QQ, self) + D = self.demazure_operator(C(element), reduced_word) + if only_support: + index_set = tuple(frozenset(reduced_word)) + else: + index_set = self.cartan_type().index_set() + return self.subcrystal(contained=D.support(), generators=[element], + index_set=index_set) + def _test_stembridge_local_axioms(self, index_set=None, verbose=False, complete=False, **options): r""" This implements tests for the Stembridge local characterization @@ -661,7 +752,6 @@ def _test_stembridge_local_axioms(self, index_set=None, verbose=False, **options sage: t._test_stembridge_local_axioms(verbose=True) True """ - from sage.combinat.subset import Subsets tester = self._tester(**options) goodness=True if index_set is None: index_set=self.index_set() diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index 15a43bf02c3..d974f1312a4 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -169,7 +169,7 @@ class ElementMethods: + 2*B[word: bab] + 2*B[word: baba] + 3*B[word: babb] + B[word: babbab] + 9*B[word: bb] + 3*B[word: bbab] """ - __mul__ = Magmas.ElementMethods.__mul__.im_func + __mul__ = Magmas.ElementMethods.__mul__.__func__ # __imul__ = __mul__ diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index edbd32e5dac..d97b3b99c87 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -3,30 +3,7 @@ The ``codes`` object may be used to access the codes that Sage can build. -- :func:`codes.BCHCode ` -- :func:`codes.BinaryGolayCode ` -- :func:`codes.BinaryReedMullerCode ` -- :func:`codes.CyclicCode ` -- :func:`codes.CyclicCodeFromGeneratingPolynomial ` -- :func:`codes.CyclicCodeFromCheckPolynomial ` -- :func:`codes.DuadicCodeEvenPair ` -- :func:`codes.DuadicCodeOddPair ` -- :func:`codes.ExtendedBinaryGolayCode ` -- :func:`codes.ExtendedQuadraticResidueCode ` -- :func:`codes.ExtendedTernaryGolayCode ` -- :func:`codes.HammingCode ` -- :func:`codes.LinearCodeFromCheckMatrix ` -- :func:`codes.QuadraticResidueCode ` -- :func:`codes.QuadraticResidueCodeEvenPair ` -- :func:`codes.QuadraticResidueCodeOddPair ` -- :func:`codes.QuasiQuadraticResidueCode ` -- :func:`codes.RandomLinearCode ` -- :func:`codes.RandomLinearCodeGuava ` -- :func:`codes.ReedSolomonCode ` -- :func:`codes.TernaryGolayCode ` -- :func:`codes.ToricCode ` -- :func:`codes.TrivialCode ` -- :func:`codes.WalshCode ` +{INDEX_OF_FUNCTIONS} .. NOTE:: @@ -52,3 +29,7 @@ ToricCode, TrivialCode, WalshCode) 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)) diff --git a/src/sage/combinat/crystals/catalog.py b/src/sage/combinat/crystals/catalog.py index 63663280594..6ba6aee2b57 100644 --- a/src/sage/combinat/crystals/catalog.py +++ b/src/sage/combinat/crystals/catalog.py @@ -20,6 +20,7 @@ * :class:`GeneralizedYoungWalls ` * :func:`HighestWeight ` +* :class:`Induced ` * :func:`KirillovReshetikhin ` * :class:`KyotoPathModel ` * :class:`Letters ` @@ -66,6 +67,8 @@ from sage.combinat.crystals.kirillov_reshetikhin import KirillovReshetikhinCrystal as KirillovReshetikhin from sage.combinat.rigged_configurations.rc_crystal import CrystalOfRiggedConfigurations as RiggedConfigurations +from sage.combinat.crystals.induced_structure import InducedCrystal as Induced + from tensor_product import TensorProductOfCrystals as TensorProduct from direct_sum import DirectSumOfCrystals as DirectSum diff --git a/src/sage/combinat/crystals/direct_sum.py b/src/sage/combinat/crystals/direct_sum.py index 9e84d995dc1..c0aef6c6479 100644 --- a/src/sage/combinat/crystals/direct_sum.py +++ b/src/sage/combinat/crystals/direct_sum.py @@ -19,6 +19,7 @@ from sage.structure.parent import Parent from sage.categories.category import Category from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets +from sage.sets.family import Family from sage.structure.element_wrapper import ElementWrapper from sage.structure.element import get_coercion_model @@ -81,10 +82,46 @@ class DirectSumOfCrystals(DisjointUnionEnumeratedSets): sage: TestSuite(C).run() """ - __classcall_private__ = staticmethod(DisjointUnionEnumeratedSets.__classcall_private__) + @staticmethod + def __classcall_private__(cls, crystals, facade=True, keepkey=False, category=None): + """ + Normalization of arguments; see :class:`UniqueRepresentation`. + + TESTS: + We check that direct sum of crystals have unique representation:: - def __init__(self, crystals, **options): + sage: B = crystals.Tableaux(['A',2], shape=[2,1]) + sage: C = crystals.Letters(['A',2]) + sage: D1 = crystals.DirectSum([B, C]) + sage: D2 = crystals.DirectSum((B, C)) + sage: D1 is D2 + True + sage: D3 = crystals.DirectSum([B, C, C]) + sage: D4 = crystals.DirectSum([D1, C]) + sage: D3 is D4 + True + """ + if not isinstance(facade, bool) or not isinstance(keepkey, bool): + raise TypeError + # Normalize the facade-keepkey by giving keepkey dominance + if keepkey: + facade = False + else: + facade = True + + # We expand out direct sums of crystals + ret = [] + for x in Family(crystals): + if isinstance(x, DirectSumOfCrystals): + ret += list(x.crystals) + else: + ret.append(x) + category = Category.meet([Category.join(c.categories()) for c in ret]) + return super(DirectSumOfCrystals, cls).__classcall__(cls, + Family(ret), facade=facade, keepkey=keepkey, category=category) + + def __init__(self, crystals, facade, keepkey, category, **options): """ TESTS:: @@ -99,19 +136,12 @@ def __init__(self, crystals, **options): sage: isinstance(B, DirectSumOfCrystals) True """ - if 'keepkey' in options: - keepkey = options['keepkey'] + if facade: + Parent.__init__(self, facade=tuple(crystals), category=category) else: - keepkey = False -# facade = options['facade'] - if keepkey: - facade = False - else: - facade = True - category = Category.meet([Category.join(crystal.categories()) for crystal in crystals]) - Parent.__init__(self, category = category) - DisjointUnionEnumeratedSets.__init__(self, crystals, keepkey = keepkey, facade = facade) - self.rename("Direct sum of the crystals %s"%(crystals,)) + Parent.__init__(self, category=category) + DisjointUnionEnumeratedSets.__init__(self, crystals, keepkey=keepkey, facade=facade) + self.rename("Direct sum of the crystals {}".format(crystals)) self._keepkey = keepkey self.crystals = crystals if len(crystals) == 0: diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index 8b60825f19b..d8856ec9a20 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -83,7 +83,6 @@ 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.infinity import Infinity class AbstractSingleCrystalElement(Element): r""" diff --git a/src/sage/combinat/crystals/generalized_young_walls.py b/src/sage/combinat/crystals/generalized_young_walls.py index 204ab2b74db..14bd73956ec 100644 --- a/src/sage/combinat/crystals/generalized_young_walls.py +++ b/src/sage/combinat/crystals/generalized_young_walls.py @@ -47,7 +47,6 @@ from sage.categories.highest_weight_crystals import HighestWeightCrystals from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.combinat.root_system.root_system import RootSystem -from sage.rings.infinity import Infinity class GeneralizedYoungWall(CombinatorialElement): r""" @@ -624,13 +623,14 @@ def in_highest_weight_crystal(self,La): raise TypeError("Must be an element in the weight lattice realization") ac = self.parent().weight_lattice_realization().simple_coroots() n = self.cartan_type().classical().rank() + index_set = self.index_set() for k in range(1,self.cols+1): - for j in self.index_set(): + for j in index_set: if self.a(j,k) - self.a( (j-1) % (n+1) ,k) <= 0: continue else: p_not_found = True - for p in self.index_set(): + for p in index_set: if (j+k) % (n+1) == (p+1) % (n+1) and self.a(j,k) - self.a( (j-1) % (n+1) ,k) <= La.scalar(ac[p]): p_not_found = False continue @@ -987,6 +987,6 @@ def __iter__(self): sage: next(x) [0] """ - for c in self.subcrystal(direction='lower'): - if c.in_highest_weight_crystal(self.hw) : + for c in super(CrystalOfGeneralizedYoungWalls, self).__iter__(): + if c.in_highest_weight_crystal(self.hw): yield c diff --git a/src/sage/combinat/crystals/induced_structure.py b/src/sage/combinat/crystals/induced_structure.py new file mode 100644 index 00000000000..d7770927072 --- /dev/null +++ b/src/sage/combinat/crystals/induced_structure.py @@ -0,0 +1,689 @@ +r""" +Induced Crystals + +We construct a crystal structure on a set induced by a bijection `\Phi`. + +AUTHORS: + +- Travis Scrimshaw (2014-05-15): Initial implementation +""" + +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# 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.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.structure.element_wrapper import ElementWrapper + +class InducedCrystal(Parent, UniqueRepresentation): + r""" + A crystal induced from an injection. + + Let `X` be a set and let `C` be crystal and consider any injection + `\Phi : X \to C`. We induce a crystal structure on `X` by considering + `\Phi` to be a crystal morphism. + + Alternatively we can induce a crystal structure on some (sub)set of `X` + by considering an injection `\Phi : C \to X` considered as a crystal + morphism. This form is also useful when the set `X` is not explicitly + known. + + INPUT: + + - ``X`` -- the base set + - ``phi`` -- the map `\Phi` + - ``inverse`` -- (optional) the inverse map `\Phi^{-1}` + - ``from_crystal`` -- (default: ``False``) if the induced structure is + of the second type `\Phi : C \to X` + + EXAMPLES: + + We construct a crystal structure of Gelfand-Tsetlin patterns by going + through their bijection with semistandard tableaux:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: I.digraph().is_isomorphic(D.digraph(), edge_labels=True) + True + + Now we construct the above example but inducing the structure going the + other way (from tableaux to Gelfand-Tsetlin patterns). This can also + give us more information coming from the crystal. :: + + sage: D2 = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G2 = GelfandTsetlinPatterns(4, 1) + sage: phi2 = lambda x: D2(x.to_tableau()) + sage: phi2_inv = lambda x: G2(x.to_tableau()) + sage: I2 = crystals.Induced(D2, phi2_inv, phi2, from_crystal=True) + sage: I2.module_generators + ([[0, 0, 0, 0], [0, 0, 0], [0, 0], [0]], + [[1, 0, 0, 0], [1, 0, 0], [1, 0], [1]], + [[1, 1, 0, 0], [1, 1, 0], [1, 1], [1]], + [[1, 1, 1, 0], [1, 1, 1], [1, 1], [1]], + [[1, 1, 1, 1], [1, 1, 1], [1, 1], [1]]) + + We check an example when the codomain is larger than the domain + (although here the crystal structure is trivial):: + + sage: P = Permutations(4) + sage: D = crystals.Tableaux(['A',3], shapes=Partitions(4)) + sage: T = crystals.TensorProduct(D, D) + sage: phi = lambda p: T(D(RSK(p)[0]), D(RSK(p)[1])) + sage: phi_inv = lambda d: RSK_inverse(d[0].to_tableau(), d[1].to_tableau(), output='permutation') + sage: all(phi_inv(phi(p)) == p for p in P) # Check it really is the inverse + True + sage: I = crystals.Induced(P, phi, phi_inv) + sage: I.digraph() + Multi-digraph on 24 vertices + + We construct an example without a specified inverse map:: + + sage: X = Words(2,4) + sage: L = crystals.Letters(['A',1]) + sage: T = crystals.TensorProduct(*[L]*4) + sage: Phi = lambda x : T(*[L(i) for i in x]) + sage: I = crystals.Induced(X, Phi) + sage: I.digraph() + Digraph on 16 vertices + """ + @staticmethod + def __classcall_private__(cls, X, phi, inverse=None, from_crystal=False): + """ + Normalize input to ensure a unique representation. + + 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 + """ + if from_crystal: + return InducedFromCrystal(X, phi, inverse) + + return super(InducedCrystal, cls).__classcall__(cls, X, phi, inverse) + + def __init__(self, X, phi, inverse): + """ + Initialize ``self``. + + TESTS: + + Note that pickling only works when the input functions + can be pickled:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: import __main__ + sage: __main__.phi = phi + sage: __main__.phi_inv = phi_inv + sage: I = crystals.Induced(G, phi, phi_inv) + sage: TestSuite(I).run() + """ + try: + codomain = phi.codomain() + except AttributeError: + codomain = phi(X.an_element()).parent() + + self._set = X + self._phi = phi + + if inverse is None: + try: + inverse = ~self._phi + except (TypeError, ValueError): + try: + inverse = self._phi.section() + except AttributeError: + if X.cardinality() == float('inf'): + raise ValueError("the inverse map must be defined for infinite sets") + self._preimage = {} + for x in X: + y = phi(x) + if y in self._preimage: + raise ValueError("the map is not injective") + self._preimage[y] = x + inverse = self._preimage.__getitem__ + self._inverse = inverse + + self._cartan_type = codomain.cartan_type() + Parent.__init__(self, category=codomain.category()) + + self.module_generators = self + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: crystals.Induced(G, phi, phi_inv) + Crystal of Gelfand-Tsetlin patterns of width 4 and max value 1 + induced by + """ + return "Crystal of {} induced by {}".format(self._set, self._phi) + + def _element_constructor_(self, x): + """ + Construct an element of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: I = crystals.Induced(G, phi, phi_inv) + sage: I([[1,1,0,0],[1,0,0],[1,0],[1]]) + [[1, 1, 0, 0], [1, 0, 0], [1, 0], [1]] + sage: I(D(3,2,1)) + [[1, 1, 1, 0], [1, 1, 1], [1, 1], [1]] + sage: I([[1,1,0,0],[1,0,0],[0,1],[1]]) + Traceback (most recent call last): + ... + ValueError: unable to convert [[1, 1, 0, 0], [1, 0, 0], [0, 1], [1]] + """ + if x in self._set: + return self.element_class(self, self._set(x)) + + try: + return self.element_class(self, self._inverse(x)) + except (TypeError, ValueError, AttributeError): + raise ValueError("unable to convert {}".format(x)) + + def __contains__(self, x): + """ + Check if ``x`` is in ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: I = crystals.Induced(G, phi, phi_inv) + sage: all(g in I for g in G) + True + sage: [[1,1,0,0],[1,0,0],[1,0],[1]] in I + True + sage: [[1,1,0,0],[1,0,0],[0,1],[1]] in I + False + """ + if isinstance(x, InducedCrystal.Element): + return x.parent() == self + + return x in self._set + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: I = crystals.Induced(G, phi, phi_inv) + sage: sorted([x for x in I]) + [[[0, 0, 0, 0], [0, 0, 0], [0, 0], [0]], + [[1, 0, 0, 0], [0, 0, 0], [0, 0], [0]], + [[1, 0, 0, 0], [1, 0, 0], [0, 0], [0]], + [[1, 0, 0, 0], [1, 0, 0], [1, 0], [0]], + [[1, 0, 0, 0], [1, 0, 0], [1, 0], [1]], + [[1, 1, 0, 0], [1, 0, 0], [0, 0], [0]], + [[1, 1, 0, 0], [1, 0, 0], [1, 0], [0]], + [[1, 1, 0, 0], [1, 0, 0], [1, 0], [1]], + [[1, 1, 0, 0], [1, 1, 0], [1, 0], [0]], + [[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]], + [[1, 1, 0, 0], [1, 1, 0], [1, 1], [1]], + [[1, 1, 1, 0], [1, 1, 0], [1, 0], [0]], + [[1, 1, 1, 0], [1, 1, 0], [1, 0], [1]], + [[1, 1, 1, 0], [1, 1, 0], [1, 1], [1]], + [[1, 1, 1, 0], [1, 1, 1], [1, 1], [1]], + [[1, 1, 1, 1], [1, 1, 1], [1, 1], [1]]] + """ + for x in self._set: + yield self.element_class(self, x) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: P = Permutations(4) + sage: D = crystals.Tableaux(['A',3], shapes=Partitions(4)) + sage: T = crystals.TensorProduct(D, D) + sage: phi = lambda p: T(D(RSK(p)[0]), D(RSK(p)[1])) + sage: phi_inv = lambda d: RSK_inverse(d[0].to_tableau(), d[1].to_tableau(), output='permutation') + sage: I = crystals.Induced(P, phi, phi_inv) + sage: I.cardinality() == factorial(4) + True + """ + return self._set.cardinality() + + class Element(ElementWrapper): + """ + An element of an induced crystal. + """ + def e(self, i): + """ + Return `e_i` of ``self``. + + EXAMPLES:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.e(1) + sage: elt.e(2) + [[1, 1, 0, 0], [1, 1, 0], [1, 1], [1]] + sage: elt.e(3) + """ + P = self.parent() + ret = P._phi(self.value).e(i) + if ret is None: + return None + try: + return self.__class__(P, P._inverse(ret)) + except (ValueError, TypeError, AttributeError): + return None + + def f(self, i): + """ + Return `f_i` of ``self``. + + EXAMPLES:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.f(1) + [[1, 1, 0, 0], [1, 1, 0], [1, 0], [0]] + sage: elt.f(2) + sage: elt.f(3) + [[1, 1, 0, 0], [1, 0, 0], [1, 0], [1]] + """ + P = self.parent() + ret = P._phi(self.value).f(i) + if ret is None: + return None + try: + return self.__class__(P, P._inverse(ret)) + except (ValueError, TypeError, AttributeError): + return None + + def epsilon(self, i): + r""" + Return `\varepsilon_i` of ``self``. + + EXAMPLES:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: [elt.epsilon(i) for i in I.index_set()] + [0, 1, 0] + """ + return self.parent()._phi(self.value).epsilon(i) + + def phi(self, i): + r""" + Return `\varphi_i` of ``self``. + + EXAMPLES:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: [elt.phi(i) for i in I.index_set()] + [1, 0, 1] + """ + return self.parent()._phi(self.value).phi(i) + + def weight(self): + """ + Return the weight of ``self``. + + EXAMPLES:: + + 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: I = crystals.Induced(G, phi, phi_inv) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.weight() + (1, 0, 1, 0) + """ + return self.parent()._phi(self.value).weight() + +class InducedFromCrystal(Parent, UniqueRepresentation): + r""" + A crystal induced from an injection. + + Alternatively we can induce a crystal structure on some (sub)set of `X` + by considering an injection `\Phi : C \to X` considered as a crystal + morphism. + + .. SEEALSO:: + + :class:`InducedCrystal` + + INPUT: + + - ``X`` -- the base set + - ``phi`` -- the map `\Phi` + - ``inverse`` -- (optional) the inverse map `\Phi^{-1}` + + EXAMPLES: + + We construct a crystal structure on generalized permutations with a + fixed first row by using RSK:: + + sage: C = crystals.Tableaux(['A',3], shape=[2,1]) + sage: def psi(x): + ....: ret = RSK_inverse(x.to_tableau(), Tableau([[1,1],[2]])) + ....: return (tuple(ret[0]), tuple(ret[1])) + sage: psi_inv = lambda x: C(RSK(*x)[0]) + sage: I = crystals.Induced(C, psi, psi_inv, from_crystal=True) + """ + def __init__(self, X, phi, inverse): + """ + Initialize ``self``. + + TESTS: + + Note that pickling only works when the input functions + can be pickled:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: import __main__ + sage: __main__.phi = phi + sage: __main__.phi_inv = phi_inv + sage: I = crystals.Induced(D, phi_inv, phi, from_crystal=True) + sage: TestSuite(I).run() + """ + self._crystal = X + self._phi = phi + + if inverse is None: + try: + inverse = ~self._phi + except (TypeError, ValueError): + try: + inverse = self._phi.section() + except AttributeError: + if X.cardinality() == float('inf'): + raise ValueError("the inverse map must be defined for infinite sets") + self._preimage = {} + for x in X: + y = phi(x) + if y in self._preimage: + raise ValueError("the map is not injective") + self._preimage[y] = x + inverse = self._preimage.__getitem__ + self._inverse = inverse + + self._cartan_type = X.cartan_type() + Parent.__init__(self, category=X.category()) + self.module_generators = tuple(self.element_class(self, phi(mg)) + for mg in X.module_generators) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return D(x.to_tableau()) + sage: def phi_inv(x): return G(x.to_tableau()) + sage: crystals.Induced(D, phi_inv, phi, from_crystal=True) + Crystal induced by from + The crystal of tableaux of type ['A', 3] and shape(s) + [[], [1], [1, 1], [1, 1, 1], [1, 1, 1, 1]] + """ + return "Crystal induced by {} from {}".format(self._phi, self._crystal) + + def _element_constructor_(self, x): + """ + Construct an element of ``self``. + + EXAMPLES:: + + sage: C = crystals.Tableaux(['A',3], shape=[2,1]) + sage: def psi(x): + ....: ret = RSK_inverse(x.to_tableau(), Tableau([[1,1],[2]])) + ....: return (tuple(ret[0]), tuple(ret[1])) + sage: psi_inv = lambda x: C(RSK(*x)[0]) + sage: I = crystals.Induced(C, psi, psi_inv, from_crystal=True) + sage: I([[1, 1, 2], [2, 2, 1]]) + ((1, 1, 2), (2, 2, 1)) + sage: I(C(2,1,3)) + ((1, 1, 2), (2, 3, 1)) + """ + if x in self._crystal: + return self.element_class(self, self._phi(self._crystal(x))) + + try: + return self.element_class(self, self._phi(self._inverse(x))) + except (TypeError, ValueError, AttributeError): + raise ValueError("unable to convert {}".format(x)) + + def __contains__(self, x): + """ + Check if ``x`` is in ``self``. + + EXAMPLES:: + + sage: C = crystals.Tableaux(['A',3], shape=[2,1]) + sage: def psi(x): + ....: ret = RSK_inverse(x.to_tableau(), Tableau([[1,1],[2]])) + ....: return (tuple(ret[0]), tuple(ret[1])) + sage: psi_inv = lambda x: C(RSK(*x)[0]) + sage: I = crystals.Induced(C, psi, psi_inv, from_crystal=True) + sage: ((1, 1, 2), (2, 2, 1)) in I + True + sage: ((1, 2, 2), (1, 1, 2)) in I + False + sage: ((1, 2, 3), (1, 2, 3)) in I + False + sage: ((1, 2, 2), (1, 3, 2)) in I + False + """ + if isinstance(x, InducedFromCrystal.Element): + return x.parent() == self + + try: + y = self._inverse(x) + return y in self._crystal and self._phi(y) == x + except (ValueError, TypeError): + return False + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = crystals.Tableaux(['A',2], shape=[2,1]) + sage: def psi(x): + ....: ret = RSK_inverse(x.to_tableau(), Tableau([[1,1],[2]])) + ....: return (tuple(ret[0]), tuple(ret[1])) + sage: psi_inv = lambda x: C(RSK(*x)[0]) + sage: I = crystals.Induced(C, psi, psi_inv, from_crystal=True) + sage: sorted(x for x in I) + [((1, 1, 2), (1, 2, 1)), + ((1, 1, 2), (2, 2, 1)), + ((1, 1, 2), (2, 3, 1)), + ((1, 1, 2), (3, 3, 1)), + ((1, 1, 2), (3, 3, 2)), + ((1, 1, 2), (1, 3, 1)), + ((1, 1, 2), (1, 3, 2)), + ((1, 1, 2), (2, 3, 2))] + """ + for x in self._crystal: + yield self.element_class(self, self._phi(x)) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: C = crystals.Tableaux(['A',3], shape=[2,1]) + sage: def psi(x): + ....: ret = RSK_inverse(x.to_tableau(), Tableau([[1,1],[2]])) + ....: return (tuple(ret[0]), tuple(ret[1])) + sage: psi_inv = lambda x: C(RSK(*x)[0]) + sage: I = crystals.Induced(C, psi, psi_inv, from_crystal=True) + sage: I.cardinality() == C.cardinality() + True + """ + return self._crystal.cardinality() + + class Element(ElementWrapper): + """ + An element of an induced crystal. + """ + def e(self, i): + """ + Return `e_i` of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return G(x.to_tableau()) + sage: def phi_inv(x): return D(G(x).to_tableau()) + sage: I = crystals.Induced(D, phi, phi_inv, from_crystal=True) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.e(1) + sage: elt.e(2) + [[1, 1, 0, 0], [1, 1, 0], [1, 1], [1]] + sage: elt.e(3) + """ + P = self.parent() + ret = P._inverse(self.value).e(i) + if ret is None: + return None + return self.__class__(P, P._phi(ret)) + + def f(self, i): + """ + Return `f_i` of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return G(x.to_tableau()) + sage: def phi_inv(x): return D(G(x).to_tableau()) + sage: I = crystals.Induced(D, phi, phi_inv, from_crystal=True) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.f(1) + [[1, 1, 0, 0], [1, 1, 0], [1, 0], [0]] + sage: elt.f(2) + sage: elt.f(3) + [[1, 1, 0, 0], [1, 0, 0], [1, 0], [1]] + """ + P = self.parent() + ret = P._inverse(self.value).f(i) + if ret is None: + return None + return self.__class__(P, P._phi(ret)) + + def epsilon(self, i): + r""" + Return `\varepsilon_i` of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return G(x.to_tableau()) + sage: def phi_inv(x): return D(G(x).to_tableau()) + sage: I = crystals.Induced(D, phi, phi_inv, from_crystal=True) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: [elt.epsilon(i) for i in I.index_set()] + [0, 1, 0] + """ + return self.parent()._inverse(self.value).epsilon(i) + + def phi(self, i): + r""" + Return `\varphi_i` of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return G(x.to_tableau()) + sage: def phi_inv(x): return D(G(x).to_tableau()) + sage: I = crystals.Induced(D, phi, phi_inv, from_crystal=True) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: [elt.epsilon(i) for i in I.index_set()] + [0, 1, 0] + """ + return self.parent()._inverse(self.value).phi(i) + + def weight(self): + """ + Return the weight of ``self``. + + EXAMPLES:: + + sage: D = crystals.Tableaux(['A',3], shapes=PartitionsInBox(4,1)) + sage: G = GelfandTsetlinPatterns(4, 1) + sage: def phi(x): return G(x.to_tableau()) + sage: def phi_inv(x): return D(G(x).to_tableau()) + sage: I = crystals.Induced(D, phi, phi_inv, from_crystal=True) + sage: elt = I([[1, 1, 0, 0], [1, 1, 0], [1, 0], [1]]) + sage: elt.weight() + (1, 0, 1, 0) + """ + return self.parent()._inverse(self.value).weight() + diff --git a/src/sage/combinat/crystals/infinity_crystals.py b/src/sage/combinat/crystals/infinity_crystals.py index bc7d0b5fb38..ab9d9ac316b 100644 --- a/src/sage/combinat/crystals/infinity_crystals.py +++ b/src/sage/combinat/crystals/infinity_crystals.py @@ -31,7 +31,6 @@ from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.highest_weight_crystals import HighestWeightCrystals from sage.categories.homset import Hom -from sage.categories.morphism import Morphism from sage.misc.cachefunc import cached_method from sage.misc.flatten import flatten @@ -40,6 +39,7 @@ from sage.combinat.crystals.letters import CrystalOfLetters from sage.combinat.crystals.tensor_product import CrystalOfWords, CrystalOfTableauxElement + class InfinityCrystalOfTableaux(CrystalOfWords): r""" `\mathcal{B}(\infty)` crystal of tableaux. @@ -97,7 +97,7 @@ class InfinityCrystalOfTableaux(CrystalOfWords): `\mathcal{T}(\infty)` is the set of marginally large semistandard tableaux with exactly `n-1` rows over the alphabet `\{1 \prec \cdots \prec n, \overline{n} \prec \cdots \prec \overline{1} \}` and subject - to the following constaints: + to the following constraints: - for each `1 \le i \le n`, the contents of the boxes in the `i`-th row are `\preceq \overline{i}`, @@ -165,8 +165,11 @@ class InfinityCrystalOfTableaux(CrystalOfWords): We check that a few classical crystals embed into `\mathcal{T}(\infty)`:: sage: def crystal_test(B, C): - ....: g = {C.module_generators[0] : B.module_generators[0]} - ....: f = C.crystal_morphism(g) + ....: T = crystals.elementary.T(C.cartan_type(), C.module_generators[0].weight()) + ....: TP = crystals.TensorProduct(T, B) + ....: mg = TP(T[0], B.module_generators[0]) + ....: g = {C.module_generators[0]: mg} + ....: f = C.crystal_morphism(g, category=HighestWeightCrystals()) ....: G = B.digraph(subset=[f(x) for x in C]) ....: return G.is_isomorphic(C.digraph(), edge_labels=True) sage: B = crystals.infinity.Tableaux(['A',2]) @@ -225,7 +228,8 @@ def __init__(self, cartan_type): sage: B = crystals.infinity.Tableaux(['A',2]) sage: TestSuite(B).run() # long time """ - Parent.__init__( self, category=(HighestWeightCrystals(), InfiniteEnumeratedSets()) ) + Parent.__init__(self, category=(HighestWeightCrystals(), + InfiniteEnumeratedSets())) self._cartan_type = cartan_type self.letters = CrystalOfLetters(cartan_type) self.module_generators = (self.module_generator(),) @@ -239,7 +243,7 @@ def _repr_(self): sage: B = crystals.infinity.Tableaux(['A',4]); B The infinity crystal of tableaux of type ['A', 4] """ - return "The infinity crystal of tableaux of type %s"%self._cartan_type + return "The infinity crystal of tableaux of type %s" % self._cartan_type @cached_method def module_generator(self): @@ -490,7 +494,8 @@ def weight(self): cur_col_len = 1 else: cur_col_len += 1 - shape_wt += La[1] # Since we miss the last column (which is always height 1) + shape_wt += La[1] + # Since we miss the last column (which is always height 1) return CrystalOfTableauxElement.weight(self) - shape_wt def reduced_form(self): @@ -644,7 +649,7 @@ def content(self): ct = self.parent().cartan_type().type() for i,row in enumerate(tab): for entry in row: - if entry == -i-1 and ct in ('B','D','G'): + if entry == -i-1 and ct in ('B', 'D', 'G'): count += 2 elif entry != i+1: count += 1 @@ -697,6 +702,7 @@ def string_parameters(self,word): ret.append(a) return ret + class InfinityCrystalOfTableauxTypeD(InfinityCrystalOfTableaux): r""" `\mathcal{B}(\infty)` crystal of tableaux for type `D_n`. @@ -836,4 +842,3 @@ def f(self, i): for j in range(i-1): ret._list.insert(0,self.parent().letters(j+1)) return ret - diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index 77171972e35..474a8a82f2e 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -28,8 +28,11 @@ from sage.functions.other import floor, ceil from sage.combinat.combinat import CombinatorialObject from sage.structure.parent import Parent +from sage.categories.crystals import CrystalMorphism from sage.categories.regular_crystals import RegularCrystals from sage.categories.finite_crystals import FiniteCrystals +from sage.categories.homset import Hom +from sage.categories.map import Map from sage.rings.integer import Integer from sage.rings.all import QQ from sage.combinat.crystals.affine import AffineCrystalFromClassical, \ @@ -59,7 +62,7 @@ def KirillovReshetikhinCrystalFromLSPaths(cartan_type, r, s=1): EXAMPLES:: - sage: K = crystals.kirillov_reshetikhin.LSPaths(['A',2,1],2) + sage: K = crystals.kirillov_reshetikhin.LSPaths(['A',2,1],2) # indirect doctest sage: KR = crystals.KirillovReshetikhin(['A',2,1],2,1) sage: G = K.digraph() sage: GR = KR.digraph() @@ -97,7 +100,6 @@ def KirillovReshetikhinCrystalFromLSPaths(cartan_type, r, s=1): sage: G.is_isomorphic(GR, edge_labels = True) True - TESTS:: sage: K = crystals.kirillov_reshetikhin.LSPaths(['G',2,1],2) @@ -631,7 +633,7 @@ def R_matrix(self, K): INPUT: - ``self`` -- a crystal `L` - - ``K`` -- a Kirillov-Reshetikhin crystal of the same type as `L`. + - ``K`` -- a Kirillov-Reshetikhin crystal of the same type as `L` Returns the *combinatorial `R`-matrix* from `L \otimes K \to K \otimes L`, where the combinatorial `R`-matrix is the affine @@ -691,7 +693,7 @@ def R_matrix(self, K): gen1 = T1( self.module_generator(), K.module_generator() ) gen2 = T2( K.module_generator(), self.module_generator() ) g = { gen1 : gen2 } - return T1.crystal_morphism(g, acyclic = False) + return T1.crystal_morphism(g, check=False) @cached_method def kirillov_reshetikhin_tableaux(self): @@ -895,10 +897,12 @@ def classical_decomposition(self): @cached_method def promotion(self): - """ - Specifies the promotion operator used to construct the affine type A crystal. - For type A this corresponds to the Dynkin diagram automorphism which maps i to i+1 mod n+1, - where n is the rank. + r""" + Specifies the promotion operator used to construct the affine + type `A` crystal. + + For type `A` this corresponds to the Dynkin diagram automorphism + which `i \mapsto i+1 \mod n+1`, where `n` is the rank. EXAMPLES:: @@ -907,14 +911,19 @@ def promotion(self): sage: K.promotion()(b) [[1, 3], [2, 4]] """ - return lambda x : self.classical_crystal(x.to_tableau().promotion(self._cartan_type[1])) + T = self.classical_crystal + return CrystalDiagramAutomorphism(T, + lambda x: T(x.to_tableau().promotion(self._cartan_type[1])), + cache=False) @cached_method def promotion_inverse(self): - """ - Specifies the inverse promotion operator used to construct the affine type A crystal. - For type A this corresponds to the Dynkin diagram automorphism which maps i to i-1 mod n+1, - where n is the rank. + r""" + Specifies the inverse promotion operator used to construct the + affine type `A` crystal. + + For type `A` this corresponds to the Dynkin diagram automorphism + which `i \mapsto i-1 \mod n+1`, where `n` is the rank. EXAMPLES:: @@ -926,14 +935,19 @@ def promotion_inverse(self): sage: K.promotion_inverse()(K.promotion()(b)) [[1, 2], [3, 3]] """ - return lambda x : self.classical_crystal(x.to_tableau().promotion_inverse(self._cartan_type[1])) + T = self.classical_crystal + return CrystalDiagramAutomorphism(T, + lambda x: T(x.to_tableau().promotion_inverse(self._cartan_type[1])), + cache=False) def dynkin_diagram_automorphism(self, i): - """ - Specifies the Dynkin diagram automorphism underlying the promotion action on the crystal - elements. The automorphism needs to map node 0 to some other Dynkin node. + r""" + Specifies the Dynkin diagram automorphism underlying the promotion + action on the crystal elements. The automorphism needs to map node + 0 to some other Dynkin node. - For type A we use the Dynkin diagram automorphism which maps i to i+1 mod n+1, where n is the rank. + For type `A` we use the Dynkin diagram automorphism which + `i \mapsto i+1 \mod n+1`, where `n` is the rank. EXAMPLES:: @@ -999,10 +1013,12 @@ def classical_decomposition(self): @cached_method def promotion(self): """ - Specifies the promotion operator used to construct the affine type `D_n^{(1)}` etc. crystal. - This corresponds to the Dynkin diagram automorphism which interchanges nodes 0 and 1, - and leaves all other nodes unchanged. On the level of crystals it is constructed using - `\pm` diagrams. + Specifies the promotion operator used to construct the affine + type `D_n^{(1)}` etc. crystal. + + This corresponds to the Dynkin diagram automorphism which + interchanges nodes 0 and 1, and leaves all other nodes unchanged. + On the level of crystals it is constructed using `\pm` diagrams. EXAMPLES:: @@ -1021,13 +1037,14 @@ def promotion(self): T = self.classical_decomposition() ind = list(T.index_set()) ind.remove(1) - return T.crystal_morphism( self.promotion_on_highest_weight_vectors(), index_set = ind) + return CrystalDiagramAutomorphism(T, self.promotion_on_highest_weight_vectors(), ind) def promotion_inverse(self): """ Return inverse of promotion. - In this case promotion is an involution, so promotion inverse equals promotion. + In this case promotion is an involution, so promotion + inverse equals promotion. EXAMPLES:: @@ -1041,11 +1058,12 @@ def promotion_inverse(self): def dynkin_diagram_automorphism(self, i): """ - Specifies the Dynkin diagram automorphism underlying the promotion action on the crystal - elements. The automorphism needs to map node 0 to some other Dynkin node. + Specifies the Dynkin diagram automorphism underlying the promotion + action on the crystal elements. The automorphism needs to map + node 0 to some other Dynkin node. - Here we use the Dynkin diagram automorphism which interchanges nodes 0 and 1 and leaves - all other nodes unchanged. + Here we use the Dynkin diagram automorphism which interchanges + nodes 0 and 1 and leaves all other nodes unchanged. EXAMPLES:: @@ -1080,8 +1098,9 @@ def promotion_on_highest_weight_vectors(self): def from_highest_weight_vector_to_pm_diagram(self, b): """ - This gives the bijection between an element b in the classical decomposition - of the KR crystal that is `{2,3,..,n}`-highest weight and `\pm` diagrams. + This gives the bijection between an element ``b`` in the classical + decomposition of the KR crystal that is `{2, 3, \ldots, n}`-highest + weight and `\pm` diagrams. EXAMPLES:: @@ -1110,8 +1129,9 @@ def from_highest_weight_vector_to_pm_diagram(self, b): def from_pm_diagram_to_highest_weight_vector(self, pm): """ - This gives the bijection between a `\pm` diagram and an element b in the classical - decomposition of the KR crystal that is {2,3,..,n}-highest weight. + This gives the bijection between a `\pm` diagram and an element + ``b`` in the classical decomposition of the KR crystal that + is `{2, 3, \ldots, n}`-highest weight. EXAMPLES:: @@ -1189,16 +1209,21 @@ class KR_type_E6(KirillovReshetikhinCrystalFromPromotion): def classical_decomposition(self): """ - Specifies the classical crystal underlying the KR crystal of type `E_6^{(1)}`. + Specifies the classical crystal underlying the KR crystal + of type `E_6^{(1)}`. EXAMPLES:: sage: K = crystals.KirillovReshetikhin(['E',6,1], 2,2) sage: K.classical_decomposition() - Direct sum of the crystals Family (Finite dimensional highest weight crystal of type ['E', 6] and highest weight 0, Finite dimensional highest weight crystal of type ['E', 6] and highest weight Lambda[2], Finite dimensional highest weight crystal of type ['E', 6] and highest weight 2*Lambda[2]) + Direct sum of the crystals Family + (Finite dimensional highest weight crystal of type ['E', 6] and highest weight 0, + Finite dimensional highest weight crystal of type ['E', 6] and highest weight Lambda[2], + Finite dimensional highest weight crystal of type ['E', 6] and highest weight 2*Lambda[2]) sage: K = crystals.KirillovReshetikhin(['E',6,1], 1,2) sage: K.classical_decomposition() - Direct sum of the crystals Family (Finite dimensional highest weight crystal of type ['E', 6] and highest weight 2*Lambda[1],) + Direct sum of the crystals Family + (Finite dimensional highest weight crystal of type ['E', 6] and highest weight 2*Lambda[1],) """ La = self.cartan_type().classical().root_system().weight_lattice().fundamental_weights() if self.r() in [1,6]: @@ -1296,8 +1321,8 @@ def highest_weight_dict(self): @cached_method def highest_weight_dict_inv(self): r""" - Returns a dictionary between a tuple of affine weights and a classical component, and - `{2,3,4,5,6}` highest weight elements. + Return a dictionary between a tuple of affine weights and a classical + component, and `{2,3,4,5,6}` highest weight elements. EXAMPLES:: @@ -1316,7 +1341,8 @@ def highest_weight_dict_inv(self): def automorphism_on_affine_weight(self, weight): r""" - Acts with the Dynkin diagram automorphism on affine weights as outputted by the affine_weight method. + Acts with the Dynkin diagram automorphism on affine weights + as outputted by the ``affine_weight`` method. EXAMPLES:: @@ -1334,8 +1360,8 @@ def automorphism_on_affine_weight(self, weight): @cached_method def promotion_on_highest_weight_vectors(self): r""" - Gives a dictionary of the promotion map on `{1,2,3,4,5}` highest weight elements to - `{2,3,4,5,6}` elements in self. + Gives a dictionary of the promotion map on `{1,2,3,4,5}` highest + weight elements to `{2,3,4,5,6}` elements in ``self``. EXAMPLES:: @@ -1354,7 +1380,8 @@ def promotion_on_highest_weight_vectors(self): for (weight, i) in dic.values(): dic_weight[weight] = dic_weight.get(weight, []) + [i] map_index = lambda i_list: max(i_list[1]) + min(i_list[1]) - i_list[0] - map_element = lambda x : tuple([self.automorphism_on_affine_weight(dic[x][0]), map_index((dic[x][1],dic_weight[dic[x][0]]))]) + map_element = lambda x : tuple([ self.automorphism_on_affine_weight(dic[x][0]), + map_index((dic[x][1], dic_weight[dic[x][0]])) ]) return dict( (x, dic_inv[map_element(x)]) for x in dic.keys() ) @cached_method @@ -1374,8 +1401,9 @@ def promotion_on_highest_weight_vectors_function(self): @cached_method def promotion(self): - """ - Specifies the promotion operator used to construct the affine type `E_6^{(1)}` crystal. + r""" + Specifies the promotion operator used to construct the + affine type `E_6^{(1)}` crystal. EXAMPLES:: @@ -1390,13 +1418,14 @@ def promotion(self): """ T = self.classical_decomposition() ind = [1,2,3,4,5] - return T.crystal_morphism( self.promotion_on_highest_weight_vectors_function(), automorphism = lambda i : self.dynkin_diagram_automorphism(i), index_set = ind) + return CrystalDiagramAutomorphism(T, self.promotion_on_highest_weight_vectors(), ind, + automorphism=self.dynkin_diagram_automorphism) @cached_method def promotion_inverse(self): r""" - Returns the inverse promotion. Since promotion is of order 3, the inverse promotion is the same - as promotion applied twice. + Return the inverse promotion. Since promotion is of order 3, + the inverse promotion is the same as promotion applied twice. EXAMPLES:: @@ -1407,8 +1436,8 @@ def promotion_inverse(self): True """ p = self.promotion() - return lambda x : p(p(x)) - + #return lambda x : p(p(x)) + return p * p class KR_type_C(KirillovReshetikhinGenericCrystal): r""" @@ -1538,8 +1567,10 @@ def to_ambient_crystal(self): """ keys = self.highest_weight_dict().keys() pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict, index_set = self.cartan_type().classical().index_set(), - automorphism = lambda i : i+1 ) + classical = self.cartan_type().classical() + return self.crystal_morphism( pdict, index_set=classical.index_set(), + automorphism=lambda i: i+1, + cartan_type=classical, check=False ) @cached_method def from_ambient_crystal(self): @@ -1553,14 +1584,16 @@ def from_ambient_crystal(self): EXAMPLES:: sage: K = crystals.KirillovReshetikhin(['C',3,1], 2,2) - sage: b=K.ambient_crystal()(rows=[[2,2],[3,3]]) + sage: b = K.ambient_crystal()(rows=[[2,2],[3,3]]) sage: K.from_ambient_crystal()(b) [[1, 1], [2, 2]] """ keys = self.highest_weight_dict().keys() - pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict_inv, index_set = [j+1 for j in self.cartan_type().classical().index_set()], - automorphism = lambda i : i-1 ) + pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) + for key in keys ) + ind = [j+1 for j in self.cartan_type().classical().index_set()] + return AmbientRetractMap( self, self.ambient_crystal(), pdict_inv, index_set=ind, + automorphism=lambda i : i-1 ) class KR_type_CElement(KirillovReshetikhinGenericCrystalElement): r""" @@ -1795,8 +1828,10 @@ def to_ambient_crystal(self): """ keys = self.highest_weight_dict().keys() pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict, index_set = self.cartan_type().classical().index_set(), - automorphism = lambda i : i+1 ) + classical = self.cartan_type().classical() + return self.crystal_morphism( pdict, index_set=classical.index_set(), + automorphism=lambda i: i+1, + cartan_type=classical, check=False ) @cached_method def from_ambient_crystal(self): @@ -1816,9 +1851,11 @@ def from_ambient_crystal(self): [[1, 1]] """ keys = self.highest_weight_dict().keys() - pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict_inv, index_set = [j+1 for j in self.cartan_type().classical().index_set()], - automorphism = lambda i : i-1 ) + pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) + for key in keys ) + ind = [j+1 for j in self.cartan_type().classical().index_set()] + return AmbientRetractMap( self, self.ambient_crystal(), pdict_inv, index_set=ind, + automorphism=lambda i : i-1 ) class KR_type_A2Element(KirillovReshetikhinGenericCrystalElement): r""" @@ -2047,9 +2084,13 @@ def to_ambient_crystal(self): [[], [[1, 1]], [[2, 2]], [[-2, -2]], [[-1, -1]]] """ keys = self.highest_weight_dict().keys() - pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict, index_set = self.cartan_type().classical().index_set(), - similarity_factor = self.similarity_factor() ) + pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) + for key in keys ) + classical = self.cartan_type().classical() + return self.crystal_morphism( pdict, codomain=self.ambient_crystal(), + index_set=classical.index_set(), + scaling_factors=self.similarity_factor(), + cartan_type=classical, check=False ) @cached_method def from_ambient_crystal(self): @@ -2073,8 +2114,9 @@ def from_ambient_crystal(self): """ keys = self.highest_weight_dict().keys() pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict_inv, index_set = self.cartan_type().classical().index_set(), - similarity_factor_domain = self.similarity_factor() ) + return AmbientRetractMap( self, self.ambient_crystal(), pdict_inv, + index_set=self.cartan_type().classical().index_set(), + similarity_factor_domain=self.similarity_factor() ) class KR_type_boxElement(KirillovReshetikhinGenericCrystalElement): @@ -2315,9 +2357,13 @@ def to_ambient_crystal(self): [[2], [-3], [-1]], [[3], [-2], [-1]], [[-3], [-2], [-1]]] """ keys = self.highest_weight_dict().keys() - pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict, index_set = self.cartan_type().classical().index_set(), - similarity_factor = self.similarity_factor() ) + pdict = dict( (self.highest_weight_dict()[key], self.ambient_highest_weight_dict()[key]) + for key in keys ) + classical = self.cartan_type().classical() + return self.crystal_morphism( pdict, codomain=self.ambient_crystal(), + index_set=classical.index_set(), + scaling_factors=self.similarity_factor(), + cartan_type=classical, check=False ) @cached_method def from_ambient_crystal(self): @@ -2339,8 +2385,9 @@ def from_ambient_crystal(self): """ keys = self.highest_weight_dict().keys() pdict_inv = dict( (self.ambient_highest_weight_dict()[key], self.highest_weight_dict()[key]) for key in keys ) - return self.crystal_morphism( pdict_inv, index_set = self.cartan_type().classical().index_set(), - similarity_factor_domain = self.similarity_factor() ) + return AmbientRetractMap( self, self.ambient_crystal(), pdict_inv, + index_set=self.cartan_type().classical().index_set(), + similarity_factor_domain=self.similarity_factor() ) class KR_type_BnElement(KirillovReshetikhinGenericCrystalElement): @@ -3091,12 +3138,13 @@ def promotion_on_highest_weight_vectors_inverse(self): True """ D = self.promotion_on_highest_weight_vectors() - return dict( (D[t],t) for t in D.keys()) + return dict( (D[t],t) for t in D.keys() ) @cached_method def promotion(self): - """ - Returns the promotion operator on `B^{r,s}` of type `D_n^{(1)}` for `r=n-1,n`. + r""" + Return the promotion operator on `B^{r,s}` of type + `D_n^{(1)}` for `r=n-1,n`. EXAMPLES:: @@ -3125,7 +3173,7 @@ def aut(i): elif i==n-1: return n return i - return T.crystal_morphism( self.promotion_on_highest_weight_vectors(), index_set = ind) + return CrystalDiagramAutomorphism(T, self.promotion_on_highest_weight_vectors(), ind) @cached_method def promotion_inverse(self): @@ -3153,7 +3201,7 @@ def aut(i): elif i==n-1: return n return i - return T.crystal_morphism( self.promotion_on_highest_weight_vectors_inverse(), index_set = ind) + return CrystalDiagramAutomorphism(T, self.promotion_on_highest_weight_vectors_inverse(), ind) class KR_type_D_tri1(KirillovReshetikhinGenericCrystal): r""" @@ -3699,3 +3747,192 @@ def horizontal_dominoes_removed(r, s): list = [ [y for y in x] + [0 for i in range(r-x.length())] for x in partitions_in_box(r, int(s/2)) ] two = lambda x : 2*(x-int(s/2)) + s return [Partition([two(y) for y in x]) for x in list] + +##################################################################### +## Morphisms + +class AmbientRetractMap(Map): + r""" + The retraction map from the ambient crystal. + + Consider a crystal embedding `\phi : X \to Y`, then the elements `X` + can be considered as a subcrystal of the ambient crystal `Y`. The + ambient retract is the partial map `\tilde{\phi} : Y \to X` such that + `\tilde{\phi} \circ \phi` is the identity on `X`. + """ + def __init__(self, base, ambient, pdict_inv, index_set, + similarity_factor_domain=None, automorphism=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['B',3,1], 3,1) + sage: phi = K.from_ambient_crystal() + sage: TestSuite(phi).run(skip=['_test_category', '_test_pickling']) + """ + from sage.categories.sets_with_partial_maps import SetsWithPartialMaps + Map.__init__(self, Hom(ambient, base, SetsWithPartialMaps())) + + if similarity_factor_domain is None: + similarity_factor_domain = dict( (i,1) for i in index_set ) + if automorphism is None: + automorphism = lambda i: i + + self._pdict_inv = pdict_inv + self._automorphism = automorphism + self._similarity_factor_domain = similarity_factor_domain + self._index_set = index_set + + def _repr_type(self): + """ + Return a string describing ``self``. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['B',3,1], 3,1) + sage: phi = K.from_ambient_crystal() + sage: phi._repr_type() + 'Ambient retract' + """ + return "Ambient retract" + + def _call_(self, x): + """ + A fast map from the virtual image to the base crystal. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['B',3,1], 3,1) + sage: phi = K.from_ambient_crystal() + sage: b = K.ambient_crystal()(rows=[[1],[2],[-3]]) + sage: phi(b) + [++-, []] + """ + automorphism = self._automorphism + sfd = self._similarity_factor_domain + for i in self._index_set: + c = x.e_string([i for k in range(sfd[i])]) + if c is not None: + d = self(c).f(automorphism(i)) + assert d is not None + #now we know that x is hw + return d + return self._pdict_inv[x] + +class CrystalDiagramAutomorphism(CrystalMorphism): + """ + The crystal automorphism induced from the diagram automorphism. + + For example, in type `A_n^{(1)}` this is the promotion operator and in + type `D_n^{(1)}`, this corresponds to the automorphism induced from + interchanging the `0` and `1` nodes in the Dynkin diagram. + + INPUT: + + - ``C`` -- a crystal + - ``on_hw`` -- a function for the images of the ``index_set``-highest + weight elements + - ``index_set`` -- (default: the empty set) the index set + - ``automorphism`` -- (default: the identity) the twisting automorphism + - ``cache`` -- (default: True) cache the result + """ + def __init__(self, C, on_hw, index_set=None, automorphism=None, cache=True): + """ + Construct the promotion operator. + + TESTS:: + + sage: K = crystals.KirillovReshetikhin(['A',3,1], 2,2) + sage: p = K.promotion() + sage: TestSuite(p).run(skip=['_test_category', '_test_pickling']) + """ + if automorphism is None: + automorphism = lambda i: i + if index_set is None: + index_set = () + self._twist = automorphism + if isinstance(on_hw, dict): + self._on_hw = on_hw.__getitem__ + else: + self._on_hw = on_hw + parent = Hom(C, C) + + self._cache = {} + self._cache_result = bool(cache) + if isinstance(cache, dict): + self._cache = cache + self._index_set = tuple(index_set) + CrystalMorphism.__init__(self, parent, C.cartan_type()) + + def _call_(self, x): + """ + Return the image of ``x`` under ``self``. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',3,1], 2,2) + sage: p = K.promotion() + sage: elt = K(3,1,3,1) + sage: p(elt.lift()) + [[2, 2], [4, 4]] + """ + # We do our own caching so we can take advantage of known images above us + if x in self._cache: + return self._cache[x] + + ind = self._index_set + cur = x + path = [] + while cur not in self._cache: + n = None + for i in ind: + n = cur.e(i) + if n is not None: + path.append(self._twist(i)) + cur = n + break + + if n is None: # We're at a I-highest weight element + break + + if cur in self._cache: + cur = self._cache[cur] + else: + cur = self._on_hw(cur) + + y = cur.f_string(reversed(path)) + assert y is not None + self._cache[x] = y + return y + + def _repr_type(self): + """ + Return a string describing ``self``. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',3,1], 2,2) + sage: K.promotion()._repr_type() + 'Diagram automorphism' + """ + return "Diagram automorphism" + + def is_isomorphism(self): + """ + Return ``True`` as ``self`` is a crystal isomorphism. + + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',3,1], 2,2) + sage: K.promotion().is_isomorphism() + True + """ + return True + + # All of these are consequences of being an isomorphism + is_surjective = is_isomorphism + is_embedding = is_isomorphism + is_strict = is_isomorphism + __nonzero__ = is_isomorphism + diff --git a/src/sage/combinat/crystals/letters.pyx b/src/sage/combinat/crystals/letters.pyx index a7fa5decdf0..8a76da09c3d 100644 --- a/src/sage/combinat/crystals/letters.pyx +++ b/src/sage/combinat/crystals/letters.pyx @@ -418,19 +418,6 @@ cdef class Letter(Element): """ return self.value - def __richcmp__(left, right, int op): - """ - Entry point for rich comparisons. Needed for cython because we are - overriding `__hash__()`. - - EXAMPLES:: - - sage: C = crystals.Letters(['D', 4]) - sage: C(4) > C(-4) - False - """ - return (left)._richcmp(right, op) - cpdef _richcmp_(left, Element right, int op): """ Return ``True`` if ``left`` compares with ``right`` based on ``op``. @@ -1298,19 +1285,6 @@ cdef class LetterTuple(Element): """ return hash(self.value) - def __richcmp__(left, right, int op): - """ - Entry point for rich comparisons. Needed for cython because we are - overriding `__hash__()`. - - EXAMPLES:: - - sage: C = crystals.Letters(['E', 6]) - sage: C((1,)) > C((-1, 3)) - False - """ - return (left)._richcmp(right, op) - cpdef _richcmp_(left, Element right, int op): """ Check comparison between ``left`` and ``right`` based on ``op`` diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py new file mode 100644 index 00000000000..01854b2af76 --- /dev/null +++ b/src/sage/combinat/crystals/subcrystal.py @@ -0,0 +1,397 @@ +r""" +Subcrystals + +These are the crystals that are subsets of a larger ambient crystal. + +AUTHORS: + +- Travis Scrimshaw (2013-10-16): Initial implementation +""" + +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw +# +# 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.lazy_attribute import lazy_attribute +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.crystals import Crystals +from sage.categories.finite_crystals import FiniteCrystals +from sage.combinat.root_system.cartan_type import CartanType +from sage.rings.integer import Integer +from sage.rings.infinity import infinity + +class Subcrystal(Parent, UniqueRepresentation): + """ + 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`. + + INPUT: + + - ``ambient`` -- the ambient crystal + - ``contained`` -- (optional) a set (or function) which specifies when an + element is contained in the subcrystal; the default is everything + possible is included + - ``generators`` -- (optional) the generators for the subcrystal; the + default is the generators for the ambient crystal + - ``virtualization``, ``scaling_factors`` -- (optional) + dictionaries whose key `i` corresponds to the sets `\sigma_i` + and `\gamma_i` respectively used to define virtual crystals; see + :class:`~sage.combinat.crystals.virtual_crystal.VirtualCrystal` + - ``cartan_type`` -- (optional) the Cartan type for the subcrystal; the + default is the Cartan type for the ambient crystal + - ``index_set`` -- (optional) the index set for the subcrystal; the + default is the index set for the Cartan type + - ``category`` -- (optional) the category for the subcrystal; the + default is the :class:`~sage.categories.crystals.Crystals` category + + .. SEEALSO:: + + :meth:`~sage.categories.crystals.Crystals.ParentMethods.subcrystal` + + EXAMPLES: + + We build out a subcrystal starting from an element and only going + to the lowest weight:: + + sage: B = crystals.Tableaux(['A',3], shape=[2,1]) + sage: S = B.subcrystal(generators=[B(3,1,2)], direction='lower') + sage: S.cardinality() + 11 + + Here we build out in both directions starting from an element, but we + also have restricted ourselves to type `A_2`:: + + sage: T = B.subcrystal(index_set=[1,2], generators=[B(3,1,1)]) + sage: T.cardinality() + 8 + sage: list(T) + [[[1, 1], [3]], + [[1, 2], [3]], + [[1, 1], [2]], + [[1, 2], [2]], + [[2, 2], [3]], + [[2, 3], [3]], + [[1, 3], [2]], + [[1, 3], [3]]] + + Now we take the crystal corresponding to the intersection of + the previous two subcrystals:: + + sage: U = B.subcrystal(contained=lambda x: x in S and x in T, generators=B) + sage: list(U) + [[[2, 3], [3]], [[1, 2], [3]], [[2, 2], [3]]] + + .. TODO:: + + Include support for subcrystals which only contains certain arrows. + """ + @staticmethod + def __classcall_private__(cls, ambient, contained=None, generators=None, + virtualization=None, scaling_factors=None, + cartan_type=None, index_set=None, category=None): + """ + Normalize arguments to ensure a (relatively) unique representation. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S1 = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: S2 = B.subcrystal(generators=[B(2,1,1), B(5,2,4)], cartan_type=['A',4], index_set=(1,2)) + sage: S1 is S2 + True + """ + if isinstance(contained, (list, tuple, set, frozenset)): + contained = frozenset(contained) + #elif contained in Sets(): + + if cartan_type is None: + cartan_type = ambient.cartan_type() + else: + cartan_type = CartanType(cartan_type) + if index_set is None: + index_set = cartan_type.index_set + if generators is None: + generators = ambient.module_generators + + category = Crystals().or_subcategory(category) + + if virtualization is not None: + if scaling_factors is None: + scaling_factors = {i:1 for i in index_set} + from sage.combinat.crystals.virtual_crystal import VirtualCrystal + return VirtualCrystal(ambient, virtualization, scaling_factors, contained, + generators, cartan_type, index_set, category) + if scaling_factors is not None: + # virtualization must be None + virtualization = {i:(i,) for i in index_set} + from sage.combinat.crystals.virtual_crystal import VirtualCrystal + return VirtualCrystal(ambient, virtualization, scaling_factors, contained, + generators, cartan_type, index_set, category) + + # We need to give these as optional arguments so it unpickles correctly + return super(Subcrystal, cls).__classcall__(cls, ambient, contained, + tuple(generators), + cartan_type=cartan_type, + index_set=tuple(index_set), + category=category) + + def __init__(self, ambient, contained, generators, cartan_type, index_set, category): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: TestSuite(S).run() + """ + self._ambient = ambient + self._contained = contained + self._cardinality = None # ``None`` means currently unknown + self._cartan_type = cartan_type + self._index_set = tuple(index_set) + Parent.__init__(self, category=category) + self.module_generators = tuple(self.element_class(self, g) for g in generators + if self._containing(g)) + + if isinstance(contained, frozenset): + self._cardinality = Integer(len(contained)) + self._list = [self.element_class(self, x) for x in contained] + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + Subcrystal of The crystal of tableaux of type ['A', 4] and shape(s) [[2, 1]] + """ + return "Subcrystal of {}".format(self._ambient) + + @lazy_attribute + def _containing(self): + """ + Check if ``x`` is contained in ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: S._containing(B(5,2,4)) + True + sage: S._containing(B(4,2,4)) + True + """ + if self._contained is None: + return lambda x: True + if isinstance(self._contained, frozenset): + return self._contained.__contains__ + return self._contained # Otherwise it should be a function + + def __contains__(self, x): + """ + Check if ``x`` is in ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: B(5,2,4) in S + True + sage: mg = B.module_generators[0] + sage: mg in S + True + sage: mg.f(2).f(3) in S + False + """ + if isinstance(x, Subcrystal.Element) and x.parent() == self: + return True + + if x in self._ambient: + if not self._containing(x): + return False + x = self.element_class(self, x) + + if self in FiniteCrystals(): + return x in self.list() + + # TODO: make this work for infinite crystals + import warnings + warnings.warn("Testing containment in an infinite crystal" + " defaults to returning True") + return True + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=[B(2,1,1)], index_set=[1,2]) + sage: S.cardinality() + 8 + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: S = B.subcrystal(max_depth=4) + sage: S.cardinality() + 22 + """ + if self._cardinality is not None: + return self._cardinality + + try: + card = Integer(len(self._list)) + self._cardinality = card + return self._cardinality + except AttributeError: + if self in FiniteCrystals(): + return Integer(len(self.list())) + card = super(Subcrystal, self).cardinality() + if card == infinity: + self._cardinality = card + return card + self._cardinality = Integer(len(self.list())) + return self._cardinality + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: S.index_set() + (1, 2) + """ + return self._index_set + + class Element(ElementWrapper): + """ + An element of a subcrystal. Wraps an element in the ambient crystal. + """ + def __lt__(self, other): + """ + Check less than. + + EXAMPLES:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: sorted(S) + [[[1, 1]](-1), + [[1, 2]](-1), + [](0), + [[1, 1]](0), + [[1, 2]](0), + [[1, -2]](0), + [[2, 2]](0), + [](1), + [[2, -1]](1), + [[-2, -1]](1), + [[-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 + + def e(self, i): + """ + Return `e_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: mg = S.module_generators[1] + sage: mg.e(2) + sage: mg.e(1) + [[1, 4], [5]] + """ + ret = self.value.e(i) + if ret is None or not self.parent()._containing(ret): + return None + return self.__class__(self.parent(), ret) + + def f(self, i): + """ + Return `f_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: mg = S.module_generators[1] + sage: mg.f(1) + sage: mg.f(2) + [[3, 4], [5]] + """ + ret = self.value.f(i) + if ret is None or not self.parent()._containing(ret): + return None + return self.__class__(self.parent(), ret) + + def epsilon(self, i): + r""" + Return `\varepsilon_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: mg = S.module_generators[1] + sage: mg.epsilon(1) + 1 + sage: mg.epsilon(2) + 0 + """ + return self.value.epsilon(i) + + def phi(self, i): + r""" + Return `\varphi_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: mg = S.module_generators[1] + sage: mg.phi(1) + 0 + sage: mg.phi(2) + 1 + """ + return self.value.phi(i) + + def weight(self): + """ + Return the weight of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A',4], shape=[2,1]) + sage: S = B.subcrystal(generators=(B(2,1,1), B(5,2,4)), index_set=[1,2]) + sage: mg = S.module_generators[1] + sage: mg.weight() + (0, 1, 0, 1, 1) + """ + return self.value.weight() + diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index e608eccc19d..8c746d07ebc 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -656,12 +656,16 @@ def __classcall_private__(cls, *crystals, **options): sage: C = crystals.Letters(['A',2]) sage: T = crystals.TensorProduct(C, C) - sage: T2 = crystals.TensorProduct(C, C) + sage: T2 = crystals.TensorProduct(C, C, cartan_type=['A',2]) sage: T is T2 True sage: T.category() Category of tensor products of classical crystals + sage: T3 = crystals.TensorProduct(C, C, C) + sage: T3p = crystals.TensorProduct(T, C) + sage: T3 is T3p + True sage: B1 = crystals.TensorProduct(T, C) sage: B2 = crystals.TensorProduct(C, T) sage: B3 = crystals.TensorProduct(C, C, C) @@ -734,6 +738,11 @@ def _element_constructor_(self, *crystalElements): class TensorProductOfCrystalsWithGenerators(TensorProductOfCrystals): """ Tensor product of crystals with a generating set. + + .. TODO:: + + Deprecate this class in favor of using + :meth:`~sage.categories.crystals.Crystals.ParentMethods.subcrystal`. """ def __init__(self, crystals, generators, cartan_type): """ @@ -770,6 +779,10 @@ def _repr_(self): class FullTensorProductOfCrystals(TensorProductOfCrystals): """ Full tensor product of crystals. + + .. TODO:: + + Merge this into :class:`TensorProductOfCrystals`. """ def __init__(self, crystals, **options): """ diff --git a/src/sage/combinat/crystals/virtual_crystal.py b/src/sage/combinat/crystals/virtual_crystal.py new file mode 100644 index 00000000000..eef6051c311 --- /dev/null +++ b/src/sage/combinat/crystals/virtual_crystal.py @@ -0,0 +1,405 @@ +r""" +Virtual Crystals + +These are the crystals that are subsets of a larger ambient crystal with +virtual crystal operators. + +AUTHORS: + +- Travis Scrimshaw (2013-10-16): Initial implementation +""" + +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw +# +# 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.categories.crystals import Crystals +from sage.categories.finite_crystals import FiniteCrystals +from sage.combinat.root_system.cartan_type import CartanType +from sage.combinat.crystals.subcrystal import Subcrystal +from sage.sets.family import Family + +class VirtualCrystal(Subcrystal): + r""" + A virtual crystal `V` of an ambient crystal `\widehat{B}` is a crystal + formed by taking a subset of `\widehat{B}` and whose crystal structure + is given by + + .. MATH:: + + e_i = \prod_{j \in \sigma_i} \widehat{e}_j^{\gamma_i}, \quad + f_i = \prod_{j \in \sigma_i} \widehat{f}_j^{\gamma_i}, + + .. MATH:: + + \varepsilon_i = \frac{\widehat{\varepsilon}_j}{\gamma_j}, \quad + \varphi_i = \frac{\widehat{\varphi}_j}{\gamma_j}, \quad + \operatorname{wt} = \Psi^{-1} \circ \widehat{\operatorname{wt}} + + where `\sigma_i` is a subset of the index set of `B`, `\gamma_i \in \ZZ` + are the *scaling factors*, and `\Psi : P \to \widehat{P}` is an embedding + of the weight lattices. We note that for the crystal to be well-defined, + we must have + + .. MATH:: + + \widehat{\varepsilon}_j = \widehat{\varepsilon|j^{\prime}}, + \quad \widehat{\varphi}_j = \widehat{\varphi}_{j^{\prime}} + + for all `j, j^{\prime} \in \sigma_i` and that the order that the Kashiwara + operators in the ambient space are applied does not affect the result. + + INPUT: + + - ``ambient`` -- the ambient crystal + - ``virtualization`` -- a dictionary whose key `i` corresponds + to the set `\sigma_i` + - ``scaling_factors`` -- a dictionary whose key `i` corresponds to + the scaling factor `\gamma_i` + - ``contained`` -- (optional) a set (or function) which specifies when an + element is contained in the subcrystal; the default is everything + possible is included + - ``generators`` -- (optional) the generators for the virtual crystal; the + default is the generators for the ambient crystal + - ``cartan_type`` -- (optional) the Cartan type for the virtual crystal; + the default is the Cartan type for the ambient crystal + - ``index_set`` -- (optional) the index set for the virtual crystal; the + default is the index set for the Cartan type + - ``category`` -- (optional) the category for the virtual crystal; the + default is the :class:`~sage.categories.crystals.Crystals` category + + EXAMPLES: + + We construct an example from a natural virtualization map of type `C_n` + in type `A_{2n-1}`:: + + sage: C = crystals.Tableaux(['C',2], shape=[1]) + sage: A = crystals.Tableaux(['A',3], shape=[2,1,1]) + sage: psi = C.crystal_morphism(A.module_generators) + sage: V = psi.image() + sage: list(V) + [[[1, 1], [2], [3]], + [[1, 2], [2], [4]], + [[1, 3], [3], [4]], + [[2, 4], [3], [4]]] + sage: V.digraph().is_isomorphic(C.digraph(), edge_labels=True) + True + + We construct the virtualization of a `U_q'(\mathfrak{g})`-crystal + `B^{r,s}` of type `C_n^{(1)}` in type `A_{2n+1}^{(2)}`. Here it is not + a default folding known to Sage, so we have to explicitly state the + folding (since the scaling factors are not specified, they are all + assumed to be 1):: + + sage: K = crystals.KirillovReshetikhin(['C',2,1], 1,1) + sage: VK = crystals.KirillovReshetikhin(['A',5,2], 1,1) + sage: target = VK.module_generator().f(1); target + [[2]] + sage: psi = K.crystal_morphism({K.module_generator(): target}, + ....: virtualization={0:[0,1], 1:[2], 2:[3]}) + sage: V = psi.image() + sage: list(V) + [[[2]], [[3]], [[-2]], [[-3]]] + sage: V.digraph().is_isomorphic(K.digraph(), edge_labels=True) + True + + We create an example of `B(\Lambda_n)` of type `B_n` inside + of `B(2\Lambda_n)` using the doubling map through the (virtual) + subcrystal method:: + + sage: BB = crystals.Tableaux(['B',3], shape=[1,1,1]) + sage: S = BB.subcrystal(scaling_factors={1:2, 2:2, 3:2}) + sage: B = crystals.Tableaux(['B',3], shape=[1/2,1/2,1/2]) + sage: S.digraph().is_isomorphic(B.digraph(), edge_labels=True) + True + + We can also directly construct a virtual crystal using + :class:`VirtualCrystal` (however it is recommended to use either + :meth:`~sage.categories.crystals.Crystals.ParentMethods.crystal_morphism` + or :meth:`~sage.categories.crystals.Crystals.ParentMethods.subcrystal`):: + + 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: G = crystals.Tableaux(['C',2], shape=[1]).digraph() + sage: V.digraph().is_isomorphic(G, edge_labels=True) + True + + sage: C1 = crystals.Tableaux(['A',3], shape=[1]) + sage: C2 = crystals.Tableaux(['A',3], shape=[1,1,1]) + sage: T = C1.tensor(C2) + sage: mg = T(C1.module_generators[0], C2.module_generators[0]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, + ....: cartan_type=['C',2], generators=[mg]) + sage: V.digraph().is_isomorphic(G, edge_labels=True) + True + + REFERENCES: + + - [FOS09]_ + - [OSS03]_ + - [OSS2003]_ + """ + @staticmethod + def __classcall_private__(cls, ambient, virtualization, scaling_factors, + contained=None, generators=None, + cartan_type=None, index_set=None, category=None): + """ + Normalize arguments to ensure a unique representation. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi1 = B.crystal_morphism(C.module_generators) + sage: V1 = psi1.image() + sage: psi2 = B.crystal_morphism(C.module_generators, index_set=[1,2,3]) + sage: V2 = psi2.image() + sage: V1 is V2 + True + """ + if cartan_type is None: + cartan_type = ambient.cartan_type() + else: + cartan_type = CartanType(cartan_type) + if index_set is None: + index_set = cartan_type.index_set() + if generators is None: + generators = ambient.module_generators + virtualization = Family(virtualization) + scaling_factors = Family(scaling_factors) + + category = Crystals().or_subcategory(category) + + return super(Subcrystal, cls).__classcall__(cls, ambient, virtualization, scaling_factors, + contained, tuple(generators), cartan_type, + tuple(index_set), category) + + def __init__(self, ambient, virtualization, scaling_factors, + contained, generators, cartan_type, index_set, category): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: TestSuite(V).run() + """ + self._virtualization = virtualization + self._scaling_factors = scaling_factors + Subcrystal.__init__(self, ambient, contained, generators, + cartan_type, index_set, category) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: psi.image() + Virtual crystal of The crystal of tableaux of type ['D', 4] and shape(s) [[2]] of type ['B', 3] + """ + return "Virtual crystal of {} of type {}".format(self._ambient, self._cartan_type) + + def __contains__(self, x): + """ + Check if ``x`` is in ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = C.module_generators[0] + sage: mg in V + True + sage: mg.f(1) in V + False + sage: mg.f(1).f(1) in V + True + """ + if not Subcrystal.__contains__(self, x): + return False + if self in FiniteCrystals(): + if isinstance(x, self._ambient.element_class): + if x.parent() == self: + x = self.element_class(self, self._ambient(x)) + elif x.parent() == self._ambient: + x = self.element_class(self, self._ambient(x)) + elif isinstance(x, self.element_class) and x.parent() != self: + x = self.element_class(self, x.value) + return x in self.list() + return True + + def virtualization(self): + """ + Return the virtualization sets `\sigma_i`. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: V.virtualization() + Finite family {1: (1,), 2: (2,), 3: (3, 4)} + """ + return self._virtualization + + def scaling_factors(self): + """ + Return the scaling factors `\gamma_i`. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: V.scaling_factors() + Finite family {1: 2, 2: 2, 3: 1} + """ + return self._scaling_factors + + class Element(Subcrystal.Element): + """ + An element of a virtual (sub)crystal. Wraps an element in the + ambient crystal. + """ + def e(self, i): + """ + Return `e_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = V.module_generators[0] + sage: mg.e(1) + sage: b = psi(B.module_generators[0].f(1)) + sage: V(b).e(1) + [[1, 1]] + """ + s = [] + P = self.parent() + sf = P._scaling_factors[i] + for j in P._virtualization[i]: + s += [j]*sf + ret = self.value.e_string(s) + if ret is None: + return None + return self.__class__(P, ret) + + def f(self, i): + """ + Return `f_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = V.module_generators[0] + sage: mg.f(1) + [[2, 2]] + sage: mg.f(2) + """ + s = [] + P = self.parent() + sf = P._scaling_factors[i] + for j in P._virtualization[i]: + s += [j]*sf + ret = self.value.f_string(s) + if ret is None: + return None + return self.__class__(P, ret) + + def epsilon(self, i): + r""" + Return `\varepsilon_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = V.module_generators[0] + sage: mg.epsilon(2) + 0 + sage: mg.f(1).epsilon(1) + 1 + """ + P = self.parent() + return self.value.epsilon(P._virtualization[i][0]) / P._scaling_factors[i] + + def phi(self, i): + r""" + Return `\varphi_i` of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = V.module_generators[0] + sage: mg.phi(1) + 1 + sage: mg.phi(2) + 0 + """ + P = self.parent() + return self.value.phi(P._virtualization[i][0]) / P._scaling_factors[i] + + def weight(self): + """ + Return the weight of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['B',3], shape=[1]) + sage: C = crystals.Tableaux(['D',4], shape=[2]) + sage: psi = B.crystal_morphism(C.module_generators) + sage: V = psi.image() + sage: mg = V.module_generators[0] + sage: mg.weight() + (1, 0, 0) + sage: mg.f(1).weight() + (0, 1, 0) + sage: all(V(psi(x)).weight() == x.weight() for x in B) + True + """ + P = self.parent() + WLR = P.weight_lattice_realization() + wt = self.value.weight() + ac = P._ambient.weight_lattice_realization().simple_coroots() + La = WLR.fundamental_weights() + v = P._virtualization + sf = P._scaling_factors + return WLR.sum(wt.scalar(ac[v[i][0]]) / sf[i] * La[i] + for i in self.index_set()) + +# TODO: implement a devirtualization map + diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index c94dc998ab4..f83e07ec611 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -317,7 +317,7 @@ def steiner_triple_system(n): t = (n-3) // 6 Z = range(2*t+1) - T = lambda (x,y) : x + (2*t+1)*y + T = lambda x_y : x_y[0] + (2*t+1)*x_y[1] sts = [[(i,0),(i,1),(i,2)] for i in Z] + \ [[(i,k),(j,k),(((t+1)*(i+j)) % (2*t+1),(k+1)%3)] for k in range(3) for i in Z for j in Z if i != j] @@ -326,7 +326,7 @@ def steiner_triple_system(n): t = (n-1) // 6 N = range(2*t) - T = lambda (x,y) : x+y*t*2 if (x,y) != (-1,-1) else n-1 + T = lambda x_y : x_y[0]+x_y[1]*t*2 if x_y != (-1,-1) else n-1 L1 = lambda i,j : (i+j) % ((n-1)//3) L = lambda i,j : L1(i,j)//2 if L1(i,j)%2 == 0 else t+(L1(i,j)-1)//2 diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index f4da2356734..9e79cd02f49 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -9,19 +9,7 @@ It defines the following functions: -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :func:`is_difference_family` | Check if the input is a (``k``, ``l``)-difference family. - :func:`difference_family` | Return a (``k``, ``l``)-difference family on an Abelian group of size ``v``. - :func:`radical_difference_family` | Return a radical difference family. - :func:`radical_difference_set` | Return a radical difference set. - :func:`singer_difference_set` | Return a difference set associated to hyperplanes in a projective space. - :func:`df_q_6_1` | Return a difference family with parameter `k=6` on a finite field. - :func:`one_radical_difference_family` | Return a radical difference family using an exhaustive search. - :func:`twin_prime_powers_difference_set` | Return a twin prime powers difference family. +{INDEX_OF_FUNCTIONS} REFERENCES: @@ -1338,3 +1326,7 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch "Please contact sage-devel@googlegroups.com".format(G,v,k,l,D)) return G, D + +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/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index fc0876497be..6d670a62307 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -4,35 +4,7 @@ An incidence structure is specified by a list of points, blocks, or an incidence matrix ([1]_, [2]_). :class:`IncidenceStructure` instances have the following methods: -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~IncidenceStructure.ground_set` | Return the ground set (i.e the list of points). - :meth:`~IncidenceStructure.num_points` | Return the size of the ground set. - :meth:`~IncidenceStructure.num_blocks` | Return the number of blocks. - :meth:`~IncidenceStructure.blocks` | Return the list of blocks. - :meth:`~IncidenceStructure.block_sizes` | Return the set of block sizes. - :meth:`~IncidenceStructure.degree` | Return the degree of a point `p` - :meth:`~IncidenceStructure.degrees` | Return the degree of all sets of given size, or the degree of all points. - :meth:`~IncidenceStructure.is_connected` | Test whether the design is connected. - :meth:`~IncidenceStructure.is_simple` | Test whether this design is simple (i.e. no repeated block). - :meth:`~IncidenceStructure.incidence_matrix` | Return the incidence matrix `A` of the design - :meth:`~IncidenceStructure.incidence_graph` | Return the incidence graph of the design - :meth:`~IncidenceStructure.packing` | Return a maximum packing - :meth:`~IncidenceStructure.relabel` | Relabel the ground set - :meth:`~IncidenceStructure.is_resolvable` | Test whether the hypergraph is resolvable - :meth:`~IncidenceStructure.is_t_design` | Test whether ``self`` is a `t-(v,k,l)` design. - :meth:`~IncidenceStructure.dual` | Return the dual design. - :meth:`~IncidenceStructure.automorphism_group` | Return the automorphism group - :meth:`~IncidenceStructure.canonical_label` | Return a canonical label for the incidence structure. - :meth:`~IncidenceStructure.is_isomorphic` | Return whether the two incidence structures are isomorphic. - :meth:`~IncidenceStructure.isomorphic_substructures_iterator` | Iterates over all copies of ``H2`` contained in ``self`` - :meth:`~IncidenceStructure.edge_coloring` | Return an optimal edge coloring` - :meth:`~IncidenceStructure.copy` | Return a copy of the incidence structure. - :meth:`~IncidenceStructure.induced_substructure` | Return the substructure induced by a set of points. - :meth:`~IncidenceStructure.trace` | Return the trace of a set of point +{METHODS_OF_IncidenceStructure} REFERENCES: @@ -1939,3 +1911,6 @@ def _latex_(self): tex += "\\end{tikzpicture}" return tex + +from sage.misc.rest_index_of_methods import gen_rest_table_index +__doc__ = __doc__.format(METHODS_OF_IncidenceStructure=gen_rest_table_index(IncidenceStructure)) diff --git a/src/sage/combinat/designs/latin_squares.py b/src/sage/combinat/designs/latin_squares.py index 79094f65da5..0364ce14509 100644 --- a/src/sage/combinat/designs/latin_squares.py +++ b/src/sage/combinat/designs/latin_squares.py @@ -395,8 +395,8 @@ def mutually_orthogonal_latin_squares(k,n, partitions = False, check = True, exi return False raise EmptySetError("There does not exist {} MOLS of order {}!".format(k,n)) - OA = orthogonal_array(k+2,n,check=False) - OA.sort() # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..." + # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..." + OA = sorted(orthogonal_array(k+2,n,check=False)) # We first define matrices as lists of n^2 values matrices = [[] for _ in range(k)] diff --git a/src/sage/combinat/designs/orthogonal_arrays.py b/src/sage/combinat/designs/orthogonal_arrays.py index 424b95b6f79..df597c315a5 100644 --- a/src/sage/combinat/designs/orthogonal_arrays.py +++ b/src/sage/combinat/designs/orthogonal_arrays.py @@ -846,8 +846,7 @@ def orthogonal_array(k,n,t=2,resolvable=False, check=True,existence=False,explai k = orthogonal_array(None,n,existence=True)-1 if existence: return k - OA = orthogonal_array(k+1,n,check=check) - OA.sort() + OA = sorted(orthogonal_array(k+1,n,check=check)) return [B[1:] for B in OA] # If k is set to None we find the largest value available @@ -1263,8 +1262,7 @@ def incomplete_orthogonal_array(k,n,holes,resolvable=False, existence=False): if existence: return orthogonal_array(k+1,n,existence=True) - OA = orthogonal_array(k+1,n) - OA.sort() # The future classes are now well-ordered + OA = sorted(orthogonal_array(k+1,n)) OA = [B[1:] for B in OA] # We now relabel the points so that the last n blocks are the [i,i,...] diff --git a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py index ca54123910f..bce8794da58 100644 --- a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py +++ b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py @@ -700,8 +700,7 @@ def thwart_lemma_3_5(k,n,m,a,b,c,d=0,complement=False,explain_construction=False # Adding the first two trivial columns OA.insert(0,[j for i in range(n) for j in range(n)]) OA.insert(0,[i for i in range(n) for j in range(n)]) - OA=zip(*OA) - OA.sort() + OA=sorted(zip(*OA)) # Moves the first three columns to the end OA = [list(B[3:]+B[:3]) for B in OA] @@ -1089,8 +1088,7 @@ def product_with_parallel_classes(OA1,k,g1,g2,g1_parall,parall,check=True): # # OA1 and resolvable OA2 and OA3 OA1 = orthogonal_array(k,n1) - OA3 = orthogonal_array(k+1,n3) - OA3.sort() + OA3 = sorted(orthogonal_array(k+1,n3)) OA3 = [B[1:] for B in OA3] OA2 = orthogonal_array(k+1,n2) OA2.sort() diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 14a7e685b10..78883634690 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -193,6 +193,7 @@ :attr:`~FSMState.final_word_out` | Final output of a state :attr:`~FSMState.is_final` | Describes whether a state is final or not :attr:`~FSMState.is_initial` | Describes whether a state is initial or not + :attr:`~FSMState.initial_probability` | Probability of starting in this state as part of a Markov chain :meth:`~FSMState.label` | Label of a state :meth:`~FSMState.relabeled` | Returns a relabeled deep copy of a state :meth:`~FSMState.fully_equal` | Checks whether two states are fully equal (including all attributes) @@ -339,7 +340,6 @@ So let's test the automaton with some input:: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: NAF([0]) True sage: NAF([0, 1]) @@ -542,7 +542,6 @@ Transition from 0 to 1: 1|0, Transition from 1 to 0: 0|1, Transition from 1 to 1: 1|1] - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: shift_right_transducer([0, 1, 1, 0]) [0, 1, 1] sage: shift_right_transducer([1, 0, 0]) @@ -601,16 +600,9 @@ ``xor_transducer`` as a :meth:`composition ` of transducers. -As described in :meth:`Transducer.cartesian_product`, we have to -temporarily set -``finite_state_machine.FSMOldCodeTransducerCartesianProduct`` to -``False`` in order to disable backwards compatible code. - :: - sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = False sage: product_transducer = shift_right_transducer.cartesian_product(transducers.Identity([0, 1])) - sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = True sage: Gray_transducer = xor_transducer(product_transducer) We use :meth:`~FiniteStateMachine.construct_final_word_out` to make sure that all output @@ -779,7 +771,8 @@ .. [HKP2015] Clemens Heuberger, Sara Kropf, and Helmut Prodinger, *Output sum of transducers: Limiting distribution and periodic - fluctuation*, :arxiv:`1502.01412`. + fluctuation*, + `Electron. J. Combin. 22 (2015), #P2.19 `_. .. [HKW2015] Clemens Heuberger, Sara Kropf and Stephan Wagner, *Variances and Covariances in the Central Limit Theorem for the Output @@ -1008,8 +1001,6 @@ def startswith(list, prefix): FSMEmptyWordSymbol = '-' EmptyWordLaTeX = r'\varepsilon' EndOfWordLaTeX = r'\$' -FSMOldCodeTransducerCartesianProduct = True -FSMOldProcessOutput = True # See trac #16132 (deprecation). tikz_automata_where = {"right": 0, "above": 90, "left": 180, @@ -1105,6 +1096,9 @@ class FSMState(SageObject): the state is reached as the last state of some input; only for final states. + - ``initial_probability`` -- (default: ``None``) The probability of + starting in this state if it is a state of a Markov chain. + - ``hook`` -- (default: ``None``) A function which is called when the state is reached during processing input. It takes two input parameters: the first is the current state (to allow using the same @@ -1247,9 +1241,22 @@ class FSMState(SageObject): [0] """ + initial_probability = None + """ + The probability of starting in this state if it is part of a Markov chain. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: S = FSMState('state', initial_probability=1/3) + sage: S.initial_probability + 1/3 + """ + def __init__(self, label, word_out=None, is_initial=False, is_final=False, final_word_out=None, + initial_probability=None, hook=None, color=None, allow_label_None=False): """ See :class:`FSMState` for more information. @@ -1319,6 +1326,7 @@ def __init__(self, label, word_out=None, self._final_word_out_ = None self.is_final = is_final self.final_word_out = final_word_out + self.initial_probability = initial_probability if hook is not None: if hasattr(hook, '__call__'): @@ -1544,13 +1552,32 @@ def __copy__(self): sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') - sage: copy(A) - 'A' + sage: A.is_initial = True + sage: A.is_final = True + sage: A.final_word_out = [1] + sage: A.color = 'green' + sage: A.initial_probability = 1/2 + sage: B = copy(A) + sage: B.fully_equal(A) + True + sage: A.label() is B.label() + True + sage: A.is_initial is B.is_initial + True + sage: A.is_final is B.is_final + True + sage: A.final_word_out is B.final_word_out + True + sage: A.color is B.color + True + sage: A.initial_probability is B.initial_probability + True """ new = FSMState(self.label(), self.word_out, self.is_initial, self.is_final, color=self.color, - final_word_out=self.final_word_out) + final_word_out=self.final_word_out, + initial_probability=self.initial_probability) if hasattr(self, 'hook'): new.hook = self.hook return new @@ -1588,6 +1615,7 @@ def __deepcopy__(self, memo): new.hook = deepcopy(self.hook, memo) new.color = deepcopy(self.color, memo) new.final_word_out = deepcopy(self.final_word_out, memo) + new.initial_probability = deepcopy(self.initial_probability, memo) return new @@ -1608,7 +1636,8 @@ def deepcopy(self, memo=None): sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState((1, 3), color=[1, 2], - ....: is_final=True, final_word_out=3) + ....: is_final=True, final_word_out=3, + ....: initial_probability=1/3) sage: B = deepcopy(A) sage: B (1, 3) @@ -1628,6 +1657,10 @@ def deepcopy(self, memo=None): True sage: B.final_word_out is A.final_word_out False + sage: B.initial_probability == A.initial_probability + True + sage: B.initial_probability is A.initial_probability + False """ return deepcopy(self, memo) @@ -1808,7 +1841,8 @@ def fully_equal(left, right, compare_color=True): left.is_final == right.is_final and left.final_word_out == right.final_word_out and left.word_out == right.word_out and - color) + color and + left.initial_probability == right.initial_probability) def __nonzero__(self): @@ -3443,23 +3477,12 @@ def __imul__(self, other): def __call__(self, *args, **kwargs): """ - .. WARNING:: - - The default output of this method is scheduled to change. - This docstring describes the new default behaviour, which can - already be achieved by setting - ``FSMOldProcessOutput`` to ``False``. - - Calls either method :meth:`.composition` or :meth:`.process` (with + Call either method :meth:`.composition` or :meth:`.process` (with ``full_output=False``). See the documentation of these functions for possible parameters. - By setting ``FSMOldProcessOutput`` to ``False`` - the new desired output is produced. - EXAMPLES:: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior 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]) @@ -3846,8 +3869,9 @@ def is_Markov_chain(self, is_zero=None): ``True`` or ``False``. :attr:`on_duplicate_transition` must be - :func:`duplicate_transition_add_input` and the sum of the input - weights of the transitions leaving a state must add up to 1. + :func:`duplicate_transition_add_input`, the sum of the input weights + of the transitions leaving a state must add up to 1 and the sum of + initial probabilities must add up to 1 (or all be ``None``). EXAMPLES:: @@ -3874,6 +3898,24 @@ def is_Markov_chain(self, is_zero=None): sage: F.is_Markov_chain() False + The initial probabilities of all states must be ``None`` or they must + sum up to 1. The initial probabilities of all states have to be set in the latter case:: + + sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], + ....: [1, 0, 1, 0]], + ....: on_duplicate_transition=duplicate_transition_add_input) + sage: F.is_Markov_chain() + True + sage: F.state(0).initial_probability = 1/4 + sage: F.is_Markov_chain() + False + sage: F.state(1).initial_probability = 7 + sage: F.is_Markov_chain() + False + sage: F.state(1).initial_probability = 3/4 + sage: F.is_Markov_chain() + True + If the probabilities are variables in the symbolic ring, :func:`~sage.symbolic.assumptions.assume` will do the trick:: @@ -3897,6 +3939,7 @@ def is_Markov_chain(self, is_zero=None): ....: return polynomial in (p + q - 1)*R sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], ....: on_duplicate_transition=duplicate_transition_add_input) + sage: F.state(0).initial_probability = p + q sage: F.is_Markov_chain() False sage: F.is_Markov_chain(is_zero_polynomial) @@ -3912,8 +3955,17 @@ def default_is_zero(expression): if self.on_duplicate_transition != duplicate_transition_add_input: return False + if any(s.initial_probability is not None for s in self.iter_states()) and \ + any(s.initial_probability is None for s in self.iter_states()): + return False + + if any(s.initial_probability is not None for s in self.iter_states()) and \ + not is_zero_function(sum(s.initial_probability for s + in self.iter_states()) - 1): + return False + return all(is_zero_function(sum(t.word_in[0] for t in state.transitions) - 1) - for state in self.states()) + for state in self.iter_states()) #************************************************************************* @@ -7307,7 +7359,8 @@ def _composition_explorative_(self, other): sage: B.determinisation() Automaton with 1 state """ - def composition_transition((state1, state2), input): + def composition_transition(states, input): + (state1, state2) = states return [((new_state1, new_state2), output_second) for _, new_state1, output_first in first.process([input], @@ -8894,11 +8947,9 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: block01 = transducers.CountSubblockOccurrences( ....: [0, 1], ....: input_alphabet=[0, 1]) - sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = False sage: product_01x10 = block01.cartesian_product(block10) sage: block_difference = transducers.sub([0, 1])(product_01x10) sage: T = block_difference.simplification().relabeled() - sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = True sage: T.transitions() [Transition from 0 to 1: 0|-1, Transition from 0 to 0: 1|0, @@ -9687,14 +9738,7 @@ def _minimization_Moore_(self): def process(self, *args, **kwargs): """ - .. WARNING:: - - The default output of this method is scheduled to change. - This docstring describes the new default behaviour, which can - already be achieved by setting - ``FSMOldProcessOutput`` to ``False``. - - Returns whether the automaton accepts the input and the state + Return whether the automaton accepts the input and the state where the computation stops. INPUT: @@ -9778,9 +9822,6 @@ def process(self, *args, **kwargs): You can use :meth:`.determinisation` to get a deterministic automaton machine. - By setting ``FSMOldProcessOutput`` to ``False`` - the new desired output is produced. - This function uses an iterator which, in its simplest form, goes from one state to another in each step. To decide which way to go, it uses the input words of the outgoing transitions and @@ -9828,7 +9869,6 @@ def process(self, *args, **kwargs): :: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: NAF = Automaton( ....: {'_': [('_', 0), ('1', 1)], '1': [('_', 0)]}, ....: initial_states=['_'], final_states=['_', '1']) @@ -9911,14 +9951,6 @@ def process(self, *args, **kwargs): :meth:`~FiniteStateMachine.__call__`, :class:`FSMProcessIterator`. """ - if FSMOldProcessOutput: - from sage.misc.superseded import deprecation - deprecation(16132, "The output of Automaton.process " - "(and thus of Automaton.__call__) " - "will change. Please use the corresponding " - "functions from FiniteStateMachine " - "for the original output.") - # set default values options = copy(self._process_default_options_) options.update(kwargs) @@ -9974,7 +10006,7 @@ def _process_convert_output_(self, output_data, **kwargs): ....: always_include_output=True) (True, 'a', [1, 0, 1]) """ - if FSMOldProcessOutput or kwargs['always_include_output']: + if kwargs['always_include_output']: return super(Automaton, self)._process_convert_output_( output_data, **kwargs) accept_input, current_state, output = output_data @@ -10335,13 +10367,6 @@ def function(transition1, transition2): def cartesian_product(self, other, only_accessible_components=True): """ - .. WARNING:: - - The default output of this method is scheduled to change. - This docstring describes the new default behaviour, which can - already be achieved by setting - ``FSMOldCodeTransducerCartesianProduct`` to ``False``. - Return a new transducer which can simultaneously process an input with the machines ``self`` and ``other`` where the output labels are `d`-tuples of the original output labels. @@ -10370,13 +10395,7 @@ def cartesian_product(self, other, only_accessible_components=True): B_d), a, (b_1, \ldots, b_d))` in the new transducer if `a_1 = \cdots = a_d =: a`. - EXAMPLES: - - Originally a different output was constructed by - :meth:`Transducer.cartesian_product`. This output is now produced by - :meth:`Transducer.intersection`. - - :: + EXAMPLES:: sage: transducer1 = Transducer([('A', 'A', 0, 0), ....: ('A', 'A', 1, 1)], @@ -10390,20 +10409,6 @@ def cartesian_product(self, other, only_accessible_components=True): ....: final_states=[1], ....: determine_alphabets=True) sage: result = transducer1.cartesian_product(transducer2) - doctest:...: DeprecationWarning: The output of - Transducer.cartesian_product will change. - Please use Transducer.intersection for the original output. - See http://trac.sagemath.org/16061 for details. - sage: result - Transducer with 1 state - - By setting ``FSMOldCodeTransducerCartesianProduct`` to ``False`` - the new desired output is produced. - - :: - - sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = False - sage: result = transducer1.cartesian_product(transducer2) sage: result Transducer with 2 states sage: result.transitions() @@ -10524,16 +10529,6 @@ def cartesian_product(self, other, only_accessible_components=True): (0, 0, 0), (0, 0, 1)] """ - if FSMOldCodeTransducerCartesianProduct: - from sage.misc.superseded import deprecation - deprecation(16061, "The output of Transducer.cartesian_product " - "will change. Please use " - "Transducer.intersection for the original " - "output.") - return self.intersection( - other, - only_accessible_components=only_accessible_components) - def function(*transitions): if equal(t.word_in for t in transitions): return (transitions[0].word_in, @@ -10654,14 +10649,7 @@ def simplification(self): def process(self, *args, **kwargs): """ - .. WARNING:: - - The default output of this method is scheduled to change. - This docstring describes the new default behaviour, which can - already be achieved by setting - ``FSMOldProcessOutput`` to ``False``. - - Returns whether the transducer accepts the input, the state + Return whether the transducer accepts the input, the state where the computation stops and which output is generated. INPUT: @@ -10744,9 +10732,6 @@ def process(self, *args, **kwargs): Note that in the case the transducer is not deterministic, all possible paths are taken into account. - By setting ``FSMOldProcessOutput`` to ``False`` - the new desired output is produced. - This function uses an iterator which, in its simplest form, goes from one state to another in each step. To decide which way to go, it uses the input words of the outgoing transitions and @@ -10786,7 +10771,6 @@ def process(self, *args, **kwargs): EXAMPLES:: - sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) @@ -10924,14 +10908,6 @@ def process(self, *args, **kwargs): ... TypeError: No input tape given. """ - if FSMOldProcessOutput: - from sage.misc.superseded import deprecation - deprecation(16132, "The output of Transducer.process " - "(and thus of Transducer.__call__) " - "will change. Please use the corresponding " - "functions from FiniteStateMachine " - "for the original output.") - # set default values options = copy(self._process_default_options_) options.update(kwargs) @@ -10983,9 +10959,6 @@ def _process_convert_output_(self, output_data, **kwargs): ....: full_output=True) (True, 'a', [1, 0, 1]) """ - if FSMOldProcessOutput: - return super(Transducer, self)._process_convert_output_( - output_data, **kwargs) accept_input, current_state, output = output_data if kwargs['full_output']: if current_state.label() is None: diff --git a/src/sage/combinat/finite_state_machine_generators.py b/src/sage/combinat/finite_state_machine_generators.py index a6f2b0b9c1f..8e0e84f4ec9 100644 --- a/src/sage/combinat/finite_state_machine_generators.py +++ b/src/sage/combinat/finite_state_machine_generators.py @@ -75,6 +75,7 @@ from sage.combinat.finite_state_machine import Transducer from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ +from functools import reduce class TransducerGenerators(object): r""" @@ -1648,7 +1649,8 @@ def recursion_transitions(carry, level, force_nonnegative_target): return ((c, j), output) - def transition_function((state_carry, state_level), input): + def transition_function(states2, input): + (state_carry, state_level) = states2 ((carry, level), output) = recursion_transitions( state_carry, state_level, False) # no more recursion transition is possible, diff --git a/src/sage/combinat/integer_list.py b/src/sage/combinat/integer_list.py index d9158ca99d6..d37523b6e8a 100644 --- a/src/sage/combinat/integer_list.py +++ b/src/sage/combinat/integer_list.py @@ -1243,9 +1243,9 @@ def __eq__(self, other): a = self._element_constructor b = other._element_constructor if ismethod(a): - a = a.im_func + a = a.__func__ if ismethod(b): - b = b.im_func + b = b.__func__ return a == b def __ne__(self, other): diff --git a/src/sage/combinat/matrices/all.py b/src/sage/combinat/matrices/all.py index bfbd88c7738..aefbbfd3495 100644 --- a/src/sage/combinat/matrices/all.py +++ b/src/sage/combinat/matrices/all.py @@ -3,5 +3,4 @@ """ from latin import LatinSquare, LatinSquare_generator from dlxcpp import DLXCPP -from dancing_links import make_dlxwrapper from hadamard_matrix import hadamard_matrix, hadamard_matrix_www diff --git a/src/sage/combinat/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index 0dd93a610f0..9ef01f8f038 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -14,23 +14,15 @@ Dancing Links internal pyx code #***************************************************************************** -from cpython.list cimport * -from cpython.int cimport * -from cpython.ref cimport * +include 'sage/ext/interrupt.pxi' -cdef extern from "dancing_links_c.h": - ctypedef struct vector_int "std::vector": - void (* push_back)(int elem) - void clear() - int at(size_t loc) - int size() - - ctypedef struct vector_vector_int "std::vector >": - void (* push_back)(vector_int elem) +from libcpp.vector cimport vector +cdef extern from "dancing_links_c.h": ctypedef struct dancing_links: - vector_int solution - void add_rows(vector_vector_int rows) + vector[int] solution + int number_of_columns() + void add_rows(vector[vector[int]] rows) int search() void freemem() @@ -38,86 +30,92 @@ cdef extern from "ccobject.h": dancing_links* dancing_links_construct "Construct"(void *mem) void dancing_links_destruct "Destruct"(dancing_links *mem) - -from sage.rings.integer cimport Integer - cdef class dancing_linksWrapper: - cdef dancing_links x - cdef object rows + cdef dancing_links _x + cdef _rows def __init__(self, rows): """ - Initialize our wrapper (self.x) as an actual C++ object. + Initialize our wrapper (self._x) as an actual C++ object. We must pass a list of rows at start up. There are no methods for resetting the list of rows, so this class acts as a one-time executor of the C++ code. - TESTS: - sage: rows = [[0,1,2], [1, 2]] - sage: x = make_dlxwrapper(dumps(rows)) - sage: loads(x.__reduce__()[1][0]) - [[0, 1, 2], [1, 2]] + TESTS:: + sage: rows = [[0,1,2], [1, 2]] + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: x = dlx_solver(rows) + sage: x + Dancing links solver for 3 columns and 2 rows + sage: type(x) + """ - pass + self._init_rows(rows) - # Note that the parameters to __cinit__, if any, must be identical to __init__ - # This is due to the way Python constructs class instance. - def __cinit__(self, rows): - self.rows = PyList_New(len(rows)) - dancing_links_construct(&self.x) - if rows: - self.add_rows(rows) + def __cinit__(self): + dancing_links_construct(&self._x) def __dealloc__(self): - self.x.freemem() - dancing_links_destruct(&self.x) + self._x.freemem() + dancing_links_destruct(&self._x) - def __str__(self): + def __repr__(self): """ The string representation of this wrapper is just the list of rows as supplied at startup. - TESTS: - sage: rows = [[0,1,2]] - sage: print make_dlxwrapper(dumps(rows)).__str__() - [[0, 1, 2]] + TESTS:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [1,2], [0]] + sage: dlx_solver(rows) + Dancing links solver for 3 columns and 3 rows """ + return "Dancing links solver for {} columns and {} rows".format( + self._x.number_of_columns(), + len(self._rows)) + + def rows(self): + r""" + Return the list of rows. - return self.rows.__str__() + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [1,2], [0]] + sage: x = dlx_solver(rows) + sage: x.rows() + [[0, 1, 2], [1, 2], [0]] + """ + return self._rows def __reduce__(self): """ This is used when pickling. - TESTS: + TESTS:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] - sage: x = make_dlxwrapper(dumps(rows)) - sage: loads(x.__reduce__()[1][0]) - [[0, 1, 2]] + sage: X = dlx_solver(rows) + sage: X == loads(dumps(X)) + 1 + sage: rows += [[2]] + sage: Y = dlx_solver(rows) + sage: Y == loads(dumps(X)) + 0 """ - # A comment from sage/rings/integer.pyx: - - # This single line below took me HOURS to figure out. - # It is the *trick* needed to pickle Cython extension types. - # The trick is that you must put a pure Python function - # as the first argument, and that function must return - # the result of unpickling with the argument in the second - # tuple as input. All kinds of problems happen - # if we don't do this. - #return sage.rings.integer.make_integer, (self.str(32),) - - import sage.combinat.matrices.dancing_links - from sage.all import dumps - return sage.combinat.matrices.dancing_links.make_dlxwrapper, (dumps(self.rows),) + return type(self), (self._rows,) def __richcmp__(dancing_linksWrapper left, dancing_linksWrapper right, int op): """ Two dancing_linksWrapper objects are equal if they were initialised using the same row list. - TESTS: + TESTS:: + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] sage: X = dlx_solver(rows) @@ -131,7 +129,7 @@ cdef class dancing_linksWrapper: """ cdef int equal - equal = left.rows == right.rows + equal = left._rows == right._rows if op == 2: # == return equal @@ -140,29 +138,16 @@ cdef class dancing_linksWrapper: else: return NotImplemented - def dumps(self): - """ - TESTS: - sage: from sage.combinat.matrices.dancing_links import dlx_solver - sage: rows = [[0,1,2]] - sage: X = dlx_solver(rows) - sage: X == loads(dumps(X)) - 1 - sage: rows += [[2]] - sage: Y = dlx_solver(rows) - sage: Y == loads(dumps(X)) - 0 - """ - return self.rows.dumps() - - def add_rows(self, rows): + def _init_rows(self, rows): """ Initialize our instance of dancing_links with the given rows. - This is for internal use by dlx_solver. - This doctest tests add_rows vicariously! + This is for internal use by dlx_solver only. TESTS: + + This doctest tests ``_init_rows`` vicariously! :: + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] sage: rows+= [[0,2]] @@ -173,7 +158,7 @@ cdef class dancing_linksWrapper: 1 The following example would crash in Sage's debug version - from :trac:`13864` prior to the fix from :trac:`13822`:: + from :trac:`13864` prior to the fix from :trac:`13882`:: sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: x = dlx_solver([]) # indirect doctest @@ -181,35 +166,31 @@ cdef class dancing_linksWrapper: [] """ - if not rows: - return + cdef vector[int] v + cdef vector[vector[int]] vv - cdef vector_int v - cdef vector_vector_int vv + self._rows = [row for row in rows] - cdef int i = 0 - - for row in rows: + for row in self._rows: v.clear() - Py_INCREF(row); - PyList_SET_ITEM(self.rows, i, row) - i += 1 - for x in row: v.push_back(x) vv.push_back(v) - self.x.add_rows(vv) + sig_on() + self._x.add_rows(vv) + sig_off() def get_solution(self): """ After calling search(), we can extract a solution - from the instance variable self.x.solution, a C++ vector + from the instance variable self._x.solution, a C++ vector listing the rows that make up the current solution. - TESTS: + TESTS:: + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] sage: rows+= [[0,2]] @@ -221,16 +202,18 @@ cdef class dancing_linksWrapper: sage: print x.get_solution() [3, 0] """ + cdef size_t i s = [] - for i in range(self.x.solution.size()): - s.append(self.x.solution.at(i)) + for i in range(self._x.solution.size()): + s.append(self._x.solution.at(i)) return s def search(self): """ - TESTS: + EXAMPLES:: + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] sage: rows+= [[0,2]] @@ -241,17 +224,38 @@ cdef class dancing_linksWrapper: 1 sage: print x.get_solution() [3, 0] - """ - x = self.x.search() + TESTS: + + Test that :trac:`11814` is fixed:: + sage: dlx_solver([]).search() + 0 + sage: dlx_solver([[]]).search() + 0 + + If search is called once too often, it keeps returning 0:: + + sage: x = dlx_solver([[0]]) + sage: x.search() + 1 + sage: x.search() + 0 + sage: x.search() + 0 + """ + sig_on() + x = self._x.search() + sig_off() return x + def dlx_solver(rows): """ Internal-use wrapper for the dancing links C++ code. - EXAMPLES: + EXAMPLES:: + sage: from sage.combinat.matrices.dancing_links import dlx_solver sage: rows = [[0,1,2]] sage: rows+= [[0,2]] @@ -269,31 +273,24 @@ def dlx_solver(rows): sage: print x.search() 0 """ - - cdef dancing_linksWrapper dlw - - dlw = dancing_linksWrapper(rows) - #dlw.add_rows(rows) - - return dlw + return dancing_linksWrapper(rows) def make_dlxwrapper(s): """ Create a dlx wrapper from a Python *string* s. - This is used in unpickling. We expect s to be dumps(rows) where - rows is the list of rows used to instantiate the object. - TESTS: + This was historically used in unpickling and is kept for backwards + compatibility. We expect s to be ``dumps(rows)`` where rows is the + list of rows used to instantiate the object. + + TESTS:: + + sage: from sage.combinat.matrices.dancing_links import make_dlxwrapper sage: rows = [[0,1,2]] sage: x = make_dlxwrapper(dumps(rows)) sage: print x.__str__() - [[0, 1, 2]] + Dancing links solver for 3 columns and 1 rows """ - from sage.all import loads - - cdef dancing_linksWrapper dlw - dlw = dancing_linksWrapper(loads(s)) - return dlw - + return dancing_linksWrapper(loads(s)) diff --git a/src/sage/combinat/matrices/dancing_links_c.h b/src/sage/combinat/matrices/dancing_links_c.h index 9f6e5da4ced..b6d9e8cc899 100644 --- a/src/sage/combinat/matrices/dancing_links_c.h +++ b/src/sage/combinat/matrices/dancing_links_c.h @@ -253,6 +253,10 @@ class dancing_links { best_col = NULL; } + int number_of_columns() { + return nr_columns; + } + void add_rows(vector > rows) { assert(nr_columns == -1); @@ -265,6 +269,10 @@ class dancing_links { nr_columns++; + if (nr_columns == 0) { + return; + } + setup_columns(); // Add each row @@ -277,7 +285,13 @@ class dancing_links { bool search() { - assert(nr_columns > 0); + if (nr_columns <= 0) { + return false; + } + + if (mode == SEARCH_DONE) { + return false; + } // If current_node or best_col have changed from being NULL // then we must have already found a solution and we are diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 5923e2a7210..2858c48519e 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -1,5 +1,8 @@ r""" Hasse diagrams of posets + +{INDEX_OF_FUNCTIONS} + """ #***************************************************************************** # Copyright (C) 2008 Peter Jipsen , @@ -58,6 +61,8 @@ def _repr_(self): def linear_extension(self): r""" + Return a linear extension + TESTS:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -70,6 +75,8 @@ def linear_extension(self): def linear_extensions(self): r""" + Return all linear extensions + TESTS:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -81,6 +88,8 @@ def linear_extensions(self): def is_linear_extension(self,lin_ext=None): r""" + Test if an ordering is a linear extension. + TESTS:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -103,6 +112,8 @@ def is_linear_extension(self,lin_ext=None): def cover_relations_iterator(self): r""" + Iterate over cover relations. + TESTS:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -115,6 +126,8 @@ def cover_relations_iterator(self): def cover_relations(self): r""" + Return the list of cover relations. + TESTS:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -1525,3 +1538,7 @@ def chains(self, element_class=list, exclude=None): return PairwiseCompatibleSubsets(vertices, self.are_comparable, element_class = element_class) + +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/posets.py b/src/sage/combinat/posets/posets.py index 047af78e04a..7224f4c41d7 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -43,13 +43,13 @@ :widths: 30, 70 :delim: | - :meth:`~FinitePoset.covers` | Return ``True`` if y covers x and False otherwise. - :meth:`~FinitePoset.lower_covers` | Return a list of lower covers of the element y. - :meth:`~FinitePoset.upper_covers` | Return a list of upper covers of the element y. An upper cover of y is an element x such that y x is a cover relation. - :meth:`~FinitePoset.cover_relations` | Return the list of pairs `[u,v]` which are cover relations. - :meth:`~FinitePoset.lower_covers_iterator` | Return an iterator for the lower covers of the element y. An lower cover of y is an element x such that y x is a cover relation. - :meth:`~FinitePoset.upper_covers_iterator` | Return an iterator for the upper covers of the element y. An upper cover of y is an element x such that y x is a cover relation. - :meth:`~FinitePoset.cover_relations_iterator` | Return an iterator for the cover relations of the poset. + :meth:`~FinitePoset.covers` | Return ``True`` if ``y`` covers ``x``. + :meth:`~FinitePoset.lower_covers` | Return elements covered by given element. + :meth:`~FinitePoset.upper_covers` | Return elements covering given element. + :meth:`~FinitePoset.cover_relations` | Return the list of cover relations. + :meth:`~FinitePoset.lower_covers_iterator` | Return an iterator over elements covered by given element. + :meth:`~FinitePoset.upper_covers_iterator` | Return an iterator over elements covering given element. + :meth:`~FinitePoset.cover_relations_iterator` | Return an iterator over cover relations of the poset. **Properties of the poset** @@ -1764,14 +1764,13 @@ def level_sets(self): def cover_relations(self): """ - Returns the list of pairs [u,v] of elements of the poset such that - u v is a cover relation (that is, u v and there does not exist z - such that u z v). + Return the list of pairs ``[x, y]`` of elements of the poset such + that ``y`` covers ``x``. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: Q.cover_relations() + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.cover_relations() [[1, 2], [0, 2], [2, 3], [3, 4]] """ return [c for c in self.cover_relations_iterator()] @@ -1805,14 +1804,14 @@ def cover_relations_graph(self): def cover_relations_iterator(self): """ - Returns an iterator for the cover relations of the poset. + Return an iterator over the cover relations of the poset. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: type(Q.cover_relations_iterator()) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: type(P.cover_relations_iterator()) - sage: [z for z in Q.cover_relations_iterator()] + sage: [z for z in P.cover_relations_iterator()] [[1, 2], [0, 2], [2, 3], [3, 4]] """ for u,v,l in self._hasse_diagram.edge_iterator(): @@ -2900,73 +2899,89 @@ def is_graded(self): rank = rf(maxes[0]) return all(rf(i) == rank for i in maxes) - def covers(self,x,y): + def covers(self, x, y): """ - Returns True if y covers x and False otherwise. + Return ``True`` if ``y`` covers ``x`` and ``False`` otherwise. + + Element `y` covers `x` if `x < y` and there is no `z` such that + `x < z < y`. EXAMPLES:: - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.covers(Q(1),Q(6)) + sage: P = Poset([[1,5], [2,6], [3], [4], [], [6,3], [4]]) + sage: P.covers(1, 6) True - sage: Q.covers(Q(1),Q(4)) + sage: P.covers(1, 4) + False + sage: P.covers(1, 5) False """ return self._hasse_diagram.has_edge(*[self._element_to_vertex(_) for _ in (x,y)]) - def upper_covers_iterator(self,y): + def upper_covers_iterator(self, x): """ - Returns an iterator for the upper covers of the element y. An upper - cover of y is an element x such that y x is a cover relation. + Return an iterator over the upper covers of the element ``x``. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: type(Q.upper_covers_iterator(0)) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[]}) + sage: type(P.upper_covers_iterator(0)) """ - for x in self._hasse_diagram.neighbor_out_iterator(self._element_to_vertex(y)): - yield self._vertex_to_element(x) + for e in self._hasse_diagram.neighbor_out_iterator(self._element_to_vertex(x)): + yield self._vertex_to_element(e) - def upper_covers(self,y): + def upper_covers(self, x): """ - Returns a list of upper covers of the element y. An upper cover of - y is an element x such that y x is a cover relation. + Return the list of upper covers of the element ``x``. + + An upper cover of `x` is an element `y` such that `x < y` and + there is no element `z` so that `x < z < y`. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: [Q.upper_covers(_) for _ in Q.list()] - [[2], [2], [3], [4], []] + sage: P = Poset([[1,5], [2,6], [3], [4], [], [6,3], [4]]) + sage: P.upper_covers(1) + [2, 6] + + .. SEEALSO:: :meth:`lower_covers` """ - return [x for x in self.upper_covers_iterator(y)] + return [e for e in self.upper_covers_iterator(x)] - def lower_covers_iterator(self,y): + def lower_covers_iterator(self, x): """ - Returns an iterator for the lower covers of the element y. An lower - cover of y is an element x such that y x is a cover relation. + Return an iterator over the lower covers of the element ``x``. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: type(Q.lower_covers_iterator(0)) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[]}) + sage: l0 = P.lower_covers_iterator(3) + sage: type(l0) + sage: l0.next() + 2 """ - for x in self._hasse_diagram.neighbor_in_iterator(self._element_to_vertex(y)): - yield self._vertex_to_element(x) + for e in self._hasse_diagram.neighbor_in_iterator(self._element_to_vertex(x)): + yield self._vertex_to_element(e) - def lower_covers(self,y): + def lower_covers(self, x): """ - Returns a list of lower covers of the element y. An lower cover of - y is an element x such that y x is a cover relation. + Return the list of lower covers of the element ``x``. + + A lower cover of `x` is an element `y` such that `y < x` and + there is no element `z` so that `y < z < x`. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: [Q.lower_covers(_) for _ in Q.list()] - [[], [], [1, 0], [2], [3]] + sage: P = Poset([[1,5], [2,6], [3], [4], [], [6,3], [4]]) + sage: P.lower_covers(3) + [2, 5] + sage: P.lower_covers(0) + [] + + .. SEEALSO:: :meth:`upper_covers` """ - return [x for x in self.lower_covers_iterator(y)] + return [e for e in self.lower_covers_iterator(x)] def cardinality(self): """ @@ -4928,7 +4943,10 @@ def flag_h_polynomial(self): def characteristic_polynomial(self): r""" - Return the characteristic polynomial of a graded poset ``self``. + Return the characteristic polynomial of the poset. + + The poset is expected to be graded and have a bottom + element. If `P` is a graded poset with rank `n` and a unique minimal element `\hat{0}`, then the characteristic polynomial of @@ -4957,8 +4975,10 @@ def characteristic_polynomial(self): """ hasse = self._hasse_diagram rk = hasse.rank_function() - if rk is None: - raise TypeError('the poset should be ranked') + if not self.is_graded(): + raise TypeError('the poset should be graded') + if not self.has_bottom(): + raise TypeError('the poset should have a bottom element') n = rk(hasse.maximal_elements()[0]) x0 = hasse.minimal_elements()[0] q = polygen(ZZ, 'q') diff --git a/src/sage/combinat/rigged_configurations/bij_infinity.py b/src/sage/combinat/rigged_configurations/bij_infinity.py index 0e8d8185eeb..eef0e31695e 100644 --- a/src/sage/combinat/rigged_configurations/bij_infinity.py +++ b/src/sage/combinat/rigged_configurations/bij_infinity.py @@ -217,7 +217,7 @@ def run(self): sage: RC = crystals.infinity.RiggedConfigurations(['B',4]) sage: T = crystals.infinity.Tableaux(['B',4]) sage: Psi = T.crystal_morphism({T.module_generators[0]: RC.module_generators[0]}) - sage: TS = T.subcrystal(max_depth=4) + sage: TS = [x.value for x in T.subcrystal(max_depth=4)] sage: all(Psi(b) == RC(b) for b in TS) # long time # indirect doctest True """ @@ -254,7 +254,7 @@ def run(self): sage: RC = crystals.infinity.RiggedConfigurations(['B',4]) sage: T = crystals.infinity.Tableaux(['B',4]) sage: Psi = RC.crystal_morphism({RC.module_generators[0]: T.module_generators[0]}) - sage: RCS = RC.subcrystal(max_depth=4) + sage: RCS = [x.value for x in RC.subcrystal(max_depth=4)] sage: all(Psi(nu) == T(nu) for nu in RCS) # long time # indirect doctest True """ @@ -292,7 +292,7 @@ def run(self): sage: RC = crystals.infinity.RiggedConfigurations(['D',4]) sage: T = crystals.infinity.Tableaux(['D',4]) sage: Psi = T.crystal_morphism({T.module_generators[0]: RC.module_generators[0]}) - sage: TS = T.subcrystal(max_depth=4) + sage: TS = [x.value for x in T.subcrystal(max_depth=4)] sage: all(Psi(b) == RC(b) for b in TS) # long time # indirect doctest True """ @@ -331,7 +331,7 @@ def run(self): sage: RC = crystals.infinity.RiggedConfigurations(['D',4]) sage: T = crystals.infinity.Tableaux(['D',4]) sage: Psi = RC.crystal_morphism({RC.module_generators[0]: T.module_generators[0]}) - sage: RCS = RC.subcrystal(max_depth=4) + sage: RCS = [x.value for x in RC.subcrystal(max_depth=4)] sage: all(Psi(nu) == T(nu) for nu in RCS) # long time # indirect doctest True """ diff --git a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py index a1729766f5c..780ade27b54 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py +++ b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py @@ -1873,6 +1873,19 @@ def complement_rigging(self, reverse_factors=False): ....: c = mg.complement_rigging(True) ....: hwc = c.to_tensor_product_of_kirillov_reshetikhin_tableaux() ....: assert hw == hwc + + TESTS: + + We check that :trac:`18898` is fixed:: + + sage: RC = RiggedConfigurations(['D',4,1], [[2,1], [2,1], [2,3]]) + sage: x = RC(partition_list=[[1], [1,1], [1], [1]], rigging_list=[[0], [2,1], [0], [0]]) + sage: ascii_art(x) + 0[ ]0 2[ ]2 0[ ]0 0[ ]0 + 2[ ]1 + sage: ascii_art(x.complement_rigging()) + 0[ ]0 2[ ]1 0[ ]0 0[ ]0 + 2[ ]0 """ P = self.parent() if reverse_factors: @@ -1885,7 +1898,15 @@ def complement_rigging(self, reverse_factors=False): for a,p in enumerate(mg): nu.append(list(p)) vac_nums = mg.get_vacancy_numbers(a+1) - rig.append( [vac - p.rigging[i] for i,vac in enumerate(vac_nums)] ) + riggings = [vac - p.rigging[i] for i,vac in enumerate(vac_nums)] + block = 0 + for j,i in enumerate(p): + if p[block] != i: + riggings[block:j] = sorted(riggings[block:j], reverse=True) + block = j + riggings[block:] = sorted(riggings[block:], reverse=True) + rig.append(riggings) + rc = P(partition_list=nu, rigging_list=rig) return rc.f_string(reversed(e_str)) diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 58e262d7a1c..0f96b9bc1ca 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -452,6 +452,25 @@ def cartan_type(self): return self return self._cartan_type + def subtype(self, index_set): + """ + Return a subtype of ``self`` given by ``index_set``. + + A subtype can be considered the Dynkin diagram induced from + the Dynkin diagram of ``self`` by ``index_set``. + + EXAMPLES:: + + sage: C = CartanMatrix(['F',4]) + sage: C.subtype([1,2,3]) + [ 2 -1 0] + [-1 2 -1] + [ 0 -2 2] + """ + ind = self.index_set() + I = [ind.index(i) for i in index_set] + return CartanMatrix(self.matrix_from_rows_and_columns(I, I)) + def rank(self): r""" Return the rank of ``self``. diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index e9a791b3657..ad25dff373a 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -1143,6 +1143,25 @@ def relabel(self, relabelling): import type_relabel return type_relabel.CartanType(self, relabelling) + def subtype(self, index_set): + """ + Return a subtype of ``self`` given by ``index_set``. + + A subtype can be considered the Dynkin diagram induced from + the Dynkin diagram of ``self`` by ``index_set``. + + EXAMPLES:: + + sage: ct = CartanType(['A',6,2]) + sage: ct.dynkin_diagram() + O=<=O---O=<=O + 0 1 2 3 + BC3~ + sage: ct.subtype([1,2,3]) + ['C', 3] + """ + return self.cartan_matrix().subtype(index_set).cartan_type() + def marked_nodes(self, marked_nodes): """ Return a Cartan type with the nodes ``marked_nodes`` marked. diff --git a/src/sage/combinat/root_system/dynkin_diagram.py b/src/sage/combinat/root_system/dynkin_diagram.py index 2ea91bd0f96..d3fdd03aaf1 100644 --- a/src/sage/combinat/root_system/dynkin_diagram.py +++ b/src/sage/combinat/root_system/dynkin_diagram.py @@ -550,6 +550,26 @@ def relabel(self, relabelling, inplace=False, **kwds): G._cartan_type = self._cartan_type.relabel(relabelling) return G + def subtype(self, index_set): + """ + Return a subtype of ``self`` given by ``index_set``. + + A subtype can be considered the Dynkin diagram induced from + the Dynkin diagram of ``self`` by ``index_set``. + + EXAMPLES:: + + sage: D = DynkinDiagram(['A',6,2]); D + O=<=O---O=<=O + 0 1 2 3 + BC3~ + sage: D.subtype([1,2,3]) + O---O=<=O + 1 2 3 + C3 + """ + return self.cartan_matrix().subtype(index_set).dynkin_diagram() + def is_finite(self): """ Check if ``self`` corresponds to a finite root system. diff --git a/src/sage/combinat/root_system/integrable_representations.py b/src/sage/combinat/root_system/integrable_representations.py index 56fb4d8af11..c15ebff7444 100644 --- a/src/sage/combinat/root_system/integrable_representations.py +++ b/src/sage/combinat/root_system/integrable_representations.py @@ -1037,7 +1037,7 @@ def next_level(x): mc = P(self.to_weight(x[0])).monomial_coefficients() contr = sum(fw[sequence[j]]*mc.get(j,0) for j in self._index_set if j != i).coerce_to_sl() - if ldict.has_key(contr): + if contr in ldict: ldict[contr] += x[1] else: ldict[contr] = x[1] diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 8670bfb430c..5429e7bcd90 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -619,7 +619,7 @@ def short_roots(self): """ if not self.cartan_type().is_finite(): raise NotImplementedError("only implemented for finite Cartan types") - return filter(lambda x: x.is_short_root(), self.roots()) + return [x for x in self.roots() if x.is_short_root()] def long_roots(self): """ @@ -638,7 +638,7 @@ def long_roots(self): """ if not self.cartan_type().is_finite(): raise NotImplementedError("only implemented for finite Cartan types") - return filter(lambda x: x.is_long_root(), self.roots()) + return [x for x in self.roots() if x.is_long_root()] @cached_method def positive_roots(self, index_set=None): diff --git a/src/sage/combinat/tiling.py b/src/sage/combinat/tiling.py index 4e88951cd1e..21201c27789 100644 --- a/src/sage/combinat/tiling.py +++ b/src/sage/combinat/tiling.py @@ -1076,17 +1076,6 @@ def is_suitable(self): If these conditions are not verified, then the problem is not suitable in the sense that there are no solution. - .. NOTE:: - - The DLX solver throws a Segmentation Fault when the - number of rows is zero:: - - sage: from sage.combinat.matrices.dancing_links import dlx_solver - sage: rows = [] - sage: x = dlx_solver(rows) - sage: x.search() # not tested - BOOM !!! - EXAMPLES:: sage: from sage.combinat.tiling import TilingSolver, Polyomino @@ -1320,7 +1309,7 @@ def dlx_solver(self): sage: T = TilingSolver([p,q,r], box=(1,1,6)) sage: x = T.dlx_solver() sage: x - + Dancing links solver for 9 columns and 15 rows """ from sage.combinat.matrices.dancing_links import dlx_solver rows = self.rows() diff --git a/src/sage/dev/sagedev.py b/src/sage/dev/sagedev.py index 3e97b82d197..be0c2d51928 100644 --- a/src/sage/dev/sagedev.py +++ b/src/sage/dev/sagedev.py @@ -44,6 +44,8 @@ r'^(?!.*/\.)(?!.*\.\.)(?!/)(?!.*//)(?!.*@\{)(?!.*\\)' r'[^\040\177 ~^:?*[]+(? Auto-merging alices_file - CONFLICT (add/add): Merge conflict in alices_file - - Please edit the affected files to resolve the conflicts. When you are finished, - your resolution will be commited. + CONFLICT (add/add): Merge conflict in alices_file... + Please edit the affected files to resolve the conflicts... Finished? [ok/Abort] abort Undo the latest commit by alice, so we can pull again:: @@ -3130,8 +3130,7 @@ def merge(self, ticket_or_branch=MASTER_BRANCH, pull=None, create_dependency=Non Automatic merge failed, there are conflicting commits. Auto-merging alice2 - CONFLICT (add/add): Merge conflict in alice2 - + CONFLICT (add/add): Merge conflict in alice2... Please edit the affected files to resolve the conflicts. When you are finished, your resolution will be commited. Finished? [ok/Abort] abort @@ -3166,6 +3165,26 @@ def merge(self, ticket_or_branch=MASTER_BRANCH, pull=None, create_dependency=Non Cannot merge because working directory is not in a clean state. # (use "sage --dev commit" to commit your changes) + + Merging a ticket whose branch field points to a commit checks whether + the commit is present in the current branch:: + + sage: alice.trac.set_attributes(1, branch='12345678') + sage: alice._UI.append("discard") + sage: alice.merge(ticket_or_branch=1, pull=True) + The following files in your working directory contain uncommitted changes: + + alice2 + + Discard changes? [discard/Cancel/stash] discard + # It seems that the branch for #1 has already been merged in the latest version of sage. The branch field on #1 points to the commit "12345678". + Failed to determine whether commit "12345678" is present in the current branch "ticket/2". Is your "master" branch up to date? + sage: sha = alice.git.show_ref('master',hash=True)[:8] + sage: alice.trac.set_attributes(1, branch=sha) + sage: alice.merge(ticket_or_branch=1, pull=True) + # It seems that the branch for #1 has already been merged in the latest version of sage. The branch field on #1 points to the commit "...". + Your branch "ticket/2" already contains commit "...". Nothing to merge. + """ try: self.reset_to_clean_state() @@ -3247,9 +3266,34 @@ def merge(self, ticket_or_branch=MASTER_BRANCH, pull=None, create_dependency=Non if pull: assert remote_branch if not self._is_remote_branch_name(remote_branch, exists=True): + if ticket and self._is_commit_name(remote_branch): + commit = remote_branch + self._UI.info('It seems that the branch for #{0} has already been merged in the latest version of sage. The branch field on #{0} points to the commit "{1}".', ticket, commit) + from git_error import GitError + try: + branches = self.git.branch(contains=commit) + except GitError as e: + self._UI.error('Failed to determine whether commit "{0}" is present in the current branch "{1}". Is your "{2}" branch up to date?', commit, current_branch, MASTER_BRANCH) + raise OperationCancelledError("unknown commit") + present_in_master = False + for present_in_branch in branches.split(): + present_in_branch = present_in_branch.split()[-1] + if current_branch == present_in_branch: + self._UI.show('Your branch "{0}" already contains commit "{1}". Nothing to merge.', current_branch, commit) + return + if MASTER_BRANCH == present_in_branch: + present_in_master = True + self._UI.show('Your branch "{0}" does not contain commit "{1}". Merging of remote commits is not implemented. You need to merge manually.', current_branch, commit) + if present_in_master: + self._UI.info('Merge "{0}" into your branch "{1}" with "{2}".', MASTER_BRANCH, current_branch, self._format_command("merge")) + else: + self._UI.info('Your branch "{0}" seems to be out of date. Pull the latest changes to "{0}" with "{1}" and "{2}". Then come back to this branch and merge "{0}" with "{3}" and "{4}".', MASTER_BRANCH, self._format_command("checkout", branch=MASTER_BRANCH), self._format_command("pull"), self._format_command("checkout", branch=current_branch), self._format_command("merge")) + raise OperationCancelledError("Remote commit merge not implemented.") + self._UI.error('Can not merge remote branch "{0}". It does not exist.', remote_branch) raise OperationCancelledError("no such branch") + self._UI.show('Merging the remote branch "{0}" into the local branch "{1}".', remote_branch, current_branch) self.git.super_silent.fetch(self.git._repository_anonymous, remote_branch) @@ -4366,6 +4410,33 @@ def _is_local_branch_name(self, name, exists=any): else: raise ValueError("exists") + def _is_commit_name(self, name): + r""" + Return whether ``name`` is a valid name for a commit. + + INPUT: + + - ``name`` -- a string + + TESTS:: + + sage: from sage.dev.test.sagedev import single_user_setup + sage: dev, config, UI, server = single_user_setup() + sage: dev = dev._sagedev + + sage: dev._is_commit_name('') + False + sage: dev._is_commit_name('ticket/1') + False + sage: dev._is_commit_name('12345a78') + True + + """ + if not isinstance(name, str): + raise ValueError("name must be a string") + + return bool(GIT_COMMIT_REGEX.match(name)) + def _is_trash_name(self, name, exists=any): r""" Return whether ``name`` is a valid name for an abandoned branch. diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index aac97a8c4db..423964b3049 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -702,7 +702,7 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): ....: FDS = FileDocTestSource(filename, DocTestDefaults(long=True,optional=True)) ....: FDS._test_enough_doctests(verbose=False) There are 7 tests in sage/combinat/dyck_word.py that are not being run - There are 4 tests in sage/combinat/finite_state_machine.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 There are 18 tests in sage/combinat/partition.py that are not being run There are 15 tests in sage/combinat/permutation.py that are not being run diff --git a/src/sage/doctest/test.py b/src/sage/doctest/test.py index be1d0cbdd83..4136be86b09 100644 --- a/src/sage/doctest/test.py +++ b/src/sage/doctest/test.py @@ -331,7 +331,7 @@ exec gdb ... Running doctests... Doctesting 1 file. - sage -t ... 1second.rst + sage -t... 1second.rst [2 tests, ... s] ---------------------------------------------------------------------- All tests passed! diff --git a/src/sage/ext/memory_allocator.pxd b/src/sage/ext/memory_allocator.pxd new file mode 100644 index 00000000000..bea992477d9 --- /dev/null +++ b/src/sage/ext/memory_allocator.pxd @@ -0,0 +1,14 @@ +cimport cython + +@cython.final +cdef class MemoryAllocator: + cdef size_t n + cdef size_t size + cdef void ** pointers + cdef void * static_pointers[16] # If n <= 16, store pointers here + + cdef void * malloc(self, size_t size) except? NULL + cdef void * calloc(self, size_t nmemb, size_t size) except? NULL + cdef void * allocarray(self, size_t nmemb, size_t size) except? NULL + cdef int resize(self, size_t new_size) except -1 + cdef inline int enlarge_if_needed(self) except -1 diff --git a/src/sage/ext/memory_allocator.pyx b/src/sage/ext/memory_allocator.pyx new file mode 100644 index 00000000000..3b04813fa43 --- /dev/null +++ b/src/sage/ext/memory_allocator.pyx @@ -0,0 +1,109 @@ +from sage.ext.memory cimport * +include 'sage/ext/interrupt.pxi' + +cdef class MemoryAllocator: + r""" + An object for memory allocation, whose resources are freed upon + ``__dealloc__``. + + EXAMPLES:: + + sage: cython( + ....: ''' + ....: from sage.ext.memory_allocator cimport MemoryAllocator + ....: cdef MemoryAllocator mem = MemoryAllocator() + ....: for n in range(100): + ....: mem.malloc(n) + ....: mem.calloc(n, n) + ....: mem.allocarray(n, n) + ....: ''') + """ + def __cinit__(self): + """ + EXAMPLES:: + + sage: cython( + ....: ''' + ....: from sage.ext.memory_allocator cimport MemoryAllocator + ....: cdef MemoryAllocator mem = MemoryAllocator.__new__(MemoryAllocator) + ....: mem.malloc(10000) + ....: print(mem.n) + ....: print(mem.size) + ....: ''') + 1 + 16 + """ + self.n = 0 + self.size = 16 + self.pointers = self.static_pointers + + cdef int resize(self, size_t new_size) except -1: + r""" + Resize the list of pointers to contain ``new_size`` elements. + + It is required that ``new_size`` is at least ``self.n``, but + this condition is not checked. + """ + cdef size_t i + if self.pointers == self.static_pointers: + # Case 1: allocate pointers for the first time + self.pointers = check_allocarray(new_size, sizeof(void*)) + for i in range(self.n): + self.pointers[i] = self.static_pointers[i] + else: + # Case 2: resize pointers + self.pointers = check_reallocarray(self.pointers, new_size, sizeof(void*)) + self.size = new_size + + cdef inline int enlarge_if_needed(self) except -1: + r""" + Enlarge the list of pointers if needed such that there is at + least one free entry. + """ + if unlikely(self.n >= self.size): + return self.resize(self.size * 2) + + cdef void * malloc(self, size_t size) except? NULL: + r""" + Returns a new pointer and stores it to be automatically freed later. + """ + self.enlarge_if_needed() + cdef void * val = check_malloc(size) + self.pointers[self.n] = val + self.n += 1 + return val + + cdef void * calloc(self, size_t nmemb, size_t size) except? NULL: + r""" + Returns a new pointer and stores it to be automatically freed later. + """ + self.enlarge_if_needed() + cdef void * val = check_calloc(nmemb, size) + self.pointers[self.n] = val + self.n += 1 + return val + + cdef void * allocarray(self, size_t nmemb, size_t size) except? NULL: + r""" + Returns a new pointer and stores it to be automatically freed later. + """ + self.enlarge_if_needed() + cdef void * val = check_allocarray(nmemb, size) + self.pointers[self.n] = val + self.n += 1 + return val + + def __dealloc__(self): + r""" + Free the allocated resources + + EXAMPLES:: + + sage: from sage.ext.memory_allocator import MemoryAllocator + sage: _ = MemoryAllocator() + """ + cdef size_t i + for i in range(self.n): + sage_free(self.pointers[i]) + if self.pointers != self.static_pointers: + sage_free(self.pointers) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index 104d5e53e08..68fbb7387a1 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -156,6 +156,7 @@ from sage.libs.mpmath import utils as mpmath_utils from sage.symbolic.expression import Expression from sage.calculus.functional import derivative +from functools import reduce def rational_param_as_tuple(x): diff --git a/src/sage/functions/prime_pi.pyx b/src/sage/functions/prime_pi.pyx index b4b94a4b159..96207f36ff2 100644 --- a/src/sage/functions/prime_pi.pyx +++ b/src/sage/functions/prime_pi.pyx @@ -30,7 +30,7 @@ EXAMPLES:: include 'sage/ext/stdsage.pxi' include 'sage/ext/interrupt.pxi' -include 'sage/libs/pari/decl.pxi' # also declares diffptr (as extern char* [sic!]) +from sage.libs.pari.paridecl cimport * from libc.stdint cimport int_fast8_t, uint_fast16_t, uint8_t, uint32_t, uint64_t from sage.rings.integer cimport Integer @@ -38,17 +38,6 @@ from sage.libs.pari.all import pari from sage.symbolic.function cimport BuiltinFunction from sage.libs.gmp.mpz cimport * -cdef extern from "pari/pari.h": - cdef void NEXT_PRIME_VIADIFF(uint32_t, uint8_t *) - # Note that this is a generic (i.e. polymorphic) and somewhat ill-defined - # PARI macro (as it uses some kind of "call by name"), whose true signature - # cannot be expressed by a C prototype declaration, since its first para- - # meter can be of any (integral) number type, and both parameters are - # effectively passed *by reference* (i.e., get altered). - # We here use uint32_t for the first argument, while in effect a reference - # to a uint32_t is passed. Declaring it more correctly with uint32_t* as - # the type of its first parameter (and uint8_t** as the type of the second) - # would require changing the macro definition. cdef uint64_t arg_to_uint64(x, str s1, str s2) except -1: if not isinstance(x, Integer): @@ -151,7 +140,7 @@ cdef class PrimePi(BuiltinFunction): cdef uint32_t __numPrimes, __maxSieve, __primeBound cdef int_fast8_t *__tabS cdef uint_fast16_t *__smallPi - cdef uint8_t *__pariPrimePtr + cdef byteptr __pariPrimePtr def __dealloc__(self): if self.__smallPi != NULL: @@ -160,7 +149,7 @@ cdef class PrimePi(BuiltinFunction): cdef void _init_tables(self): pari.init_primes(0xffffu) - self.__pariPrimePtr = diffptr + self.__pariPrimePtr = diffptr self.__smallPi = sage_malloc( 0x10000u * sizeof(uint_fast16_t)) cdef uint32_t p=0u, i=0u, k=0u @@ -327,7 +316,7 @@ cdef class PrimePi(BuiltinFunction): cdef uint32_t *prime cdef uint32_t newNumPrimes, i pari.init_primes(b+1u) - self.__pariPrimePtr = diffptr + self.__pariPrimePtr = diffptr newNumPrimes = self._pi(b, 0ull) if self.__numPrimes: prime = sage_realloc(self.__primes, diff --git a/src/sage/games/quantumino.py b/src/sage/games/quantumino.py index e838733754d..2e0309308a7 100644 --- a/src/sage/games/quantumino.py +++ b/src/sage/games/quantumino.py @@ -150,7 +150,7 @@ 5484 sage: x = T.dlx_solver() # long time (10 s) sage: x # long time (fast) - + Dancing links solver for 96 columns and 5484 rows REFERENCES: diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 0ed787e858f..93b89fa6bde 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -5894,7 +5894,7 @@ def read_palp_matrix(data, permutation=False): if permutation: last_piece = first_line[-1] last_piece = last_piece.split('=') - if last_piece[0] <> 'perm': + if last_piece[0] != 'perm': raise ValueError('PALP did not return a permutation.') p = _palp_convert_permutation(last_piece[1]) return (mat, p) diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 9afd4f32f2f..01e5f4b129a 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -796,14 +796,16 @@ def _rich_repr_(self, display_manager, **kwds): return tp.OutputPlainText(text) def cdd_Hrepresentation(self): - """ + r""" Write the inequalities/equations data of the polyhedron in cdd's H-representation format. - OUTPUT: + .. SEEALSO:: - A string. If you save the output to filename.ine then you can - run the stand-alone cdd via ``cddr+ filename.ine`` + :meth:`write_cdd_Hrepresentation` -- export the polyhedron as a + H-representation to a file. + + OUTPUT: a string EXAMPLES:: @@ -830,17 +832,41 @@ def cdd_Hrepresentation(self): raise TypeError('The base ring must be ZZ, QQ, or RDF') return cdd_Hrepresentation(cdd_type, list(self.inequality_generator()), - list(self.equation_generator()) ) + list(self.equation_generator())) - def cdd_Vrepresentation(self): + def write_cdd_Hrepresentation(self, filename): + r""" + Export the polyhedron as a H-representation to a file. + + INPUT: + + - ``filename`` -- the output file. + + .. SEEALSO:: + + :meth:`cdd_Hrepresentation` -- return the H-representation of the + polyhedron as a string. + + EXAMPLE:: + + sage: from sage.misc.temporary_file import tmp_filename + sage: filename = tmp_filename(ext='.ext') + sage: polytopes.cube().write_cdd_Hrepresentation(filename) """ + with open(filename, 'w') as f: + f.write(self.cdd_Hrepresentation()) + + def cdd_Vrepresentation(self): + r""" Write the vertices/rays/lines data of the polyhedron in cdd's V-representation format. - OUTPUT: + .. SEEALSO:: + + :meth:`write_cdd_Vrepresentation` -- export the polyhedron as a + V-representation to a file. - A string. If you save the output to filename.ext then you can - run the stand-alone cdd via ``cddr+ filename.ext`` + OUTPUT: a string EXAMPLES:: @@ -868,7 +894,29 @@ def cdd_Vrepresentation(self): return cdd_Vrepresentation(cdd_type, list(self.vertex_generator()), list(self.ray_generator()), - list(self.line_generator()) ) + list(self.line_generator())) + + def write_cdd_Vrepresentation(self, filename): + r""" + Export the polyhedron as a V-representation to a file. + + INPUT: + + - ``filename`` -- the output file. + + .. SEEALSO:: + + :meth:`cdd_Vrepresentation` -- return the V-representation of the + polyhedron as a string. + + EXAMPLE:: + + sage: from sage.misc.temporary_file import tmp_filename + sage: filename = tmp_filename(ext='.ext') + sage: polytopes.cube().write_cdd_Vrepresentation(filename) + """ + with open(filename, 'w') as f: + f.write(self.cdd_Vrepresentation()) @cached_method def n_equations(self): @@ -3867,6 +3915,80 @@ def bounding_box(self, integral=False): box_min.append(min_coord) return (tuple(box_min), tuple(box_max)) + def integral_points_count(self,verbose=False): + r""" + Return the number of integral points in the polyhedron. + + This method uses the optional package ``latte_int``. + + INPUT: + + - ``verbose`` (boolean; ``False`` by default) -- whether to display + verbose output. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: P.integral_points_count() # optional - latte_int + 27 + sage: P.integral_points_count(verbose=True) # optional - latte_int + This is LattE integrale... + ... + Total time:... + 27 + + We shrink the polyhedron a little bit:: + + sage: Q = P*(8/9) + sage: Q.integral_points_count() # optional - latte_int + 1 + + This no longer works if the coordinates are not rationals:: + + sage: Q = P*RDF(8/9) + sage: Q.integral_points_count() # optional - latte_int + Traceback (most recent call last): + ... + RuntimeError: LattE integrale failed (exit code 1) to execute... + ...Parse error in CDD-style input file /dev/stdin + sage: Q.integral_points_count(verbose=True) # optional - latte_int + Traceback (most recent call last): + ... + RuntimeError: LattE integrale failed (exit code 1) to execute count --cdd /dev/stdin, see error message above + """ + if self.is_empty(): + return 0 + + from subprocess import Popen, PIPE + from sage.misc.misc import SAGE_TMP + from sage.rings.integer import Integer + + ine = self.cdd_Hrepresentation() + args = ['count', '--cdd', '/dev/stdin'] + + try: + # The cwd argument is needed because latte + # always produces diagnostic output files. + latte_proc = Popen(args, + stdin=PIPE, stdout=PIPE, + 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") + + ans, err = latte_proc.communicate(ine) + ret_code = latte_proc.poll() + if ret_code: + if err is None: + err = ", see error message above" + else: + err = ":\n" + err + raise RuntimeError("LattE integrale failed (exit code {}) to execute {}".format(ret_code, ' '.join(args)) + err.strip()) + + return Integer(ans.splitlines()[-1]) + def integral_points(self, threshold=100000): r""" Return the integral points in the polyhedron. diff --git a/src/sage/geometry/polyhedron/base_ZZ.py b/src/sage/geometry/polyhedron/base_ZZ.py index b65c5e54593..b13c56c65a9 100644 --- a/src/sage/geometry/polyhedron/base_ZZ.py +++ b/src/sage/geometry/polyhedron/base_ZZ.py @@ -14,7 +14,6 @@ from sage.rings.all import ZZ, QQ, gcd from sage.misc.all import cached_method -from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector from constructor import Polyhedron from base import Polyhedron_base @@ -256,27 +255,17 @@ def ehrhart_polynomial(self, verbose=False, dual=None, sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... - RuntimeError: Latte returned 1 when running: - count --ehrhart-polynomial --redundancy-check=none --bim-bam-boum=19 --cdd ... - (see output above) + RuntimeError: LattE integrale failed with exit code 1 to execute... """ - if not self.is_lattice_polytope(): - raise ValueError("this must be a lattice polytope") - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(QQ, 't') if self.is_empty(): return R.zero() - from sage.misc.temporary_file import tmp_filename from sage.misc.misc import SAGE_TMP from subprocess import Popen, PIPE - in_str = self.cdd_Hrepresentation() - in_filename = tmp_filename() + '.ine' - in_file = open(in_filename, 'w') - in_file.write(self.cdd_Hrepresentation()) - in_file.close() + ine = self.cdd_Hrepresentation() args = ['count', '--ehrhart-polynomial'] if 'redundancy_check' not in kwds: @@ -305,22 +294,28 @@ def ehrhart_polynomial(self, verbose=False, dual=None, args.append('--{}'.format(key)) else: args.append('--{}={}'.format(key, value)) - args.append('--cdd') - args.append(in_filename) + args += ['--cdd', '/dev/stdin'] try: - latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) + # The cwd argument is needed because latte + # always produces diagnostic output files. + latte_proc = Popen(args, + stdin=PIPE, stdout=PIPE, + 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") + 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") - ans, err = latte_proc.communicate() + ans, err = latte_proc.communicate(ine) ret_code = latte_proc.poll() if ret_code: - if not verbose: - print err - raise RuntimeError("Latte returned {} when running:\n{}\n(see output above)".format(ret_code, ' '.join(args))) + if err is None: + err = ", see error message above" + else: + err = ":\n" + err + raise RuntimeError("LattE integrale failed with exit code {} to execute {}".format(ret_code, ' '.join(args)) + err.strip()) p = ans.splitlines()[-2] diff --git a/src/sage/geometry/polyhedron/cdd_file_format.py b/src/sage/geometry/polyhedron/cdd_file_format.py index 5b8d6dc51fe..9eed9cb84e2 100644 --- a/src/sage/geometry/polyhedron/cdd_file_format.py +++ b/src/sage/geometry/polyhedron/cdd_file_format.py @@ -14,18 +14,22 @@ from misc import _set_to_None_if_empty, _common_length_of, _to_space_separated_string - - ######################################################################### -def cdd_Vrepresentation(cdd_type, vertices, rays, lines): +def cdd_Vrepresentation(cdd_type, vertices, rays, lines, file_output=None): r""" Return a string containing the V-representation in cddlib's ext format. - NOTE: + INPUT: + + - ``file_output`` (string; optional) -- a filename to which the + representation should be written. If set to ``None`` (default), + representation is returned as a string. - If there is no vertex given, then the origin will be implicitly - added. You cannot write the empty V-representation (which cdd - would refuse to process). + .. NOTE:: + + If there is no vertex given, then the origin will be implicitly + added. You cannot write the empty V-representation (which cdd would + refuse to process). EXAMPLES:: @@ -39,6 +43,12 @@ def cdd_Vrepresentation(cdd_type, vertices, rays, lines): 0 1 0 1 0 0 end + + TESTS:: + + sage: from sage.misc.temporary_file import tmp_filename + sage: filename = tmp_filename(ext='.ext') + sage: cdd_Vrepresentation('rational', [[0,0]], [[1,0]], [[0,1]], file_output=filename) """ vertices = _set_to_None_if_empty(vertices) rays = _set_to_None_if_empty(rays) @@ -68,18 +78,36 @@ def cdd_Vrepresentation(cdd_type, vertices, rays, lines): for v in vertices: s += ' 1 ' + _to_space_separated_string(v) + '\n' s += 'end\n' - return s + + if file_output is not None: + in_file = open(file_output, 'w') + in_file.write(s) + in_file.close() + else: + return s ######################################################################### -def cdd_Hrepresentation(cdd_type, ieqs, eqns): +def cdd_Hrepresentation(cdd_type, ieqs, eqns, file_output=None): r""" Return a string containing the H-representation in cddlib's ine format. + INPUT: + + - ``file_output`` (string; optional) -- a filename to which the + representation should be written. If set to ``None`` (default), + representation is returned as a string. + EXAMPLES:: sage: from sage.geometry.polyhedron.cdd_file_format import cdd_Hrepresentation sage: cdd_Hrepresentation('rational', None, [[0,1]]) 'H-representation\nlinearity 1 1\nbegin\n 1 2 rational\n 0 1\nend\n' + + TESTS:: + + sage: from sage.misc.temporary_file import tmp_filename + sage: filename = tmp_filename(ext='.ine') + sage: cdd_Hrepresentation('rational', None, [[0,1]], file_output=filename) """ ieqs = _set_to_None_if_empty(ieqs) eqns = _set_to_None_if_empty(eqns) @@ -102,4 +130,10 @@ def cdd_Hrepresentation(cdd_type, ieqs, eqns): for i in ieqs: s += ' ' + _to_space_separated_string(i) + '\n' s += 'end\n' - return s + + if file_output is not None: + in_file = open(file_output, 'w') + in_file.write(s) + in_file.close() + else: + return s diff --git a/src/sage/graphs/asteroidal_triples.pyx b/src/sage/graphs/asteroidal_triples.pyx index 260abe307c0..56bfe7d2ce6 100644 --- a/src/sage/graphs/asteroidal_triples.pyx +++ b/src/sage/graphs/asteroidal_triples.pyx @@ -72,7 +72,7 @@ include "sage/data_structures/bitset.pxi" from libc.stdint cimport uint32_t from sage.graphs.base.static_sparse_graph cimport short_digraph, init_short_digraph, free_short_digraph - +from sage.ext.memory_allocator cimport MemoryAllocator def is_asteroidal_triple_free(G, certificate=False): """ @@ -145,6 +145,10 @@ def is_asteroidal_triple_free(G, certificate=False): return True if not certificate else (True, []) # ==> Initialize some data structures for is_asteroidal_triple_free_C + cdef MemoryAllocator mem = MemoryAllocator() + cdef uint32_t * waiting_list = mem.allocarray(n, sizeof(uint32_t)) + cdef uint32_t * _connected_structure = mem.calloc(n * n, sizeof(uint32_t)) + cdef uint32_t ** connected_structure = mem.allocarray(n, sizeof(uint32_t *)) # Copying the whole graph to obtain the list of neighbors quicker than by # calling out_neighbors. This data structure is well documented in the @@ -155,18 +159,6 @@ def is_asteroidal_triple_free(G, certificate=False): cdef bitset_t seen bitset_init(seen, n) - cdef uint32_t * waiting_list = sage_malloc(n * sizeof(uint32_t)) - cdef uint32_t * _connected_structure = sage_calloc(n * n, sizeof(uint32_t)) - cdef uint32_t ** connected_structure = sage_malloc(n * sizeof(uint32_t *)) - - if waiting_list==NULL or _connected_structure==NULL or connected_structure==NULL: - bitset_free(seen) - sage_free(waiting_list) - sage_free(_connected_structure) - sage_free(connected_structure) - free_short_digraph(sd) - raise MemoryError() - connected_structure[0] = _connected_structure for i in range(n-1): connected_structure[i+1] = connected_structure[i] + n @@ -183,12 +175,8 @@ def is_asteroidal_triple_free(G, certificate=False): finally: # Release memory bitset_free(seen) - sage_free(waiting_list) - sage_free(_connected_structure) - sage_free(connected_structure) free_short_digraph(sd) - # ==> We return the result if certificate: diff --git a/src/sage/graphs/base/boost_graph.pxd b/src/sage/graphs/base/boost_graph.pxd index 83c511527c1..297711629e6 100644 --- a/src/sage/graphs/base/boost_graph.pxd +++ b/src/sage/graphs/base/boost_graph.pxd @@ -29,9 +29,12 @@ cdef extern from "boost/graph/adjacency_list.hpp" namespace "boost": pass cdef extern from "boost_interface.cpp": + ctypedef unsigned long v_index + ctypedef unsigned long e_index + cdef cppclass result_ec: - int ec - vector[int] edges + v_index ec + vector[v_index] edges cdef cppclass result_cc: float average_clustering_coefficient @@ -40,12 +43,13 @@ cdef extern from "boost_interface.cpp": cdef cppclass BoostGraph[OutEdgeListS, VertexListS, DirectedS, EdgeListS]: BoostGraph() void add_vertex() - int num_verts() - void add_edge(int u, int v) - int num_edges() + v_index num_verts() + void add_edge(v_index u, v_index v) + e_index num_edges() result_ec edge_connectivity() - double clustering_coeff(int v) + double clustering_coeff(v_index v) result_cc clustering_coeff_all() + vector[v_index] dominator_tree(v_index v) ctypedef BoostGraph[vecS, vecS, undirectedS, vecS] BoostVecGraph ctypedef BoostGraph[vecS, vecS, bidirectionalS, vecS] BoostVecDiGraph diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index e6cd495f82a..8b167b3eeea 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -33,15 +33,12 @@ 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. Functions --------- """ -from sage.graphs.graph import Graph -from sage.graphs.digraph import DiGraph -from sage.graphs.generic_graph import GenericGraph - include "sage/ext/interrupt.pxi" cdef boost_graph_from_sage_graph(BoostGenGraph *g, g_sage): @@ -52,6 +49,8 @@ cdef boost_graph_from_sage_graph(BoostGenGraph *g, g_sage): otherwise). """ + from sage.graphs.generic_graph import GenericGraph + if not isinstance(g_sage, GenericGraph): raise ValueError("The input parameter must be a Sage graph.") @@ -103,6 +102,9 @@ cpdef edge_connectivity(g): [4, [(0, 1), (0, 2), (0, 3), (0, 4)]] """ + from sage.graphs.graph import Graph + from sage.graphs.digraph import DiGraph + # These variables are automatically deleted when the function terminates. cdef BoostVecGraph g_boost_und cdef BoostVecDiGraph g_boost_dir @@ -190,6 +192,8 @@ cpdef clustering_coeff(g, vertices = None): sage: clustering_coeff(g, vertices="abde") [0.5, {'a': 0.5, 'b': 0.5, 'd': 0.5, 'e': 0.5}] """ + from sage.graphs.graph import Graph + sig_on() # These variables are automatically deleted when the function terminates. cdef BoostVecGraph g_boost @@ -210,3 +214,151 @@ cpdef clustering_coeff(g, vertices = None): clust_v_sage = {g_vertices[v]: clust_v_int[v] for v in vertices_boost} sig_off() return [average_clustering, clust_v_sage] + + +cpdef dominator_tree(g, root, return_dict = False): + r""" + Uses Boost to compute the dominator tree of ``g``, rooted at ``root``. + + A node `d` dominates a node `n` if every path from the entry node + ``root`` to `n` must go through `d`. The immediate dominator of a node + `n` is the unique node that strictly dominates `n` but does not dominate + any other node that dominates `n`. A dominator tree is a tree where each + node's children are those nodes it immediately dominates. For more + information, see :wikipedia:`Dominator_(graph_theory)`. + + If the graph is connected and undirected, the parent of a vertex `v` is: + + - the root if `v` is in the same biconnected component as the root; + + - the first cut vertex in a path from `v` to the root, otherwise. + + If the graph is not connected, the dominator tree of the whole graph is + equal to the dominator tree of the connected component of the root. + + If the graph is directed, computing a dominator tree is more complicated, + and it needs time `O(m\log m)`, where `m` is the number of edges. The + implementation provided by Boost is the most general one, so it needs time + `O(m\log m)` even for undirected graphs. + + INPUT: + + - ``g`` (generic_graph) - the input graph. + + - ``root`` (vertex) - the root of the dominator tree. + + - ``return_dict`` (boolean) - if ``True``, the function returns a + dictionary associating to each vertex its parent in the dominator + tree. If ``False`` (default), it returns the whole tree, as a ``Graph`` + or a ``DiGraph``. + + OUTPUT: + + The dominator tree, as a graph or as a dictionary, depending on the + value of ``return_dict``. If the output is a dictionary, it will contain + ``None`` in correspondence of ``root`` and of vertices that are not + reachable from ``root``. If the output is a graph, it will not contain + vertices that are not reachable from ``root``. + + EXAMPLES: + + An undirected grid is biconnected, and its dominator tree is a star + (everyone's parent is the root):: + + sage: g = graphs.GridGraph([2,2]).dominator_tree((0,0)) + sage: g.to_dictionary() + {(0, 0): [(0, 1), (1, 0), (1, 1)], (0, 1): [(0, 0)], (1, 0): [(0, 0)], (1, 1): [(0, 0)]} + + If the graph is made by two 3-cycles `C_1,C_2` connected by an edge `(v,w)`, + with `v \in C_1`, `w \in C_2`, the cut vertices are `v` and `w`, the + biconnected components are `C_1`, `C_2`, and the edge `(v,w)`. If the root + is in `C_1`, the parent of each vertex in `C_1` is the root, the parent of + `w` is `v`, and the parent of each vertex in `C_2` is `w`:: + + sage: G = 2 * graphs.CycleGraph(3) + sage: v = 0 + sage: w = 3 + sage: G.add_edge(v,w) + sage: G.dominator_tree(1, return_dict=True) + {0: 1, 1: None, 2: 1, 3: 0, 4: 3, 5: 3} + + An example with a directed graph:: + + sage: g = digraphs.Circuit(10).dominator_tree(5) + sage: g.to_dictionary() + {0: [1], 1: [2], 2: [3], 3: [4], 4: [], 5: [6], 6: [7], 7: [8], 8: [9], 9: [0]} + + If the output is a dictionary:: + + sage: graphs.GridGraph([2,2]).dominator_tree((0,0), return_dict = True) + {(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 0)} + + TESTS: + + If ``g`` is not a graph, an error is raised:: + + sage: from sage.graphs.base.boost_graph import dominator_tree + sage: dominator_tree('I am not a graph', 0) + Traceback (most recent call last): + ... + ValueError: The input g must be a Sage graph. + + If ``root`` is not a vertex, an error is raised:: + + sage: digraphs.TransitiveTournament(10).dominator_tree('Not a vertex!') + Traceback (most recent call last): + ... + ValueError: The input root must be a vertex of g. + sage: graphs.GridGraph([2,2]).dominator_tree(0) + Traceback (most recent call last): + ... + ValueError: The input root must be a vertex of g. + + """ + from sage.graphs.graph import Graph + from sage.graphs.digraph import DiGraph + + if not isinstance(g, Graph) and not isinstance(g, DiGraph): + raise ValueError("The input g must be a Sage graph.") + if not root in g.vertices(): + raise ValueError("The input root must be a vertex of g.") + + sig_on() + # These variables are automatically deleted when the function terminates. + cdef BoostVecGraph g_boost_und + cdef BoostVecDiGraph g_boost_dir + 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())} + + if isinstance(g, Graph): + boost_graph_from_sage_graph(&g_boost_und, g) + result = g_boost_und.dominator_tree(vertex_to_int[root]) + + elif isinstance(g, DiGraph): + boost_graph_from_sage_graph(&g_boost_dir, g) + result = g_boost_dir.dominator_tree(vertex_to_int[root]) + + sig_off() + + cdef v_index no_parent = -1 + + if return_dict: + return {v:(None if result[vertex_to_int[v]] == no_parent else int_to_vertex[ result[vertex_to_int[v]]]) for v in g.vertices()}; + + edges = [[int_to_vertex[result[vertex_to_int[v]]], v] for v in g.vertices() if result[vertex_to_int[v]] != no_parent] + + if g.is_directed(): + if len(edges) == 0: + g = DiGraph() + g.add_vertex(root) + return g + else: + return DiGraph(edges) + else: + if len(edges) == 0: + g = Graph() + g.add_vertex(root) + return g + else: + return Graph(edges) diff --git a/src/sage/graphs/base/boost_interface.cpp b/src/sage/graphs/base/boost_interface.cpp index f396b2ae331..cf895eb42b8 100644 --- a/src/sage/graphs/base/boost_interface.cpp +++ b/src/sage/graphs/base/boost_interface.cpp @@ -4,16 +4,20 @@ #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 { - int ec; // The edge connectivity - vector edges; // The edges in a minimum cut, stored as a list of + v_index ec; // The edge connectivity + 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; @@ -46,14 +50,16 @@ class BoostGraph */ { typedef typename boost::adjacency_list adjacency_list; + property, no_property, 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; + typedef typename boost::property_map::type vertex_to_int_map; public: adjacency_list *graph; vector *vertices; + vertex_to_int_map index; BoostGraph() { graph = new adjacency_list(); @@ -65,11 +71,11 @@ class BoostGraph delete vertices; } - int num_verts() { + v_index num_verts() { return num_vertices(*graph); } - int num_edges() { + e_index num_edges() { return boost::num_edges(*graph); } @@ -77,7 +83,7 @@ class BoostGraph (*vertices).push_back(boost::add_vertex((*vertices).size(), *graph)); } - void add_edge(int u, int v) { + void add_edge(v_index u, v_index v) { boost::add_edge((*vertices)[u], (*vertices)[v], *graph); } @@ -87,33 +93,44 @@ class BoostGraph back_insert_iterator inserter(disconnecting_set); to_return.ec = boost::edge_connectivity(*graph, inserter); - for (unsigned i = 0; i < disconnecting_set.size(); i++) { + for (v_index i = 0; i < disconnecting_set.size(); i++) { edge_descriptor edge = disconnecting_set[i]; - to_return.edges.push_back((*graph)[boost::source(edge, *graph)]); - to_return.edges.push_back((*graph)[boost::target(edge, *graph)]); + to_return.edges.push_back(index[boost::source(edge, *graph)]); + to_return.edges.push_back(index[boost::target(edge, *graph)]); } return to_return; } - double clustering_coeff(int v) { + double clustering_coeff(v_index v) { return clustering_coefficient(*graph, (*vertices)[v]); } result_cc clustering_coeff_all() { 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)); + return to_return; + } - typedef typename exterior_vertex_property::container_type ClusteringContainer; - typedef typename exterior_vertex_property::map_type ClusteringMap; - - ClusteringContainer coefs(num_vertices(*graph)); - ClusteringMap cm(coefs, *graph); - - to_return.average_clustering_coefficient = all_clustering_coefficients(*graph, cm); - for (unsigned i = 0; i < num_verts(); i++) { - to_return.clust_of_v.push_back(cm[(*graph)[i]]); + vector dominator_tree(v_index v) { + vector fathers(num_verts()); + vector fathers_descr(num_verts(), + boost::graph_traits::null_vertex()); + + lengauer_tarjan_dominator_tree(*graph, (*vertices)[v], + make_iterator_property_map( + fathers_descr.begin(), index)); + + for (v_index i = 0; i < num_verts(); i++) { + vertex_descriptor v = fathers_descr[i]; + if (v == boost::graph_traits::null_vertex()) { + fathers[i] = -1; + } else { + fathers[i] = index[v]; + } } - - return to_return; + return fathers; } }; diff --git a/src/sage/graphs/centrality.pyx b/src/sage/graphs/centrality.pyx index 4cde7d04540..5d31cbeb888 100644 --- a/src/sage/graphs/centrality.pyx +++ b/src/sage/graphs/centrality.pyx @@ -319,5 +319,4 @@ cdef dict centrality_betweenness_C(G, numerical_type _, normalize=True): else: 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())} diff --git a/src/sage/graphs/chrompoly.pyx b/src/sage/graphs/chrompoly.pyx index b521c28ffdb..1df71ffd485 100644 --- a/src/sage/graphs/chrompoly.pyx +++ b/src/sage/graphs/chrompoly.pyx @@ -19,6 +19,7 @@ REFERENCE: from sage.rings.integer_ring import ZZ from sage.rings.integer cimport Integer +from sage.ext.memory_allocator cimport MemoryAllocator from sage.misc.all import prod include 'sage/ext/interrupt.pxi' include 'sage/ext/cdefs.pxi' @@ -102,31 +103,15 @@ def chromatic_polynomial(G, return_tree_basis = False): G.remove_loops() nverts = G.num_verts() nedges = G.num_edges() - queue = sage_malloc(nverts * sizeof(int)) - chords1 = sage_malloc((nedges - nverts + 1) * sizeof(int)) - chords2 = sage_malloc((nedges - nverts + 1) * sizeof(int)) - parent = sage_malloc(nverts * sizeof(int)) - bfs_reorder = sage_malloc(nverts * sizeof(int)) - tot = sage_malloc((nverts+1) * sizeof(mpz_t)) - if queue is NULL or \ - chords1 is NULL or \ - chords2 is NULL or \ - parent is NULL or \ - bfs_reorder is NULL or \ - tot is NULL: - if queue is not NULL: - sage_free(queue) - if chords1 is not NULL: - sage_free(chords1) - if chords2 is not NULL: - sage_free(chords2) - if parent is not NULL: - sage_free(parent) - if bfs_reorder is not NULL: - sage_free(bfs_reorder) - if tot is not NULL: - sage_free(tot) - raise RuntimeError("Error allocating memory for chrompoly.") + + cdef MemoryAllocator mem = MemoryAllocator() + queue = mem.allocarray(nverts, sizeof(int)) + chords1 = mem.allocarray((nedges - nverts + 1), sizeof(int)) + chords2 = mem.allocarray((nedges - nverts + 1), sizeof(int)) + parent = mem.allocarray(nverts, sizeof(int)) + bfs_reorder = mem.allocarray(nverts, sizeof(int)) + tot = mem.allocarray((nverts+1), sizeof(mpz_t)) + coeffs = mem.allocarray((nverts+1), sizeof(mpz_t)) num_chords = 0 # Breadth first search from 0: @@ -173,29 +158,14 @@ def chromatic_polynomial(G, return_tree_basis = False): i -= 1 try: sig_on() - contract_and_count(chords1, chords2, num_chords, nverts, tot, parent) - sig_off() - except RuntimeError: - sage_free(queue) - sage_free(chords1) - sage_free(chords2) - sage_free(parent) - sage_free(bfs_reorder) - for i from 0 <= i <= nverts: - mpz_clear(tot[i]) - sage_free(tot) - raise RuntimeError("Error allocating memory for chrompoly.") - coeffs = sage_malloc((nverts+1) * sizeof(mpz_t)) - if coeffs is NULL: - sage_free(queue) - sage_free(chords1) - sage_free(chords2) - sage_free(parent) - sage_free(bfs_reorder) - for i from 0 <= i <= nverts: + try: + contract_and_count(chords1, chords2, num_chords, nverts, tot, parent) + finally: + sig_off() + except BaseException: + for i in range(nverts): mpz_clear(tot[i]) - sage_free(tot) - raise RuntimeError("Error allocating memory for chrompoly.") + raise for i from 0 <= i <= nverts: mpz_init(coeffs[i]) # also sets them to 0 mpz_init(coeff) @@ -225,19 +195,11 @@ def chromatic_polynomial(G, return_tree_basis = False): mpz_set(c_ZZ.value, coeffs[i]) coeffs_ZZ.append(c_ZZ) f = R(coeffs_ZZ) - sage_free(queue) - sage_free(chords1) - sage_free(chords2) - sage_free(parent) - sage_free(bfs_reorder) for i from 0 <= i <= nverts: mpz_clear(tot[i]) mpz_clear(coeffs[i]) - sage_free(tot) - sage_free(coeffs) - mpz_clear(coeff) mpz_clear(m) @@ -248,20 +210,11 @@ cdef int contract_and_count(int *chords1, int *chords2, int num_chords, int nver if num_chords == 0: mpz_add_ui(tot[nverts], tot[nverts], 1) return 0 - cdef int *new_chords1 = sage_malloc(num_chords * sizeof(int)) - cdef int *new_chords2 = sage_malloc(num_chords * sizeof(int)) - cdef int *ins_list1 = sage_malloc(num_chords * sizeof(int)) - cdef int *ins_list2 = sage_malloc(num_chords * sizeof(int)) - if new_chords1 is NULL or new_chords2 is NULL or ins_list1 is NULL or ins_list2 is NULL: - if new_chords1 is not NULL: - sage_free(new_chords1) - if new_chords2 is not NULL: - sage_free(new_chords2) - if ins_list1 is not NULL: - sage_free(ins_list1) - if ins_list2 is not NULL: - sage_free(ins_list2) - raise RuntimeError("Error allocating memory for chrompoly.") + cdef MemoryAllocator mem = MemoryAllocator() + cdef int *new_chords1 = mem.allocarray(num_chords, sizeof(int)) + cdef int *new_chords2 = mem.allocarray(num_chords, sizeof(int)) + cdef int *ins_list1 = mem.allocarray(num_chords, sizeof(int)) + cdef int *ins_list2 = mem.allocarray(num_chords, sizeof(int)) cdef int i, j, k, x1, xj, z, num, insnum, parent_checked for i from 0 <= i < num_chords: # contract chord i, and recurse @@ -332,18 +285,5 @@ cdef int contract_and_count(int *chords1, int *chords2, int num_chords, int nver new_chords2[num] = chords2[j] num += 1 j += 1 - try: - contract_and_count(new_chords1, new_chords2, num, nverts - 1, tot, parent) - except RuntimeError: - sage_free(new_chords1) - sage_free(new_chords2) - sage_free(ins_list1) - sage_free(ins_list2) - raise RuntimeError("Error allocating memory for chrompoly.") + contract_and_count(new_chords1, new_chords2, num, nverts - 1, tot, parent) mpz_add_ui(tot[nverts], tot[nverts], 1) - sage_free(new_chords1) - sage_free(new_chords2) - sage_free(ins_list1) - sage_free(ins_list2) - - diff --git a/src/sage/graphs/distances_all_pairs.pxd b/src/sage/graphs/distances_all_pairs.pxd index 9928ab9f689..e46e35cb42f 100644 --- a/src/sage/graphs/distances_all_pairs.pxd +++ b/src/sage/graphs/distances_all_pairs.pxd @@ -1,8 +1,10 @@ +from libc.stdint cimport uint32_t + cdef unsigned short * c_shortest_path_all_pairs(G) except NULL cdef unsigned short * c_distances_all_pairs(G) cdef all_pairs_shortest_path_BFS(gg, unsigned short * predecessors, unsigned short * distances, - int * eccentricity) + uint32_t * eccentricity) -cdef int * c_eccentricity(G) except NULL +cdef uint32_t * c_eccentricity(G) except NULL diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index ed108dcd556..c9bcc2193ec 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -131,6 +131,9 @@ REFERENCE: tight bounds for the diameter of massive graphs. *ACM Journal of Experimental Algorithms* 13 (2008) http://dx.doi.org/10.1145/1412228.1455266 +.. [TK13] F. W. Takes and W. A. Kosters. Computing the eccentricity distribution + of large graphs. *Algorithms* 6:100-118 (2013) + http://dx.doi.org/10.3390/a6010100 Functions --------- @@ -151,14 +154,15 @@ from libc.string cimport memset from libc.stdint cimport uint64_t, uint32_t, INT32_MAX, UINT32_MAX from sage.graphs.base.c_graph cimport CGraphBackend from sage.graphs.base.c_graph cimport CGraph +from sage.ext.memory_allocator cimport MemoryAllocator -from sage.graphs.base.static_sparse_graph cimport short_digraph, init_short_digraph, free_short_digraph +from sage.graphs.base.static_sparse_graph cimport short_digraph, init_short_digraph, free_short_digraph, out_degree cdef inline all_pairs_shortest_path_BFS(gg, unsigned short * predecessors, unsigned short * distances, - int * eccentricity): + uint32_t * eccentricity): """ See the module's documentation. """ @@ -167,6 +171,7 @@ cdef inline all_pairs_shortest_path_BFS(gg, cdef list int_to_vertex = gg.vertices() cdef int i + cdef MemoryAllocator mem = MemoryAllocator() cdef int n = len(int_to_vertex) @@ -184,9 +189,7 @@ cdef inline all_pairs_shortest_path_BFS(gg, bitset_init(seen, n) # The list of waiting vertices, the beginning and the end of the list - cdef int * waiting_list = sage_malloc(n*sizeof(int)) - if waiting_list == NULL: - raise MemoryError() + cdef int * waiting_list = mem.allocarray(n, sizeof(int)) cdef int waiting_beginning = 0 cdef int waiting_end = 0 @@ -196,11 +199,7 @@ cdef inline all_pairs_shortest_path_BFS(gg, cdef uint32_t * end cdef unsigned short * c_predecessors = predecessors - cdef int * c_eccentricity = eccentricity - cdef int * c_distances = sage_malloc( n * sizeof(int)) - if c_distances == NULL: - sage_free(waiting_list) - raise MemoryError() + cdef int * c_distances = mem.allocarray(n, sizeof(int)) # Copying the whole graph to obtain the list of neighbors quicker than by # calling out_neighbors @@ -269,29 +268,32 @@ cdef inline all_pairs_shortest_path_BFS(gg, waiting_beginning += 1 # If not all the vertices have been met - for v in range(n): - if not bitset_in(seen, v): + if bitset_len(seen) < n: + bitset_complement(seen, seen) + v = bitset_next(seen, 0) + while v >= 0: c_distances[v] = INT32_MAX if predecessors != NULL: c_predecessors[v] = -1 + v = bitset_next(seen, v+1) + + if eccentricity != NULL: + eccentricity[source] = UINT32_MAX + + elif eccentricity != NULL: + eccentricity[source] = c_distances[waiting_list[n-1]] + if predecessors != NULL: c_predecessors += n - if eccentricity != NULL: - c_eccentricity[source] = 0 - for i in range(n): - c_eccentricity[source] = c_eccentricity[source] if c_eccentricity[source] >= c_distances[i] else c_distances[i] - if distances != NULL: for i in range(n): distances[i] = c_distances[i] distances += n bitset_free(seen) - sage_free(waiting_list) free_short_digraph(sd) - sage_free(c_distances) ################ # Predecessors # @@ -710,51 +712,182 @@ def distances_and_predecessors_all_pairs(G): # Eccentricity # ################ -cdef int * c_eccentricity(G) except NULL: +cdef uint32_t * c_eccentricity(G) except NULL: r""" - Returns the vector of eccentricities in G. + Return the vector of eccentricities in G. The array returned is of length n, and its ith component is the eccentricity of the ith vertex in ``G.vertices()``. """ cdef unsigned int n = G.order() - cdef int * ecc = sage_malloc(n*sizeof(int)) + cdef uint32_t * ecc = sage_calloc(n, sizeof(uint32_t)) if ecc == NULL: raise MemoryError() all_pairs_shortest_path_BFS(G, NULL, NULL, ecc) return ecc -def eccentricity(G): +cdef uint32_t * c_eccentricity_bounding(G) except NULL: r""" - Returns the vector of eccentricities in G. + Return the vector of eccentricities in G using the algorithm of [TK13]_. The array returned is of length n, and its ith component is the eccentricity of the ith vertex in ``G.vertices()``. + The algorithm proposed in [TK13]_ is based on the observation that for all + nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq + ecc[v] + d(v,w)`. Also the algorithms iteratively improves upper and lower + bounds on the eccentricity of each node until no further improvements can be + done. This algorithm offers good running time reduction on scale-free graphs. + """ + if G.is_directed(): + raise ValueError("The 'bounds' method only works on undirected graphs.") + + # Copying the whole graph to obtain the list of neighbors quicker than by + # calling out_neighbors. This data structure is well documented in the + # module sage.graphs.base.static_sparse_graph + cdef unsigned int n = G.order() + cdef short_digraph sd + init_short_digraph(sd, G) + + # allocated some data structures + cdef bitset_t seen + bitset_init(seen, n) + cdef uint32_t * distances = sage_malloc(3 * n * sizeof(uint32_t)) + cdef uint32_t * LB = sage_calloc(n, sizeof(uint32_t)) + if distances==NULL or LB==NULL: + bitset_free(seen) + sage_free(LB) + sage_free(distances) + free_short_digraph(sd) + raise MemoryError() + cdef uint32_t * waiting_list = distances + n + cdef uint32_t * UB = distances + 2 * n + memset(UB, -1, n * sizeof(uint32_t)) + + cdef uint32_t v, w, next_v, tmp, cpt = 0 + + # The first vertex is the one with largest degree + next_v = max((out_degree(sd, v), v) for v in range(n))[1] + + sig_on() + while next_v!=UINT32_MAX: + + v = next_v + cpt += 1 + + # Compute the exact eccentricity of v + LB[v] = simple_BFS(n, sd.neighbors, v, distances, NULL, waiting_list, seen) + + if LB[v]==UINT32_MAX: + # The graph is not connected. We set maximum value and exit. + for w in range(n): + LB[w] = UINT32_MAX + break + + # Improve the bounds on the eccentricity of other vertices and select + # source of the next BFS + next_v = UINT32_MAX + for w in range(n): + LB[w] = max(LB[w], max(LB[v] - distances[w], distances[w])) + UB[w] = min(UB[w], LB[v] + distances[w]) + if LB[w]==UB[w]: + continue + elif next_v==UINT32_MAX or (cpt%2==0 and LB[w]UB[next_v]): + # The next vertex is either the vertex with largest upper bound + # or smallest lower bound + next_v = w + + sig_off() + + sage_free(distances) + bitset_free(seen) + free_short_digraph(sd) + + return LB + +def eccentricity(G, method="standard"): + r""" + Return the vector of eccentricities in G. + + The array returned is of length `n`, and its `i`-th component is the + eccentricity of the ith vertex in ``G.vertices()``. + + INPUT: + + - ``G`` -- a Graph or a DiGraph. + + - ``method`` -- (default: ``'standard'``) name of the method used to compute + the eccentricity of the vertices. Available methods are ``'standard'`` + which performs a BFS from each vertex and ``'bounds'`` which uses the fast + algorithm proposed in [TK13]_ for undirected graphs. + EXAMPLE:: sage: from sage.graphs.distances_all_pairs import eccentricity sage: g = graphs.PetersenGraph() sage: eccentricity(g) [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + + TEST: + + All methods are valid:: + + sage: from sage.graphs.distances_all_pairs import eccentricity + sage: g = graphs.RandomGNP(50,.1) + sage: eccentricity(g, method='standard')==eccentricity(g, method='bounds') + True + + Case of not (strongly) connected (directed) graph:: + + sage: from sage.graphs.distances_all_pairs import eccentricity + sage: g = 2*graphs.PathGraph(2) + sage: eccentricity(g, method='bounds') + [+Infinity, +Infinity, +Infinity, +Infinity] + sage: g = digraphs.Path(3) + sage: eccentricity(g, method='standard') + [2, +Infinity, +Infinity] + + The bounds method is for Graph only:: + + sage: from sage.graphs.distances_all_pairs import eccentricity + sage: g = digraphs.Circuit(2) + sage: eccentricity(g, method='bounds') + Traceback (most recent call last): + ... + ValueError: The 'bounds' method only works on undirected graphs. + + Asking for unknown method:: + + sage: from sage.graphs.distances_all_pairs import eccentricity + sage: g = graphs.PathGraph(2) + sage: eccentricity(g, method='Nice Jazz Festival') + Traceback (most recent call last): + ... + ValueError: Unknown method 'Nice Jazz Festival'. Please contribute. """ from sage.rings.infinity import Infinity cdef int n = G.order() + # Trivial cases if n == 0: return [] + elif G.is_directed() and method=='bounds': + raise ValueError("The 'bounds' method only works on undirected graphs.") + elif not G.is_connected(): + return [Infinity]*n + + cdef uint32_t * ecc + if method=="bounds": + ecc = c_eccentricity_bounding(G) + elif method=="standard": + ecc = c_eccentricity(G) + else: + raise ValueError("Unknown method '{}'. Please contribute.".format(method)) - cdef int * ecc = c_eccentricity(G) - - cdef list l_ecc = [] - cdef int i - for i in range(n): - if ecc[i] == INT32_MAX: - l_ecc.append(Infinity) - else: - l_ecc.append(ecc[i]) + from sage.rings.integer import Integer + cdef list l_ecc = [Integer(ecc[i]) if ecc[i]!=UINT32_MAX else +Infinity for i in range(n)] sage_free(ecc) @@ -1520,6 +1653,7 @@ def floyd_warshall(gg, paths = True, distances = False): raise ValueError("The graph backend contains more than "+str( -1)+" nodes") # All this just creates two tables prec[n][n] and dist[n][n] + cdef MemoryAllocator mem = MemoryAllocator() cdef unsigned short * t_prec cdef unsigned short * t_dist cdef unsigned short ** prec @@ -1531,13 +1665,8 @@ def floyd_warshall(gg, paths = True, distances = False): cdef int w_int # init dist - t_dist = sage_malloc(n*n*sizeof(unsigned short)) - if t_dist == NULL: - raise MemoryError() - dist = sage_malloc(n*sizeof(unsigned short *)) - if dist == NULL: - sage_free(t_dist) - raise MemoryError() + t_dist = mem.allocarray(n*n, sizeof(unsigned short)) + dist = mem.allocarray(n, sizeof(unsigned short *)) dist[0] = t_dist for 1 <= i< n: dist[i] = dist[i-1] + n @@ -1550,17 +1679,8 @@ def floyd_warshall(gg, paths = True, distances = False): if paths: # init prec - t_prec = sage_malloc(n*n*sizeof(unsigned short)) - if t_prec == NULL: - sage_free(t_dist) - sage_free(dist) - raise MemoryError() - prec = sage_malloc(n*sizeof(unsigned short *)) - if prec == NULL: - sage_free(t_dist) - sage_free(dist) - sage_free(t_prec) - raise MemoryError() + t_prec = mem.allocarray(n*n, sizeof(unsigned short)) + prec = mem.allocarray(n, sizeof(unsigned short *)) prec[0] = t_prec for 1 <= i< n: prec[i] = prec[i-1] + n @@ -1616,13 +1736,6 @@ def floyd_warshall(gg, paths = True, distances = False): if paths: d_prec[v] = tmp_prec if distances: d_dist[v] = tmp_dist - if paths: - sage_free(t_prec) - sage_free(prec) - - sage_free(t_dist) - sage_free(dist) - if distances and paths: return d_dist, d_prec if paths: diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index 32c56a447e4..a7f7cd443c5 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -25,6 +25,48 @@ # Named Graphs ####################################################################### +def HarborthGraph(): + r""" + Return the Harborth Graph + + The Harborth graph has 104 edges and 52 vertices, and is the smallest known + example of a 4-regular matchstick graph. For more information, see the + :wikipedia:`Harborth_graph`. + + EXAMPLES:: + + sage: g = graphs.HarborthGraph(); g + Harborth Graph: Graph on 52 vertices + sage: g.is_regular(4) + True + + """ + g = Graph(':s_OGKI?@_?g[QABAo__YEFCp@?iIEbqHWuWLbbh?}[OfcXpGhNHdYPY_SgdYX]'+ + 'pZkfJPuo[lfZHys^mFcDs}`pG{UNNgoHC}DIgrI[qjMhTyDQrQlVydrBYmWkn', + loops=False, multiedges=False) + + g.set_pos({ 0: ( 51.5, 400.0), 1: ( 90.6, 308.0), 2: ( 90.6, 492.0), + 3: (129.8, 216.0), 4: (129.8, 584.0), 5: (150.7, 387.9), + 6: (150.7, 412.1), 7: (169.0, 124.0), 8: (169.0, 676.0), + 9: (189.9, 295.9), 10: (189.9, 504.1), 11: (229.1, 203.9), + 12: (229.1, 596.1), 13: (250.0, 400.0), 14: (251.4, 180.6), + 15: (251.4, 619.4), 16: (256.1, 300.2), 17: (256.1, 499.8), + 18: (259.3, 080.9), 19: (259.3, 719.1), 20: (333.8, 237.2), + 21: (333.8, 562.8), 22: (341.7, 137.5), 23: (341.7, 662.5), + 24: (350.0, 037.9), 25: (350.0, 336.0), 26: (350.0, 464.0), + 27: (350.0, 762.1), 28: (358.3, 137.5), 29: (358.3, 662.5), + 30: (366.2, 237.2), 31: (366.2, 562.8), 32: (440.7, 080.9), + 33: (440.7, 719.1), 34: (443.9, 300.2), 35: (443.9, 499.8), + 36: (448.6, 180.6), 37: (448.6, 619.4), 38: (450.0, 400.0), + 39: (470.9, 203.9), 40: (470.9, 596.1), 41: (510.1, 295.9), + 42: (510.1, 504.1), 43: (531.0, 124.0), 44: (531.0, 676.0), + 45: (549.3, 387.9), 46: (549.3, 412.1), 47: (570.2, 216.0), + 48: (570.2, 584.0), 49: (609.4, 308.0), 50: (609.4, 492.0), + 51: (648.5, 400.0)}) + g.name("Harborth Graph") + return g + + def HarriesGraph(embedding=1): r""" Returns the Harries Graph. @@ -1863,6 +1905,34 @@ def CoxeterGraph(): return g +def DejterGraph(): + r""" + Return the Dejter graph. + + The Dejter graph is obtained from the binary 7-cube by deleting a copy of + the Hamming code of length 7. It is 6-regular, with 112 vertices and 336 + edges. For more information, see the :wikipedia:`Dejter_graph`. + + EXAMPLES:: + + sage: g = graphs.DejterGraph(); g + Dejter Graph: Graph on 112 vertices + sage: g.is_regular(k=6) + True + sage: g.girth() + 4 + """ + from sage.graphs.generators.families import CubeGraph + from sage.coding.code_constructions import HammingCode + from sage.rings.finite_rings.constructor import FiniteField + + from string import join + g = CubeGraph(7) + g.delete_vertices([join(map(str,x),"") + for x in HammingCode(3, FiniteField(2))]) + g.name("Dejter Graph") + return g + def DesarguesGraph(): """ Returns the Desargues graph. @@ -2341,6 +2411,33 @@ def ErreraGraph(): 14: [16]} return Graph(edge_dict, name="Errera graph") +def F26AGraph(): + r""" + Return the F26A graph. + + The F26A graph is a symmetric bipartite cubic graph with 26 vertices and 39 + edges. For more information, see the :wikipedia:`F26A_graph`. + + EXAMPLE:: + + sage: g = graphs.F26AGraph(); g + F26A Graph: Graph on 26 vertices + sage: g.order(),g.size() + (26, 39) + sage: g.automorphism_group().cardinality() + 78 + sage: g.girth() + 6 + sage: g.is_bipartite() + True + sage: g.characteristic_polynomial().factor() + (x - 3) * (x + 3) * (x^4 - 5*x^2 + 3)^6 + """ + from sage.graphs.generators.families import LCFGraph + g= LCFGraph(26, [7,-7],13) + g.name("F26A Graph") + return g + def FlowerSnark(): """ Returns a Flower Snark. @@ -3278,6 +3375,94 @@ def KrackhardtKiteGraph(): pos_dict = {0:(-1,4),1:(1,4),2:(-2,3),3:(0,3),4:(2,3),5:(-1,2),6:(1,2),7:(0,1),8:(0,0),9:(0,-1)} return Graph(edges, pos=pos_dict, name="Krackhardt Kite Graph") +def Klein3RegularGraph(): + r""" + Return the Klein 3-regular graph. + + The cubic Klein graph has 56 vertices and can be embedded on a surface of + genus 3. It is the dual of + :meth:`~sage.graphs.graph_generators.GraphGenerators.Klein7RegularGraph`. For + more information, see the :wikipedia:`Klein_graphs`. + + EXAMPLE:: + + sage: g = graphs.Klein3RegularGraph(); g + Klein 3-regular Graph: Graph on 56 vertices + sage: g.order(), g.size() + (56, 84) + sage: g.girth() + 7 + sage: g.automorphism_group().cardinality() + 336 + sage: g.chromatic_number() + 3 + """ + from sage.graphs.graph_plot import _circle_embedding + g3 = Graph(':w`_GKWDBap`CMWFCpWsQUNdBwwuXPHrg`U`RIqypehVLqgHupYcFJyAv^Prk]'+ + 'EcarHwIVHAKh|\\tLVUxT]`ZDTJ{Af[o_AuKs{r_?ef', + loops=False, multiedges=False) + _circle_embedding(g3,[0, 2, 3, 4, 6, 8, 14, 1, 37, 30, 34, 48, 55, 43, 40, + 45, 18, 20, 47, 42, 23, 17, 16, 10, 41, 11, 49, 25, + 51, 26, 54, 9, 22, 15, 21, 12, 24, 7, 52, 31, 32, 36, + 46, 35, 29, 50, 27, 19, 28, 5, 33, 13, 53, 39, 38, 44]) + g3.name("Klein 3-regular Graph") + return g3 + +def Klein7RegularGraph(): + r""" + Return the Klein 7-regular graph. + + The 7-valent Klein graph has 24 vertices and can be embedded on a surface of + genus 3. It is the dual of + :meth:`~sage.graphs.graph_generators.GraphGenerators.Klein3RegularGraph`. For + more information, see the :wikipedia:`Klein_graphs`. + + EXAMPLE:: + + sage: g = graphs.Klein7RegularGraph(); g + Klein 7-regular Graph: Graph on 24 vertices + sage: g.order(), g.size() + (24, 84) + sage: g.girth() + 3 + sage: g.automorphism_group().cardinality() + 336 + sage: g.chromatic_number() + 4 + """ + from sage.graphs.graph_plot import _circle_embedding + g7 = Graph(':W__@`AaBbC_CDbDcE`F_AG_@DEH_IgHIJbFGIKaFHILeFGHMdFKN_EKOPaCNP'+ + 'Q`HOQRcGLRS`BKMSTdJKLPTU',loops=False,multiedges=False) + _circle_embedding(g7,[0, 2, 3, 1, 9, 16, 20, 21, 4, 19, 17, 7, 15, + 10, 8, 13, 11, 5, 23, 22, 14, 12, 18, 6]) + g7.name("Klein 7-regular Graph") + return g7 + +def LocalMcLaughlinGraph(): + r""" + Return the local McLaughlin graph + + The local McLaughlin graph is a strongly regular graph with parameters + `(162,56,10,24)`. It can be obtained from + :meth:`~sage.graphs.graph_generators.GraphGenerators.McLaughlinGraph` by + considering the stabilizer of a point: one of its orbits has cardinality + 162. + + EXAMPLES:: + + sage: g = graphs.LocalMcLaughlinGraph(); g # long time # optional - gap_packages + Local McLaughlin Graph: Graph on 162 vertices + sage: g.is_strongly_regular(parameters=True) # long time # optional - gap_packages + (162, 56, 10, 24) + """ + g = McLaughlinGraph() + orbits = g.automorphism_group().stabilizer(1).orbits() + orbit = [x for x in orbits if len(x) == 162][0] + g = g.subgraph(vertices=orbit) + g.relabel() + g.name("Local McLaughlin Graph") + return g + def LjubljanaGraph(embedding=1): r""" Returns the Ljubljana Graph. diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index fbb3cc63af5..70b472363c6 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -242,6 +242,7 @@ :meth:`~GenericGraph.transitive_reduction` | Return a transitive reduction of a graph. :meth:`~GenericGraph.min_spanning_tree` | Return the edges of a minimum spanning tree. :meth:`~GenericGraph.spanning_trees_count` | Return the number of spanning trees in a graph. + :meth:`~GenericGraph.dominator_tree` | Returns a dominator tree of the graph. **Plot/embedding-related methods:** @@ -2494,15 +2495,27 @@ def allows_multiple_edges(self): """ return self._backend.multiple_edges(None) - def allow_multiple_edges(self, new, check=True): + def allow_multiple_edges(self, new, check=True, keep_label='any'): """ Changes whether multiple edges are permitted in the (di)graph. INPUT: - - ``new`` - boolean. + - ``new`` (boolean): if ``True``, the new graph will allow multiple + edges. - EXAMPLES:: + - ``check`` (boolean): if ``True`` and ``new`` is ``False``, we remove + all multiple edges from the graph. + + - ``keep_label`` (``'any','min','max'``): used only if ``new`` is + ``False`` and ``check`` is ``True``. If there are multiple edges with + different labels, this variable defines which label should be kept: + any label (``'any'``), the smallest label (``'min'``), or the largest + (``'max'``). + + EXAMPLES: + + The standard behavior with undirected graphs:: sage: G = Graph(multiedges=True,sparse=True); G Multi-graph on 0 vertices @@ -2510,17 +2523,33 @@ def allow_multiple_edges(self, new, check=True): False sage: G.allows_multiple_edges() True - sage: G.add_edges([(0,1)]*3) + sage: G.add_edges([(0,1,1), (0,1,2), (0,1,3)]) sage: G.has_multiple_edges() True sage: G.multiple_edges() - [(0, 1, None), (0, 1, None), (0, 1, None)] + [(0, 1, 1), (0, 1, 2), (0, 1, 3)] sage: G.allow_multiple_edges(False); G Graph on 2 vertices sage: G.has_multiple_edges() False sage: G.edges() - [(0, 1, None)] + [(0, 1, 1)] + + If we ask for the minimum label:: + + sage: G = Graph([(0, 1, 1), (0, 1, 2), (0, 1, 3)], multiedges=True,sparse=True) + sage: G.allow_multiple_edges(False, keep_label='min') + sage: G.edges() + [(0, 1, 1)] + + If we ask for the maximum label:: + + sage: G = Graph([(0, 1, 1), (0, 1, 2), (0, 1, 3)], multiedges=True,sparse=True) + sage: G.allow_multiple_edges(False, keep_label='max') + sage: G.edges() + [(0, 1, 3)] + + The standard behavior with digraphs:: sage: D = DiGraph(multiedges=True,sparse=True); D Multi-digraph on 0 vertices @@ -2540,15 +2569,28 @@ def allow_multiple_edges(self, new, check=True): sage: D.edges() [(0, 1, None)] """ - seen = set() + seen = dict() + + if not keep_label in ['any','min','max']: + raise ValueError("Variable keep_label must be 'any', 'min', or 'max'") # TODO: this should be much faster for c_graphs, but for now we just do this if self.allows_multiple_edges() and new is False and check: for u,v,l in self.multiple_edges(): - if (u,v) in seen: - self.delete_edge(u,v,l) + if not (u,v) in seen: + # This is the first time we see this edge + seen[(u,v)] = l else: - seen.add((u,v)) + # This edge has already been seen: we have to remove + # something from the graph. + oldl = seen[(u,v)] + if ((keep_label=='min' and loldl)): + # Keep the new edge, delete the old one + self.delete_edge((u,v,oldl)) + seen[(u,v)] = l + else: + # Delete the new edge + self.delete_edge((u,v,l)) self._backend.multiple_edges(new) @@ -3301,17 +3343,23 @@ def eulerian_circuit(self, return_vertices=False, labels=True, path=False): return edges def min_spanning_tree(self, - weight_function=lambda e: 1, + weight_function=None, algorithm="Kruskal", starting_vertex=None, check=False): r""" Returns the edges of a minimum spanning tree. + At the moment, no algorithm for directed graph is implemented: if the + graph is directed, a minimum spanning tree of the corresponding + undirected graph is returned. + INPUT: - ``weight_function`` -- A function that takes an edge and returns a - numeric weight. Defaults to assigning each edge a weight of 1. + 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). - ``algorithm`` -- The algorithm to use in computing a minimum spanning tree of ``G``. The default is to use Kruskal's algorithm. The @@ -3328,7 +3376,8 @@ def min_spanning_tree(self, implementation. - ``starting_vertex`` -- The vertex from which to begin the search - for a minimum spanning tree. + for a minimum spanning tree (available only for ``Prim_fringe`` and + ``Prim_edge``). - ``check`` -- Boolean; default: ``False``. Whether to first perform sanity checks on the input graph ``G``. If appropriate, ``check`` @@ -3355,7 +3404,7 @@ def min_spanning_tree(self, 4 sage: weight = lambda e: 1 / ((e[0] + 1) * (e[1] + 1)) sage: g.min_spanning_tree(weight_function=weight) - [(3, 4, None), (2, 4, None), (1, 4, None), (0, 4, None)] + [(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) @@ -3367,14 +3416,67 @@ def min_spanning_tree(self, sage: g = graphs.CompleteGraph(5) sage: g.min_spanning_tree(algorithm='Prim_edge', starting_vertex=2, weight_function=weight) - [(2, 4, None), (3, 4, None), (1, 4, None), (0, 4, None)] + [(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) - [(2, 4), (4, 3), (4, 1), (4, 0)] + [(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)] + + TESTS: + + Check that, if ``weight_function`` is not provided, then edge weights + are used:: + + sage: g = Graph(weighted=True) + 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='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='NetworkX') + [(0, 1, 1), (1, 2, 1)] + + Check that, if ``weight_function`` is provided, it overrides edge + weights:: + + sage: g = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True) + 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='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='NetworkX', weight_function=weight) + [(0, 2, 10), (1, 2, 1)] + + If the graph is directed, it is transformed into an undirected graph:: + + sage: g = digraphs.Circuit(3) + sage: g.min_spanning_tree(weight_function=weight) + [(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 - return kruskal(self, wfunction=weight_function, check=check) - elif algorithm == "Prim_fringe": + if self.is_directed(): + return kruskal(self.to_undirected(), wfunction=weight_function, check=check) + else: + return kruskal(self, wfunction=weight_function, check=check) + + if weight_function is None: + if self.weighted(): + weight_function = lambda e:self.edge_label(e[0], e[1]) + else: + weight_function = lambda e:1 + + if algorithm == "Prim_fringe": if starting_vertex is None: v = next(self.vertex_iterator()) else: @@ -3389,7 +3491,8 @@ def min_spanning_tree(self, for i in range(self.order() - 1): # find the smallest-weight fringe vertex u = min(fringe_list, key=cmp_fun) - edges.append((fringe_list[u][1], u)) + x = fringe_list[u][1] + edges.append((min(x,u), max(x,u), self.edge_label(x, u))) tree.add(u) fringe_list.pop(u) # update fringe list @@ -3397,7 +3500,8 @@ def min_spanning_tree(self, w = weight_function((u, neighbor)) if neighbor not in fringe_list or fringe_list[neighbor][0] > w: fringe_list[neighbor] = (w, u) - return edges + return sorted(edges) + elif algorithm == "Prim_edge": if starting_vertex is None: v = next(self.vertex_iterator()) @@ -3428,11 +3532,13 @@ def min_spanning_tree(self, break else: i += 1 - return edges + return sorted(edges) + elif algorithm == "NetworkX": import networkx G = networkx.Graph([(u, v, dict(weight=weight_function((u, v)))) for u, v, l in self.edge_iterator()]) - return list(networkx.mst(G)) + return sorted([(u,v,self.edge_label(u,v)) if u malloc( n * sizeof(unsigned short *)) - cdef unsigned short * distances = malloc( n*n * sizeof(unsigned short )) - cdef index_t * current = malloc( n * sizeof(index_t)) - cdef index_t * ordering = malloc( n * sizeof(index_t)) - cdef index_t * left_to_order = malloc( n * sizeof(index_t)) - cdef index_t * index_array_tmp = malloc( n * sizeof(index_t)) - cdef range_t * range_arrays = malloc( n*n * sizeof(range_t)) - cdef range_t ** ith_range_array = malloc( n * sizeof(range_t *)) - cdef range_t * range_array_tmp = malloc( n * sizeof(range_t)) - - if (d is NULL or - distances is NULL or - current is NULL or - ordering is NULL or - left_to_order is NULL or - index_array_tmp is NULL or - ith_range_array is NULL or - range_arrays is NULL or - range_array_tmp is NULL): - - free(d) - free(distances) - free(current) - free(ordering) - free(left_to_order) - free(index_array_tmp) - free(range_arrays) - free(ith_range_array) - free(range_array_tmp) - raise MemoryError + cdef MemoryAllocator mem = MemoryAllocator() + + cdef unsigned short ** d = mem.allocarray(n, sizeof(unsigned short *)) + cdef unsigned short * distances = mem.allocarray(n*n, sizeof(unsigned short )) + cdef index_t * current = mem.allocarray(n, sizeof(index_t)) + cdef index_t * ordering = mem.allocarray(n, sizeof(index_t)) + cdef index_t * left_to_order = mem.allocarray(n, sizeof(index_t)) + cdef index_t * index_array_tmp = mem.allocarray(n, sizeof(index_t)) + cdef range_t * range_arrays = mem.allocarray(n*n, sizeof(range_t)) + cdef range_t ** ith_range_array = mem.allocarray(n, sizeof(range_t *)) + cdef range_t * range_array_tmp = mem.allocarray(n, sizeof(range_t)) cdef int i,j,kk all_pairs_shortest_path_BFS(G,NULL,distances,NULL) # compute the distance matrix @@ -270,31 +251,19 @@ def bandwidth(G, k=None): for i in range(n): left_to_order[i] = i - try: - sig_on() - if k is None: - for kk in range((n-1)//G.diameter(),n): - if bandwidth_C(n,kk,d,current,ordering,left_to_order,index_array_tmp,ith_range_array,range_array_tmp): - ans = True - break - else: - ans = bool(bandwidth_C(n,k,d,current,ordering,left_to_order,index_array_tmp,ith_range_array,range_array_tmp)) - - if ans: - order = [int_to_vertex[ordering[i]] for i in range(n)] - - sig_off() - - finally: - free(d) - free(distances) - free(current) - free(ordering) - free(left_to_order) - free(index_array_tmp) - free(range_arrays) - free(ith_range_array) - free(range_array_tmp) + sig_on() + if k is None: + for kk in range((n-1)//G.diameter(),n): + if bandwidth_C(n,kk,d,current,ordering,left_to_order,index_array_tmp,ith_range_array,range_array_tmp): + ans = True + break + else: + ans = bool(bandwidth_C(n,k,d,current,ordering,left_to_order,index_array_tmp,ith_range_array,range_array_tmp)) + + if ans: + order = [int_to_vertex[ordering[i]] for i in range(n)] + + sig_off() if ans: ans = (kk, order) if k is None else order diff --git a/src/sage/graphs/graph_decompositions/cutwidth.pyx b/src/sage/graphs/graph_decompositions/cutwidth.pyx index 786329c908b..42b1ba1ece4 100644 --- a/src/sage/graphs/graph_decompositions/cutwidth.pyx +++ b/src/sage/graphs/graph_decompositions/cutwidth.pyx @@ -32,6 +32,7 @@ vertices. :meth:`cutwidth` | Return the cutwidth of the graph and the corresponding vertex ordering. :meth:`cutwidth_dyn` | Compute the cutwidth of `G` using an exponential time and space algorithm based on dynamic programming + :meth:`cutwidth_MILP` | Compute the cutwidth of `G` and the optimal ordering of its vertices using an MILP formulation :meth:`width_of_cut_decomposition` | Return the width of the cut decomposition induced by the linear ordering `L` of the vertices of `G` @@ -99,6 +100,54 @@ algorithm. but 32 vertices already require 4GB of memory. +MILP formulation for the cutwidth +--------------------------------- + +We describe a mixed integer linear program (MILP) for determining an +optimal layout for the cutwidth of `G`. + +**Variables:** + +- `x_v^k` -- Variable set to 1 if vertex `v` is placed in the ordering at + position `i` with `i\leq k`, and 0 otherwise. + +- `y_{u,v}^{k}` -- Variable set to 1 if one of `u` or `v` is at a position + `i\leq k` and the other is at a position `j>k`, and so we have to count edge + `uv` at position `k`. Otherwise, `y_{u,v}^{k}=0`. The value of `y_{u,v}^{k}` + is a xor of the values of `x_u^k` and `x_v^k`. + +- `z` -- Objective value to minimize. It is equal to the maximum over all + position `k` of the number of edges with one extremity at position at most `k` + and the other at position stricly more than `k`, that is `\sum_{uv\in + E}y_{u,v}^{k}`. + + +**MILP formulation:** + +.. MATH:: + :nowrap: + + \begin{alignat*}{2} + \intertext{Minimize:} + &z&\\ + \intertext{Subject to:} + \sum_{i=0}^{k-1}x_v^i &\leq k*x_v^{k} & \forall v\in V,\ k\in[1,n-1] \quad(1)\\ + x_v^n & =1 & \quad \forall v\in V \quad(2)\\ + \sum_{v\in V}x_v^k & = k+1 &\quad \forall k\in[0,n-1] \quad(3)\\ + x_u^k - x_v^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(4)\\ + x_v^k - x_u^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(5)\\ + \sum_{uv\in E}y_{u,v}^k &\leq z &\quad \forall k\in[0,n-1] \quad(6)\\ + 0 \leq z &\leq |E| + \end{alignat*} + +Constraints (1)-(3) ensure that all vertices have a distinct position. +Constraints (4)-(5) force variable `y_{u,v}^k` to 1 if the edge is in the cut. +Constraint (6) count the number of edges starting at position at most `k` and +ending at a position stricly larger than `k`. + +This formulation corresponds to method :meth:`cutwidth_MILP`. + + Authors ------- @@ -183,6 +232,8 @@ def width_of_cut_decomposition(G, L): """ if not is_valid_ordering(G, L): raise ValueError("The input linear vertex ordering L is not valid for G.") + elif G.order()<=1: + return 0 position = {u:i for i,u in enumerate(L)} @@ -208,7 +259,7 @@ def width_of_cut_decomposition(G, L): # Front end method for cutwidth ################################################################################ -def cutwidth(G, algorithm="exponential", cut_off=0): +def cutwidth(G, algorithm="exponential", cut_off=0, solver=None, verbose=False): r""" Return the cutwidth of the graph and the corresponding vertex ordering. @@ -223,10 +274,24 @@ def cutwidth(G, algorithm="exponential", cut_off=0): dynamic programming. This algorithm only works on graphs with strictly less than 32 vertices. + - ``MILP`` -- Use a mixed integer linear programming formulation. This + algorithm has no size restriction but could take a very long time. + - ``cut_off`` -- (default: 0) This parameter is used to stop the search as soon as a solution with width at most ``cut_off`` is found, if any. If this bound cannot be reached, the best solution found is returned. + - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to + be used. If set to ``None``, the default one is used. This parameter is + used only when ``algorithm='MILP'``. For more information on LP solvers + and which default solver is used, see the method + :meth:`solve` of the + class + :class:`MixedIntegerLinearProgram`. + + - ``verbose`` (boolean) -- whether to display information on the + computations. + OUTPUT: A pair ``(cost, ordering)`` representing the optimal ordering of the @@ -238,30 +303,40 @@ def cutwidth(G, algorithm="exponential", cut_off=0): sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth sage: G = graphs.CompleteGraph(5) - sage: cw,L = cutwidth(G, algorithm="exponential"); cw + sage: cw,L = cutwidth(G); cw 6 sage: K = graphs.CompleteGraph(6) - sage: cw,L = cutwidth(K, algorithm="exponential"); cw + sage: cw,L = cutwidth(K); cw 9 - sage: cw,L = cutwidth(K+K, algorithm="exponential"); cw + sage: cw,L = cutwidth(K+K); cw 9 The cutwidth of a `p\times q` Grid Graph with `p\leq q` is `p+1`:: sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth sage: G = graphs.Grid2dGraph(3,3) - sage: cw,L = cutwidth(G, algorithm="exponential"); cw + sage: cw,L = cutwidth(G); cw 4 sage: G = graphs.Grid2dGraph(3,5) - sage: cw,L = cutwidth(G, algorithm="exponential"); cw + sage: cw,L = cutwidth(G); cw 4 TESTS: + Comparison of algorithms:: + + sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth + sage: for i in range(2): # long test + ....: G = graphs.RandomGNP(7, 0.3) + ....: ve, le = cutwidth(G, algorithm="exponential") + ....: vm, lm = cutwidth(G, algorithm="MILP", solver='GLPK') + ....: if ve != vm: + ....: print "Something goes wrong!" + Given a wrong algorithm:: sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth - sage: cutwidth(Graph(), algorithm="SuperFast") + sage: cutwidth(graphs.PathGraph(2), algorithm="SuperFast") Traceback (most recent call last): ... ValueError: Algorithm "SuperFast" has not been implemented yet. Please contribute. @@ -289,6 +364,9 @@ def cutwidth(G, algorithm="exponential", cut_off=0): if not cut_off in ZZ: raise ValueError("The specified cut off parameter must be an integer.") + elif G.size() <= cut_off: + # We have a trivial solution + return width_of_cut_decomposition(G, G.vertices()), G.vertices() if not G.is_connected(): # The graph has several connected components. We solve the problem on @@ -297,16 +375,17 @@ def cutwidth(G, algorithm="exponential", cut_off=0): cw, L = 0, [] for V in G.connected_components(): - if len(V)==1: - # We can directly add this vertex to the solution + # We build the connected subgraph + H = G.subgraph(V) + if H.size() <= max(cw, cut_off): + # We can directly add these vertices to the solution L.extend(V) else: - # We build the connected subgraph and do a recursive call to get - # its cutwidth and corresponding ordering - H = G.subgraph(V) + # We do a recursive call on H cwH,LH = cutwidth(H, algorithm = algorithm, - cut_off = max(cut_off,cw)) + cut_off = max(cut_off,cw), + solver = solver, verbose = verbose) # We update the cutwidth and ordering cw = max(cw, cwH) @@ -318,6 +397,9 @@ def cutwidth(G, algorithm="exponential", cut_off=0): if algorithm == "exponential": return cutwidth_dyn(G, lower_bound=cut_off) + elif algorithm == "MILP": + return cutwidth_MILP(G, lower_bound=cut_off, solver=solver, verbose=verbose) + else: raise ValueError('Algorithm "{}" has not been implemented yet. Please contribute.'.format(algorithm)) @@ -478,3 +560,157 @@ cdef inline int exists(FastDigraph g, uint8_t * neighborhoods, int S, int cost_S neighborhoods[current] = mini return neighborhoods[current] + +################################################################################ +# MILP formulations for cutwidth +################################################################################ + +def cutwidth_MILP(G, lower_bound=0, solver=None, verbose=0): + r""" + MILP formulation for the cutwidth of a Graph. + + This method uses a mixed integer linear program (MILP) for determining an + optimal layout for the cutwidth of `G`. See the :mod:`module's documentation + ` for more details on this MILP + formulation. + + INPUT: + + - ``G`` -- a Graph + + - ``lower_bound`` -- (default: 0) the algorithm searches for a solution with + cost larger or equal to ``lower_bound``. If the given bound is larger than + the optimal solution the returned solution might not be optimal. If the + given bound is too high, the algorithm might not be able to find a + feasible solution. + + - ``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`. + + - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set + to 0 by default, which means quiet. + + OUTPUT: + + A pair ``(cost, ordering)`` representing the optimal ordering of the + vertices and its cost. + + EXAMPLE: + + Cutwidth of a Cycle graph:: + + sage: from sage.graphs.graph_decompositions import cutwidth + sage: G = graphs.CycleGraph(5) + sage: cw, L = cutwidth.cutwidth_MILP(G); cw + 2 + sage: cw == cutwidth.width_of_cut_decomposition(G, L) + True + sage: cwe, Le = cutwidth.cutwidth_dyn(G); cwe + 2 + + Cutwidth of a Complete graph:: + + sage: from sage.graphs.graph_decompositions import cutwidth + sage: G = graphs.CompleteGraph(4) + sage: cw, L = cutwidth.cutwidth_MILP(G); cw + 4 + sage: cw == cutwidth.width_of_cut_decomposition(G, L) + True + + Cutwidth of a Path graph:: + + sage: from sage.graphs.graph_decompositions import cutwidth + sage: G = graphs.PathGraph(3) + sage: cw, L = cutwidth.cutwidth_MILP(G); cw + 1 + sage: cw == cutwidth.width_of_cut_decomposition(G, L) + True + + TESTS: + + Comparison with exponential algorithm:: + + sage: from sage.graphs.graph_decompositions import cutwidth + sage: for i in range(2): # long test + ....: G = graphs.RandomGNP(7, 0.3) + ....: ve, le = cutwidth.cutwidth_dyn(G) + ....: vm, lm = cutwidth.cutwidth_MILP(G, solver='GLPK') + ....: if ve != vm: + ....: print "The solution is not optimal!" + + Giving a too large lower bound:: + + sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP + sage: G = graphs.CycleGraph(3) + sage: cutwidth_MILP(G, lower_bound=G.size()+1) + Traceback (most recent call last): + ... + MIPSolverException: ... + + Giving anything else than a Graph:: + + sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP + sage: cutwidth_MILP([]) + Traceback (most recent call last): + ... + ValueError: The first input parameter must be a Graph. + """ + from sage.graphs.graph import Graph + if not isinstance(G, Graph): + raise ValueError("The first input parameter must be a Graph.") + + from sage.numerical.mip import MixedIntegerLinearProgram + p = MixedIntegerLinearProgram( maximization = False, solver = solver ) + + # Declaration of variables. + x = p.new_variable(binary=True, nonnegative=True) + y = p.new_variable(binary=True, nonnegative=True) + z = p.new_variable(integer=True, nonnegative=True) + + N = G.order() + V = G.vertices() + + # All vertices at different positions + for v in V: + for k in range(N-1): + p.add_constraint( p.sum( x[v,i] for i in range(k) ) <= k*x[v,k] ) + p.add_constraint( x[v,N-1] == 1 ) + for k in range(N): + p.add_constraint( p.sum( x[v,k] for v in V ) == k+1 ) + + # Edge uv counts at position i if one of u or v is placed at a position in + # [0,i] and the other is placed at a position in [i+1,n]. + for u,v in G.edge_iterator(labels=None): + for i in range(N): + p.add_constraint( x[u,i] - x[v,i] <= y[u,v,i] ) + p.add_constraint( x[v,i] - x[u,i] <= y[u,v,i] ) + + # Lower bound on the solution + p.add_constraint( lower_bound <= z['z'] ) + + # Objective + p.add_constraint( z['z'] <= G.size() ) + for i in range(N): + p.add_constraint( p.sum( y[u,v,i] for u,v in G.edge_iterator(labels=None) ) <= z['z'] ) + + p.set_objective( z['z'] ) + + obj = p.solve( log=verbose ) + + # We now extract the ordering and the cost of the solution + cw = int( p.get_values(z)['z'] ) + val_x = p.get_values( x ) + seq = [] + to_see = set(V) + for k in range(N): + for u in to_see: + if val_x[u,k] > 0: + seq.append(u) + to_see.discard(u) + break + + return cw, seq diff --git a/src/sage/graphs/graph_decompositions/vertex_separation.pyx b/src/sage/graphs/graph_decompositions/vertex_separation.pyx index 426df49f86a..0bff2eb3a5d 100644 --- a/src/sage/graphs/graph_decompositions/vertex_separation.pyx +++ b/src/sage/graphs/graph_decompositions/vertex_separation.pyx @@ -762,7 +762,7 @@ def vertex_separation_exp(G, verbose = False): sage_free(neighborhoods) sig_off() - + return k, list( g.int_to_vertices[i] for i in order ) ############################################################################## diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index df7f8ef1a19..f251749bbd6 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -99,12 +99,14 @@ def __append_to_doc(methods): "ClebschGraph", "CoxeterGraph", "DesarguesGraph", + "DejterGraph", "DoubleStarSnark", "DurerGraph", "DyckGraph", "EllinghamHorton54Graph", "EllinghamHorton78Graph", "ErreraGraph", + "F26AGraph", "FlowerSnark", "FolkmanGraph", "FosterGraph", @@ -115,6 +117,7 @@ def __append_to_doc(methods): "GrayGraph", "GrotzschGraph", "HallJankoGraph", + "HarborthGraph", "HarriesGraph", "HarriesWongGraph", "HeawoodGraph", @@ -126,6 +129,9 @@ def __append_to_doc(methods): "HortonGraph", "KittellGraph", "KrackhardtKiteGraph", + "Klein3RegularGraph", + "Klein7RegularGraph", + "LocalMcLaughlinGraph", "LjubljanaGraph", "LivingstoneGraph", "M22Graph", @@ -1850,6 +1856,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None ChvatalGraph = staticmethod(sage.graphs.generators.smallgraphs.ChvatalGraph) ClebschGraph = staticmethod(sage.graphs.generators.smallgraphs.ClebschGraph) CoxeterGraph = staticmethod(sage.graphs.generators.smallgraphs.CoxeterGraph) + DejterGraph = staticmethod(sage.graphs.generators.smallgraphs.DejterGraph) DesarguesGraph = staticmethod(sage.graphs.generators.smallgraphs.DesarguesGraph) DoubleStarSnark = staticmethod(sage.graphs.generators.smallgraphs.DoubleStarSnark) DurerGraph = staticmethod(sage.graphs.generators.smallgraphs.DurerGraph) @@ -1857,6 +1864,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None EllinghamHorton54Graph = staticmethod(sage.graphs.generators.smallgraphs.EllinghamHorton54Graph) EllinghamHorton78Graph = staticmethod(sage.graphs.generators.smallgraphs.EllinghamHorton78Graph) ErreraGraph = staticmethod(sage.graphs.generators.smallgraphs.ErreraGraph) + F26AGraph = staticmethod(sage.graphs.generators.smallgraphs.F26AGraph) FlowerSnark = staticmethod(sage.graphs.generators.smallgraphs.FlowerSnark) FolkmanGraph = staticmethod(sage.graphs.generators.smallgraphs.FolkmanGraph) FosterGraph = staticmethod(sage.graphs.generators.smallgraphs.FosterGraph) @@ -1868,6 +1876,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None GrotzschGraph = staticmethod(sage.graphs.generators.smallgraphs.GrotzschGraph) HallJankoGraph = staticmethod(sage.graphs.generators.smallgraphs.HallJankoGraph) WellsGraph = staticmethod(sage.graphs.generators.smallgraphs.WellsGraph) + HarborthGraph = staticmethod(sage.graphs.generators.smallgraphs.HarborthGraph) HarriesGraph = staticmethod(sage.graphs.generators.smallgraphs.HarriesGraph) HarriesWongGraph = staticmethod(sage.graphs.generators.smallgraphs.HarriesWongGraph) HeawoodGraph = staticmethod(sage.graphs.generators.smallgraphs.HeawoodGraph) @@ -1879,6 +1888,9 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None HortonGraph = staticmethod(sage.graphs.generators.smallgraphs.HortonGraph) KittellGraph = staticmethod(sage.graphs.generators.smallgraphs.KittellGraph) KrackhardtKiteGraph = staticmethod(sage.graphs.generators.smallgraphs.KrackhardtKiteGraph) + Klein3RegularGraph = staticmethod(sage.graphs.generators.smallgraphs.Klein3RegularGraph) + Klein7RegularGraph = staticmethod(sage.graphs.generators.smallgraphs.Klein7RegularGraph) + LocalMcLaughlinGraph = staticmethod(sage.graphs.generators.smallgraphs.LocalMcLaughlinGraph) LjubljanaGraph = staticmethod(sage.graphs.generators.smallgraphs.LjubljanaGraph) LivingstoneGraph = staticmethod(sage.graphs.generators.smallgraphs.LivingstoneGraph) M22Graph = staticmethod(sage.graphs.generators.smallgraphs.M22Graph) diff --git a/src/sage/graphs/hyperbolicity.pyx b/src/sage/graphs/hyperbolicity.pyx index 7005f20d623..b55a1d890d1 100644 --- a/src/sage/graphs/hyperbolicity.pyx +++ b/src/sage/graphs/hyperbolicity.pyx @@ -170,8 +170,9 @@ from sage.rings.real_mpfr import RR from sage.functions.other import floor from sage.data_structures.bitset import Bitset from sage.ext.memory cimport check_allocarray, check_calloc +from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport short_digraph -from sage.graphs.base.static_sparse_graph cimport init_short_digraph +from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph from libc.stdint cimport uint16_t, uint32_t, uint64_t include "sage/ext/interrupt.pxi" @@ -400,19 +401,15 @@ cdef inline distances_and_far_apart_pairs(gg, "pairs on something" "like that!".format( -1)) + # The list of waiting vertices + cdef MemoryAllocator mem = MemoryAllocator() + cdef uint32_t * waiting_list = mem.allocarray(n, sizeof(uint32_t)) + cdef unsigned short ** c_far_apart = mem.allocarray(n, sizeof(unsigned short*)) + # The vertices which have already been visited cdef bitset_t seen bitset_init(seen, n) - # The list of waiting vertices - cdef uint32_t * waiting_list = check_allocarray(n, sizeof(uint32_t)) - cdef unsigned short ** c_far_apart = check_allocarray(n, sizeof(unsigned short*)) - if waiting_list == NULL or c_far_apart == NULL: - bitset_free(seen) - sage_free(waiting_list) - sage_free(c_far_apart) - raise MemoryError - # the beginning and the end of the list stored in waiting_list cdef uint32_t waiting_beginning, waiting_end @@ -484,14 +481,10 @@ cdef inline distances_and_far_apart_pairs(gg, c_distances += n bitset_free(seen) - sage_free(waiting_list) free_short_digraph(sd) - sage_free(c_far_apart) - - -cdef inline pair** sort_pairs(uint32_t N, - uint16_t D, +cdef inline pair** sort_pairs(uint32_t N, + uint16_t D, unsigned short ** values, unsigned short ** to_include, uint32_t * nb_p, diff --git a/src/sage/graphs/mcqd.pyx b/src/sage/graphs/mcqd.pyx index 646911a9daf..762d8b5936c 100644 --- a/src/sage/graphs/mcqd.pyx +++ b/src/sage/graphs/mcqd.pyx @@ -1,5 +1,6 @@ include "sage/ext/interrupt.pxi" include 'sage/ext/stdsage.pxi' +from sage.ext.memory_allocator cimport MemoryAllocator def mcqd(G): """ @@ -22,15 +23,10 @@ def mcqd(G): # - c0 is the adjacency matrix # - c points toward each row of the matrix # - qmax stores the max clique - cdef bool ** c = sage_malloc(n*sizeof(bool *)) - cdef bool * c0 = sage_calloc(n*n,sizeof(bool)) - cdef int * qmax = sage_malloc(n*sizeof(int)) - sage_free(NULL) - if c == NULL or c0 == NULL or qmax == NULL: - sage_free(c) - sage_free(c0) - sage_free(qmax) - raise MemoryError("Allocation Failed") + cdef MemoryAllocator mem = MemoryAllocator() + cdef bool ** c = mem.allocarray(n, sizeof(bool *)) + cdef bool * c0 = mem.calloc(n*n, sizeof(bool)) + cdef int * qmax = mem.allocarray(n, sizeof(int)) c[0] = c0 @@ -57,9 +53,6 @@ def mcqd(G): # Returns the answer cdef list answer = [vertices[qmax[i]] for i in range(clique_number)] - sage_free(c[0]) - sage_free(c) - sage_free(qmax) del C return answer diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index b6fdd840941..ae83909a70f 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -78,30 +78,19 @@ cpdef kruskal(G, wfunction=None, bint check=False): Minimum spanning tree using Kruskal's algorithm. This function assumes that we can only compute minimum spanning trees for - undirected simple graphs. Such graphs can be weighted or unweighted. + undirected graphs. Such graphs can be weighted or unweighted, and they can + have multiple edges (since we are computing the minimum spanning tree, only + the minimum weight among all `(u,v)`-edges is considered, for each pair + of vertices `u`, `v`). INPUT: - - ``G`` -- A graph. This can be an undirected graph, a digraph, a - multigraph, or a multidigraph. Note the following behaviours: - - - If ``G`` is unweighted, then consider the simple version of ``G`` - with all self-loops and multiple edges removed. - - - If ``G`` is directed, then we only consider its undirected version. - - - If ``G`` is weighted, we ignore all of its self-loops. Note that a - weighted graph should only have numeric weights. You cannot assign - numeric weights to some edges of ``G``, but have ``None`` as a - weight for some other edge. If your input graph is weighted, you are - responsible for assign numeric weight to each of its edges. - Furthermore, we remove multiple edges as follows. First we convert - ``G`` to be undirected. Suppose there are multiple edges from `u` to - `v`. Among all such multiple edges, we choose one with minimum weight. + - ``G`` -- an undirected graph. - ``wfunction`` -- A weight function: a function that takes an edge and - returns a numeric weight. Default: ``None``. The default is to - assign each edge a weight of 1. + 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). - ``check`` -- Whether to first perform sanity checks on the input graph ``G``. Default: ``check=False``. If we toggle ``check=True``, the @@ -111,38 +100,24 @@ cpdef kruskal(G, wfunction=None, bint check=False): - Is ``G`` the null graph? - Is ``G`` disconnected? - Is ``G`` a tree? - - Is ``G`` directed? - Does ``G`` have self-loops? - Does ``G`` have multiple edges? - - Is ``G`` weighted? By default, we turn off the sanity checks for performance reasons. This means that by default the function assumes that its input graph is - simple, connected, is not a tree, and has at least one vertex. - If the input graph does not satisfy all of the latter conditions, you - should set ``check=True`` to perform some sanity checks and - preprocessing on the input graph. To further improve the runtime of this - function, you should call it directly instead of using it indirectly - via :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`. + connected, and has at least one vertex. Otherwise, you should set + ``check=True`` to perform some sanity checks and preprocessing on the + input graph. If ``G`` has multiple edges or self-loops, the algorithm + still works, but the running-time can be improved if these edges are + removed. To further improve the runtime of this function, you should call + it directly instead of using it indirectly via + :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`. OUTPUT: The edges of a minimum spanning tree of ``G``, if one exists, otherwise returns the empty list. - - If ``G`` is a tree, return the edges of ``G`` regardless of whether - ``G`` is weighted or unweighted, directed or undirected. - - - If ``G`` is unweighted, default to using unit weight for each edge of - ``G``. The default behaviour is to use the already assigned weights of - ``G`` provided that ``G`` is weighted. - - - If ``G`` is weighted and a weight function is also supplied, then use - the already assigned weights of ``G``, not the weight function. If you - really want to use a weight function for ``G`` even if ``G`` is - weighted, first convert ``G`` to be unweighted and pass in the weight - function. - .. seealso:: - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree` @@ -155,16 +130,13 @@ cpdef kruskal(G, wfunction=None, bint check=False): sage: G = Graph({1:{2:28, 6:10}, 2:{3:16, 7:14}, 3:{4:12}, 4:{5:22, 7:18}, 5:{6:25, 7:24}}) sage: G.weighted(True) sage: E = kruskal(G, check=True); E - [(1, 6, 10), (3, 4, 12), (2, 7, 14), (2, 3, 16), (4, 5, 22), (5, 6, 25)] + [(1, 6, 10), (2, 3, 16), (2, 7, 14), (3, 4, 12), (4, 5, 22), (5, 6, 25)] Variants of the previous example. :: sage: H = Graph(G.edges(labels=False)) sage: kruskal(H, check=True) [(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)] - sage: H = DiGraph(G.edges(labels=False)) - sage: kruskal(H, check=True) - [(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None), (3, 4, None), (4, 5, None)] sage: G.allow_loops(True) sage: G.allow_multiple_edges(True) sage: G @@ -193,29 +165,6 @@ cpdef kruskal(G, wfunction=None, bint check=False): sage: kruskal(G, check=True) == kruskal(H, check=True) True - Note that we only consider an undirected version of the input graph. Thus - if ``G`` is a weighted multidigraph and ``H`` is an undirected version of - ``G``, then this function should return the same minimum spanning tree - for both ``G`` and ``H``. :: - - sage: from sage.graphs.spanning_tree import kruskal - sage: G = DiGraph({1:{2:[1,14,28], 6:[10]}, 2:{3:[16], 1:[15], 7:[14], 5:[20,21]}, 3:{4:[12,11]}, 4:{3:[13,3], 5:[22], 7:[18]}, 5:{6:[25], 7:[24], 2:[1,3]}}, multiedges=True) - sage: G.multiple_edges(to_undirected=False) - [(1, 2, 1), (1, 2, 14), (1, 2, 28), (5, 2, 1), (5, 2, 3), (4, 3, 3), (4, 3, 13), (3, 4, 11), (3, 4, 12), (2, 5, 20), (2, 5, 21)] - sage: H = G.to_undirected() - sage: H.multiple_edges(to_undirected=True) - [(1, 2, 1), (1, 2, 14), (1, 2, 15), (1, 2, 28), (2, 5, 1), (2, 5, 3), (2, 5, 20), (2, 5, 21), (3, 4, 3), (3, 4, 11), (3, 4, 12), (3, 4, 13)] - sage: kruskal(G, check=True) - [(1, 2, 1), (1, 6, 10), (2, 3, 16), (2, 5, 1), (2, 7, 14), (3, 4, 3)] - sage: kruskal(G, check=True) == kruskal(H, check=True) - True - sage: G.weighted(True) - sage: H.weighted(True) - sage: kruskal(G, check=True) - [(1, 2, 1), (2, 5, 1), (3, 4, 3), (1, 6, 10), (2, 7, 14), (2, 3, 16)] - sage: kruskal(G, check=True) == kruskal(H, check=True) - True - An example from pages 599--601 in [GoodrichTamassia2001]_. :: sage: G = Graph({"SFO":{"BOS":2704, "ORD":1846, "DFW":1464, "LAX":337}, @@ -228,7 +177,7 @@ cpdef kruskal(G, wfunction=None, bint check=False): ... "BWI":{"MIA":946}}) sage: G.weighted(True) sage: kruskal(G, check=True) - [('JFK', 'PVD', 144), ('BWI', 'JFK', 184), ('BOS', 'JFK', 187), ('LAX', 'SFO', 337), ('BWI', 'ORD', 621), ('DFW', 'ORD', 802), ('BWI', 'MIA', 946), ('DFW', 'LAX', 1235)] + [('BOS', 'JFK', 187), ('BWI', 'JFK', 184), ('BWI', 'MIA', 946), ('BWI', 'ORD', 621), ('DFW', 'LAX', 1235), ('DFW', 'ORD', 802), ('JFK', 'PVD', 144), ('LAX', 'SFO', 337)] An example from pages 568--569 in [CormenEtAl2001]_. :: @@ -237,7 +186,18 @@ cpdef kruskal(G, wfunction=None, bint check=False): ... "e":{"f":10}, "f":{"g":2}, "g":{"h":1, "i":6}, "h":{"i":7}}) sage: G.weighted(True) sage: kruskal(G, check=True) - [('g', 'h', 1), ('c', 'i', 2), ('f', 'g', 2), ('a', 'b', 4), ('c', 'f', 4), ('c', 'd', 7), ('a', 'h', 8), ('d', 'e', 9)] + [('a', 'b', 4), ('a', 'h', 8), ('c', 'd', 7), ('c', 'f', 4), ('c', 'i', 2), ('d', 'e', 9), ('f', 'g', 2), ('g', 'h', 1)] + + An example with custom edge labels:: + + sage: G = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True) + sage: weight = lambda e:3-e[0]-e[1] + sage: kruskal(G, check=True) + [(0, 1, 1), (1, 2, 1)] + sage: kruskal(G, wfunction=weight, check=True) + [(0, 2, 10), (1, 2, 1)] + sage: kruskal(G, wfunction=weight, check=False) + [(0, 2, 10), (1, 2, 1)] TESTS: @@ -254,14 +214,6 @@ cpdef kruskal(G, wfunction=None, bint check=False): [] sage: kruskal(Graph(multiedges=True, loops=True), check=True) [] - sage: kruskal(DiGraph(), check=True) - [] - sage: kruskal(DiGraph(multiedges=True), check=True) - [] - sage: kruskal(DiGraph(loops=True), check=True) - [] - sage: kruskal(DiGraph(multiedges=True, loops=True), check=True) - [] The input graph must be connected. :: @@ -293,23 +245,28 @@ cpdef kruskal(G, wfunction=None, bint check=False): sage: G = my_disconnected_graph(100, 50, directed=False, multiedges=True, loops=True) # long time sage: kruskal(G, check=True) # long time [] - sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=False, loops=False) # long time - sage: kruskal(G, check=True) # long time - [] - sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=False) # long time - sage: kruskal(G, check=True) # long time - [] - sage: G = my_disconnected_graph(100, 50, directed=True, multiedges=True, loops=True) # long time - sage: kruskal(G, check=True) # long time - [] If the input graph is a tree, then return its edges. :: sage: T = graphs.RandomTree(randint(1, 50)) # long time sage: T.edges() == kruskal(T, check=True) # long time True + + If the input is not a Graph:: + + sage: kruskal("I am not a graph") + Traceback (most recent call last): + ... + ValueError: The input G must be an undirected graph. + sage: kruskal(digraphs.Path(10)) + Traceback (most recent call last): + ... + ValueError: The input G must be an undirected graph. """ - g = G + from sage.graphs.graph import Graph + if not isinstance(G, Graph): + raise ValueError("The input G must be an undirected graph.") + sortedE_iter = None # sanity checks if check: @@ -321,56 +278,23 @@ cpdef kruskal(G, wfunction=None, bint check=False): if G.num_verts() == G.num_edges() + 1: # G is a tree return G.edges() - g = G.to_undirected() - g.allow_loops(False) + g = G.to_simple(to_undirected=False, keep_label='min') + 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: if g.weighted(): - # If there are multiple edges from u to v, retain the edge of - # minimum weight among all such edges. - if g.allows_multiple_edges(): - # By this stage, g is assumed to be an undirected, weighted - # multigraph. Thus when we talk about a weighted multiedge - # (u, v, w) of g, we mean that (u, v, w) and (v, u, w) are - # one and the same undirected multiedge having the same weight - # w. - # If there are multiple edges from u to v, retain only the - # start and end vertices of such edges. Let a and b be the - # start and end vertices, respectively, of a weighted edge - # (a, b, w) having weight w. Then there are multiple weighted - # edges from a to b if and only if the set uniqE has the - # tuple (a, b) as an element. - uniqE = set() - for u, v, _ in iter(g.multiple_edges(to_undirected=True)): - uniqE.add((u, v)) - # Let (u, v) be an element in uniqE. Then there are multiple - # weighted edges from u to v. Let W be a list of all edge - # weights of multiple edges from u to v, sorted in - # nondecreasing order. If w is the first element in W, then - # (u, v, w) is an edge of minimum weight (there may be - # several edges of minimum weight) among all weighted edges - # from u to v. If i >= 2 is the i-th element in W, delete the - # multiple weighted edge (u, v, i). - for u, v in uniqE: - W = sorted(g.edge_label(u, v)) - for w in W[1:]: - g.delete_edge(u, v, w) - # all multiple edges should now be removed; check this! - assert g.multiple_edges() == [] - g.allow_multiple_edges(False) - # sort edges by weights from operator import itemgetter sortedE_iter = iter(sorted(g.edges(), key=itemgetter(2))) else: - g = g.to_simple() - if wfunction is None: - sortedE_iter = iter(sorted(g.edges())) - else: - sortedE_iter = iter(sorted(g.edges(), key=wfunction)) - # G is assumed to be simple, undirected, and unweighted - else: - if wfunction is None: sortedE_iter = iter(sorted(g.edges())) - else: - sortedE_iter = iter(sorted(g.edges(), key=wfunction)) + else: + sortedE_iter = iter(sorted(g.edges(), key=wfunction)) + + # Kruskal's algorithm T = [] cdef int n = g.order() @@ -402,7 +326,7 @@ cpdef kruskal(G, wfunction=None, bint check=False): T.append(e) # union the components by making one the parent of the other union_find[components[0]] = components[1] - return T + return sorted(T) def random_spanning_tree(self, output_as_graph=False): diff --git a/src/sage/groups/libgap_wrapper.pyx b/src/sage/groups/libgap_wrapper.pyx index f4db5acfeff..cab610c51fd 100644 --- a/src/sage/groups/libgap_wrapper.pyx +++ b/src/sage/groups/libgap_wrapper.pyx @@ -563,6 +563,11 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): TESTS:: sage: G. = FreeGroup('a, b') + sage: G_gap = G.gap() + sage: G_gap == G_gap # indirect doctest + True + sage: cmp(G.gap(), G.gap()) # indirect doctest + 0 sage: x = G([1, 2, -1, -2]) sage: y = G([2, 2, 2, 1, -2, -2, -2]) sage: x == x*y*y^(-1) # indirect doctest @@ -575,35 +580,6 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): return cmp((left)._libgap, (right)._libgap) - def __richcmp__(left, right, int op): - """ - Boilerplate for Cython elements - - See :mod:`~sage.structure.element` for details. - - EXAMPLES:: - - sage: G. = FreeGroup('a, b') - sage: G_gap = G.gap() - sage: G_gap == G_gap # indirect doctest - True - """ - return (left)._richcmp(right, op) - - def __cmp__(left, right): - """ - Boilerplate for Cython elements - - See :mod:`~sage.structure.element` for details. - - EXAMPLES:: - - sage: G. = FreeGroup('a, b') - sage: cmp(G.gap(), G.gap()) # indirect doctest - 0 - """ - return (left)._cmp(right) - cpdef MultiplicativeGroupElement _div_(left, MultiplicativeGroupElement right): """ Division of group elements. diff --git a/src/sage/groups/perm_gps/cubegroup.py b/src/sage/groups/perm_gps/cubegroup.py index 49a7a3b69e3..9b5aadc31db 100644 --- a/src/sage/groups/perm_gps/cubegroup.py +++ b/src/sage/groups/perm_gps/cubegroup.py @@ -726,8 +726,7 @@ def parse(self, mv, check=True): state_facets = state_facets + r state0 = self.faces("") state0_facets = [] - keyss = state0.keys() - keyss.sort() + keyss = sorted(state0.keys()) for k in keyss: r = state0[k][0]+state0[k][1]+state0[k][2] r.remove(0) diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 48115bce95a..162c4426766 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -1321,8 +1321,7 @@ def stabilizer(self, point, action="OnPoints"): if action == "OnPoints": point = self._domain_to_gap[point] else: - point = [self._domain_to_gap[x] for x in point] - point.sort() + point = sorted([self._domain_to_gap[x] for x in point]) except KeyError as x: raise ValueError("{} does not belong to the domain".format(x)) @@ -3153,8 +3152,7 @@ def cosets(self, S, side='right'): raise ValueError("%s is not a subgroup of %s" % (S, self)) group = sorted(copy(self.list())) - subgroup = [self(s) for s in S.list()] - subgroup.sort() + subgroup = sorted([self(s) for s in S.list()]) decomposition = [] while group: rep = group[0] diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 5e5fa6313b9..271f1f1722c 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -665,12 +665,6 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): return 1 return 0 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __cmp__(left, right): - return (left)._cmp(right) - def __call__(self, i): """ Returns the image of the integer i under this permutation. diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 9d37c85a0fc..daab932818e 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -2644,7 +2644,7 @@ def ramification_module_decomposition_hurwitz_curve(self): EXAMPLES:: sage: G = PSL(2,13) - sage: G.ramification_module_decomposition_hurwitz_curve() # random, optional - database_gap + sage: G.ramification_module_decomposition_hurwitz_curve() # random, optional - database_gap gap_packages [0, 7, 7, 12, 12, 12, 13, 15, 14] This means, for example, that the trivial representation does not @@ -2692,7 +2692,7 @@ def ramification_module_decomposition_modular_curve(self): EXAMPLES:: sage: G = PSL(2,7) - sage: G.ramification_module_decomposition_modular_curve() # random, optional - database_gap + sage: G.ramification_module_decomposition_modular_curve() # random, optional - database_gap gap_packages [0, 4, 3, 6, 7, 8] This means, for example, that the trivial representation does not diff --git a/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx b/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx index d54d7cd9e87..7f73bd0f00c 100644 --- a/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx +++ b/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx @@ -245,7 +245,7 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): return "(%s; %s, %s)"%(self.v, self.perm.cycle_string(), self.get_autom()) - def __cmp__(self, right): + cpdef int _cmp_(left, Element _right) except -2: """ Compare group elements ``self`` and ``right``. @@ -258,9 +258,6 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): sage: g[1] != g[2] # indirect doctest True """ - return ( self)._cmp(right) - - cpdef int _cmp_(left, Element _right) except -2: cdef SemimonomialTransformation right = _right return cmp([left.v, left.perm, left.get_autom()], [right.v, right.perm, right.get_autom()]) diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index 9ea0e214983..8951751e73d 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -64,6 +64,7 @@ from sage.rings.all import GF, prime_range from sage.misc.decorators import rename_keyword from sage.homology.homology_group import HomologyGroup +from functools import reduce def _latex_module(R, m): diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index 034df830ed3..789da270163 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -822,10 +822,15 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if sage: singular._eval_using_file_cutoff = 4 sage: singular._eval_line('for(int i=1;i<=3;i++){i=1;};', wait_for_prompt=False) '' - sage: singular.interrupt(timeout=3) # sometimes very slow (up to 60s on sage.math, 2012) - False + sage: singular.interrupt() + True sage: singular._eval_using_file_cutoff = cutoff + The interface still works after this interrupt:: + + sage: singular('2+3') + 5 + Last, we demonstrate that by default the execution of a command is tried twice if it fails the first time due to a crashed interface:: diff --git a/src/sage/interfaces/sagespawn.pyx b/src/sage/interfaces/sagespawn.pyx index 5fbc1d37a98..3d45dad4b51 100644 --- a/src/sage/interfaces/sagespawn.pyx +++ b/src/sage/interfaces/sagespawn.pyx @@ -106,6 +106,42 @@ class SageSpawn(spawn): """ Py_INCREF(self) + def expect_peek(self, *args, **kwds): + r""" + Like :meth:`expect` but restore the read buffer such that it + looks like nothing was actually read. The next reading will + continue at the current position. + + EXAMPLES:: + + sage: from sage.interfaces.sagespawn import SageSpawn + sage: E = SageSpawn("sh", ["-c", "echo hello world"]) + sage: _ = E.expect_peek("w") + sage: E.read() + 'hello world\r\n' + """ + ret = self.expect(*args, **kwds) + self.buffer = self.before + self.after + self.buffer + return ret + + def expect_upto(self, *args, **kwds): + r""" + Like :meth:`expect` but restore the read buffer starting from + the matched string. The next reading will continue starting + with the matched string. + + EXAMPLES:: + + sage: from sage.interfaces.sagespawn import SageSpawn + sage: E = SageSpawn("sh", ["-c", "echo hello world"]) + sage: _ = E.expect_upto("w") + sage: E.read() + 'world\r\n' + """ + ret = self.expect(*args, **kwds) + self.buffer = self.after + self.buffer + return ret + def close(self): """ Quit the child process: send the quit string, close the diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 8d704584603..5079f3f0e1b 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -306,37 +306,22 @@ '' """ - - -#We could also do these calculations without using the singular -#interface (behind the scenes the interface is used by Sage): -# sage: x, y = PolynomialRing(RationalField(), 2, names=['x','y']).gens() -# sage: C = ProjectivePlaneCurve(y**9 - x**2*(x-1)**9) -# sage: C.genus() -# 0 -# sage: C = ProjectivePlaneCurve(y**9 - x**2*(x-1)**9 + x) -# sage: C.genus() -# 40 - #***************************************************************************** # Copyright (C) 2005 David Joyner and 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 os import re import sys import pexpect +from time import sleep from expect import Expect, ExpectElement, FunctionElement, ExpectFunction @@ -390,7 +375,9 @@ def __init__(self, maxread=1000, script_subdirectory=None, terminal_echo=False, name = 'singular', prompt = prompt, - command = "Singular -t --ticks-per-sec 1000", #no tty and fine grained cputime() + # no tty, fine grained cputime() + # and do not display CTRL-C prompt + command = "Singular -t --ticks-per-sec 1000 --cntrlc=a", maxread = maxread, server = server, server_tmpdir = server_tmpdir, @@ -499,6 +486,43 @@ def _quit_string(self): """ return 'quit' + def _send_interrupt(self): + """ + Send an interrupt to Singular. If needed, additional + semi-colons are sent until we get back at the prompt. + + TESTS: + + The following works without restarting Singular:: + + sage: a = singular(1) + sage: _ = singular._expect.sendline('1+') # unfinished input + sage: try: + ....: alarm(0.5) + ....: singular._expect_expr('>') # interrupt this + ....: except KeyboardInterrupt: + ....: pass + Control-C pressed. Interrupting Singular. Please wait a few seconds... + + We can still access a:: + + sage: 2*a + 2 + """ + # Work around for Singular bug + # http://www.singular.uni-kl.de:8002/trac/ticket/727 + sleep(0.1) + + E = self._expect + E.sendline(chr(3)) + for i in range(5): + try: + E.expect_upto(self._prompt, timeout=1.0) + return + except Exception: + pass + E.sendline(";") + def _read_in_file_command(self, filename): r""" EXAMPLES:: diff --git a/src/sage/lfunctions/all.py b/src/sage/lfunctions/all.py index b1a1d225773..da6a3efac59 100644 --- a/src/sage/lfunctions/all.py +++ b/src/sage/lfunctions/all.py @@ -4,3 +4,4 @@ from sympow import sympow +from zero_sums import LFunctionZeroSum diff --git a/src/sage/lfunctions/zero_sums.pyx b/src/sage/lfunctions/zero_sums.pyx new file mode 100644 index 00000000000..bccd267cb5a --- /dev/null +++ b/src/sage/lfunctions/zero_sums.pyx @@ -0,0 +1,1875 @@ +r""" +Class file for computing sums over zeros of motivic L-functions. +All computations are done to double precision. + +AUTHORS: + +- Simon Spicer (2014-10): first version + +""" + +############################################################################## +# Copyright (C) 2014 Simon Spicer +# +# 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.structure.sage_object cimport SageObject +from sage.rings.integer_ring import ZZ +from sage.rings.real_double import RDF +from sage.rings.complex_double import CDF +from sage.rings.infinity import PlusInfinity +from sage.rings.arith import prime_powers +from sage.functions.log import log, exp +from sage.functions.other import real, imag +from sage.symbolic.constants import pi, euler_gamma +from sage.libs.pari.all import pari +from sage.misc.all import verbose +from sage.parallel.decorate import parallel +from sage.parallel.ncpus import ncpus as num_cpus +from sage.libs.flint.ulong_extras cimport n_is_prime + +cdef extern from "": + double c_exp "exp"(double) + double c_log "log"(double) + double c_cos "cos"(double) + double c_acos "acos"(double) + double c_sqrt "sqrt"(double) + +# Global variable determining the number of CPUs to use for parallel computations +cdef NCPUS + +cdef class LFunctionZeroSum_abstract(SageObject): + r""" + Abstract class for computing certain sums over zeros of a motivic L-function + without having to determine the zeros themselves. + + """ + 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 _k # The weight of the form attached to self + cdef _C1 # = log(N)/2 - log(2*pi) + cdef _C0 # = C1 - euler_gamma + cdef _ncpus # The number of CPUs to use for parallel computations + + def ncpus(self, n=None): + r""" + Set or return the number of CPUs to be used in parallel computations. + If called with no input, the number of CPUs currently set is returned; + else this value is set to n. If n is 0 then the number of CPUs is set + to the max available. + + INPUT: + + ``n`` -- (default: None) If not None, a nonnegative integer + + OUTPUT: + + If n is not None, returns a positive integer + + EXAMPLES:: + + sage: Z = LFunctionZeroSum(EllipticCurve("389a")) + sage: Z.ncpus() + 1 + sage: Z.ncpus(2) + sage: Z.ncpus() + 2 + + The following output will depend on the system that Sage is running on. + + :: + + sage: Z.ncpus(0) + sage: Z.ncpus() # random + 4 + + """ + if n is None: + return self._ncpus + elif n<0: + raise ValueError("Input must be positive integer") + elif n==0: + self._ncpus = num_cpus() + NCPUS = self._ncpus + else: + self._ncpus = n + NCPUS = self._ncpus + + def level(self): + r""" + Return the level of the form attached to self. If self was constructed + from an elliptic curve, then this is equal to the conductor of `E`. + + EXAMPLES:: + + sage: E = EllipticCurve("389a") + sage: Z = LFunctionZeroSum(E) + sage: Z.level() + 389 + + """ + return self._N + + def weight(self): + r""" + Return the weight of the form attached to self. If self was constructed + from an elliptic curve, then this is 2. + + EXAMPLES:: + + sage: E = EllipticCurve("389a") + sage: Z = LFunctionZeroSum(E) + sage: Z.weight() + 2 + + """ + return self._k + + def C0(self, include_euler_gamma=True): + r""" + Return the constant term of the logarithmic derivative of the + completed `L`-function attached to self. This is equal to + `-\eta + \log(N)/2 - \log(2\pi)`, where `\eta` is the + Euler-Mascheroni constant `= 0.5772...` + and `N` is the level of the form attached to self. + + INPUT: + + - ``include_euler_gamma`` -- bool (default: True); if set to + False, return the constant `\log(N)/2 - \log(2\pi)`, i.e., do + not subtract off the Euler-Mascheroni constant. + + EXAMPLES:: + + sage: E = EllipticCurve("389a") + sage: Z = LFunctionZeroSum(E) + sage: Z.C0() # tol 1.0e-13 + 0.5666969404983447 + sage: Z.C0(include_euler_gamma=False) # tol 1.0e-13 + 1.1439126053998776 + + """ + # Computed at initialization + if include_euler_gamma==False: + return self._C1 + else: + return self._C0 + + def cnlist(self, n, python_floats=False): + r""" + Return a list of Dirichlet coefficient of the logarithmic + derivative of the `L`-function attached to self, shifted so that + the critical line lies on the imaginary axis, up to and + including n. The i-th element of the return list is a[i]. + + INPUT: + + - ``n`` -- non-negative integer + + - ``python_floats`` -- bool (default: False); if True return a list of + Python floats instead of Sage Real Double Field elements. + + OUTPUT: + + A list of real numbers + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_EllipticCurve.cn` + + .. TODO:: + + Speed this up; make more efficient + + EXAMPLES:: + + sage: E = EllipticCurve("11a") + sage: Z = LFunctionZeroSum(E) + sage: cnlist = Z.cnlist(11) + sage: for n in range(12): print(n,cnlist[n]) # tol 1.0e-13 + (0, 0.0) + (1, 0.0) + (2, 0.6931471805599453) + (3, 0.3662040962227033) + (4, 0.0) + (5, -0.32188758248682003) + (6, 0.0) + (7, 0.555974328301518) + (8, -0.34657359027997264) + (9, 0.6103401603711721) + (10, 0.0) + (11, -0.21799047934530644) + + """ + if not python_floats: + return [self.cn(i) for i in xrange(n+1)] + else: + return [float(self.cn(i)) for i in xrange(n+1)] + + def digamma(self, s, include_constant_term=True): + r""" + Return the digamma function `\digamma(s)` on the complex input s, given by + `\digamma(s) = -\eta + \sum_{k=1}^{\infty} \frac{s-1}{k(k+s-1)}`, + where `\eta` is the Euler-Mascheroni constant `=0.5772156649\ldots`. + This function is needed in the computing the logarithmic derivative + of the `L`-function attached to self. + + INPUT: + + - ``s`` -- A complex number + + - ``include_constant_term`` -- (default: True) boolean; if set False, + only the value of the sum over `k` is returned without subtracting + off the Euler-Mascheroni constant, i.e. the returned value is + equal to `\sum_{k=1}^{\infty} \frac{s-1}{k(k+s-1)}`. + + OUTPUT: + + A real double precision number if the input is real and not a negative + integer; Infinity if the input is a negative integer, and a complex + number otherwise. + + EXAMPLES:: + + sage: Z = LFunctionZeroSum(EllipticCurve("37a")) + sage: Z.digamma(3.2) # tol 1.0e-13 + 0.9988388912865993 + sage: Z.digamma(3.2,include_constant_term=False) # tol 1.0e-13 + 1.576054556188132 + sage: Z.digamma(1+I) # tol 1.0e-13 + 0.09465032062247625 + 1.076674047468581*I + sage: Z.digamma(-2) + +Infinity + + Evaluating the sum without the constant term at the positive integers n + returns the (n-1)th harmonic number. + + :: + + sage: Z.digamma(3,include_constant_term=False) + 1.5 + sage: Z.digamma(6,include_constant_term=False) + 2.283333333333333 + + """ + # imported here so as to avoid importing Numpy on Sage startup + from scipy.special import psi + + if real(s)<0 and imag(s)==0: + try: + z = ZZ(s) + return PlusInfinity() + except: + pass + + if imag(s)==0: + F = RDF + else: + F = CDF + # Cheating: SciPy already has this function implemented for complex inputs + z = F(psi(F(s))) + if include_constant_term: + return z + else: + return z + self._euler_gamma + + def logarithmic_derivative(self, s, num_terms=10000, as_interval=False): + r""" + Compute the value of the logarithmic derivative + `\frac{L^{\prime}}{L}` at the point s to *low* precision, where `L` + is the `L`-function attached to self. + + .. WARNING:: + + The value is computed naively by evaluating the Dirichlet series + for `\frac{L^{\prime}}{L}`; convergence is controlled by the + distance of s from the critical strip `0.5<=\Re(s)<=1.5`. + You may use this method to attempt to compute values inside the + critical strip; however, results are then *not* guaranteed + to be correct to any number of digits. + + INPUT: + + - ``s`` -- Real or complex value + + - ``num_terms`` -- (default: 10000) the maximum number of terms + summed in the Dirichlet series. + + OUTPUT: + + A tuple (z,err), where z is the computed value, and err is an + upper bound on the truncation error in this value introduced + by truncating the Dirichlet sum. + + .. NOTE:: + + For the default term cap of 10000, a value accurate to all 53 + bits of a double precision floating point number is only + guaranteed when `|\Re(s-1)|>4.58`, although in practice inputs + closer to the critical strip will still yield computed values + close to the true value. + + EXAMPLES:: + + sage: E = EllipticCurve([23,100]) + sage: Z = LFunctionZeroSum(E) + sage: Z.logarithmic_derivative(10) # tol 1.0e-13 + (5.648066742632698e-05, 1.0974102859764345e-34) + sage: Z.logarithmic_derivative(2.2) # tol 1.0e-13 + (0.5751257063594758, 0.024087912696974387) + + Increasing the number of terms should see the truncation error + decrease. + + :: + + sage: Z.logarithmic_derivative(2.2,num_terms=50000) # long time # rel tol 1.0e-14 + (0.5751579645060139, 0.008988775519160675) + + Attempting to compute values inside the critical strip + gives infinite error. + + :: + + sage: Z.logarithmic_derivative(1.3) # tol 1.0e-13 + (5.442994413920786, +Infinity) + + Complex inputs and inputs to the left of the critical strip + are allowed. + + :: + + sage: Z.logarithmic_derivative(complex(3,-1)) # tol 1.0e-13 + (0.04764548578052381 + 0.16513832809989326*I, 6.584671359095225e-06) + sage: Z.logarithmic_derivative(complex(-3,-1.1)) # tol 1.0e-13 + (-13.908452173241546 + 2.591443099074753*I, 2.7131584736258447e-14) + + The logarithmic derivative has poles at the negative integers. + + :: + + sage: Z.logarithmic_derivative(-3) # tol 1.0e-13 + (-Infinity, 2.7131584736258447e-14) + """ + if imag(s) == 0: + F = RDF + else: + F = CDF + # Inputs left of the critical line are handled via the functional + # equation of the logarithmic derivative + if real(s-1)<0: + a = -2*self._C1-self.digamma(s)-self.digamma(2-s) + b,err = self.logarithmic_derivative(2-s,num_terms=num_terms) + return (a+b,err) + + z = s-1 + sigma = RDF(real(z)) + log2 = log(RDF(2)) + # Compute maximum possible Dirichlet series truncation error + # When s is in the critical strip: no guaranteed precision + if abs(sigma)<=0.5: + err = PlusInfinity() + else: + a = RDF(sigma)-RDF(0.5) + b = log(RDF(num_terms))*a + err = (b+1)*exp(-b)/a**2 + + y = F(0) + n = ZZ(2) + while n <= num_terms: + if n.is_prime_power(): + cn = self.cn(n) + y += cn/F(n)**z + n += 1 + + return (y,err) + + def completed_logarithmic_derivative(self, s, num_terms=10000): + r""" + Compute the value of the completed logarithmic derivative + `\frac{\Lambda^{\prime}}{\Lambda}` at the point s to *low* + precision, where `\Lambda = N^{s/2}(2\pi)^s \Gamma(s) L(s)` + and `L` is the `L`-function attached to self. + + .. WARNING:: + + This is computed naively by evaluating the Dirichlet series + for `\frac{L^{\prime}}{L}`; the convergence thereof is + controlled by the distance of s from the critical strip + `0.5<=\Re(s)<=1.5`. + You may use this method to attempt to compute values inside the + critical strip; however, results are then *not* guaranteed + to be correct to any number of digits. + + INPUT: + + - ``s`` -- Real or complex value + + - ``num_terms`` -- (default: 10000) the maximum number of terms + summed in the Dirichlet series. + + OUTPUT: + + A tuple (z,err), where z is the computed value, and err is an + upper bound on the truncation error in this value introduced + by truncating the Dirichlet sum. + + .. NOTE:: + + For the default term cap of 10000, a value accurate to all 53 + bits of a double precision floating point number is only + guaranteed when `|\Re(s-1)|>4.58`, although in practice inputs + closer to the critical strip will still yield computed values + close to the true value. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_EllipticCurve.logarithmic_derivative` + + EXAMPLES:: + + sage: E = EllipticCurve([23,100]) + sage: Z = LFunctionZeroSum(E) + sage: Z.completed_logarithmic_derivative(3) # tol 1.0e-13 + (6.64372066048195, 6.584671359095225e-06) + + Complex values are handled. The function is odd about s=1, so + the value at 2-s should be minus the value at s. + + :: + + sage: Z.completed_logarithmic_derivative(complex(-2.2,1)) # tol 1.0e-13 + (-6.898080633125154 + 0.22557015394248361*I, 5.623853049808912e-11) + sage: Z.completed_logarithmic_derivative(complex(4.2,-1)) # tol 1.0e-13 + (6.898080633125154 - 0.22557015394248361*I, 5.623853049808912e-11) + + """ + if real(s-1)>=0: + Ls = self.logarithmic_derivative(s,num_terms) + return (self._C1 + self.digamma(s) + Ls[0], Ls[1]) + else: + Ls = self.logarithmic_derivative(2-s,num_terms) + return (-self._C1 - self.digamma(2-s) - Ls[0], Ls[1]) + + def zerosum(self, Delta=1, tau=0, function="sincsquared_fast", ncpus=None): + r""" + Bound from above the analytic rank of the form attached to self + by computing `\sum_{\gamma} f(\Delta(\gamma-\tau))`, where + `\gamma` ranges over the imaginary parts of the zeros of `L_E(s)` + along the critical strip, and `f(x)` is an appropriate even continuous + `L_2` function such that `f(0)=1`. + + If `\tau=0`, then as `\Delta` increases this sum converges from above to + the analytic rank of the `L`-function, as `f(0) = 1` is counted with + multiplicity `r`, and the other terms all go to 0 uniformly. + + INPUT: + + - ``Delta`` -- positive real number (default: 1) parameter denoting the + tightness of the zero sum. + + - ``tau`` -- real parameter (default: 0) denoting the offset of the sum + to be computed. When `\tau=0` the sum will converge to the analytic rank + of the `L`-function as `\Delta` is increased. If `\tau` is the value + of the imaginary part of a noncentral zero, the limit will be 1 + (assuming the zero is simple); otherwise, the limit will be 0. + Currently only implemented for the sincsquared and cauchy functions; + otherwise ignored. + + - ``function`` -- string (default: "sincsquared_fast") - the function + `f(x)` as described above. Currently implemented options for `f` are + + - ``sincquared`` -- `f(x) = \left(\frac{\sin(\pi x)}{\pi x}\right)^2` + + - ``gaussian`` -- `f(x) = e^{-x^2}` + + - ``sincquared_fast`` -- Same as "sincsquared", but implementation + optimized for elliptic curve `L`-functions, and tau must be 0. self + must be attached to an elliptic curve over `\QQ` given by its global + minimal model, otherwise the returned result will be incorrect. + + - ``sincquared_parallel`` -- Same as "sincsquared_fast", but optimized + for parallel computation with large (>2.0) `\Delta` values. self must + be attached to an elliptic curve over `\QQ` given by its global minimal + model, otherwise the returned result will be incorrect. + + - ``cauchy`` -- `f(x) = \frac{1}{1+x^2}`; this is only computable to + low precision, and only when `\Delta < 2`. + + - ``ncpus`` - (default: None) If not None, a positive integer + defining the number of CPUs to be used for the computation. If left as + None, the maximum available number of CPUs will be used. + Only implemented for algorithm="sincsquared_parallel"; ignored + otherwise. + + .. WARNING:: + + Computation time is exponential in `\Delta`, roughly doubling for + every increase of 0.1 thereof. Using `\Delta=1` will yield a + computation time of a few milliseconds; `\Delta=2` takes a few + seconds, and `\Delta=3` takes upwards of an hour. Increase at your + own risk beyond this! + + OUTPUT: + + A positive real number that bounds from above the number of zeros with + imaginary part equal to `\tau`. When `\tau=0` this is an upper bound for + the `L`-function's analytic rank. + + .. SEEALSO:: + + :meth:`~sage.schemes.elliptic_curves.ell_rational_field.EllipticCurve_rational_field.analytic_rank_bound` + for more documentation and examples on calling this method on elliptic curve + `L`-functions. + + EXAMPLES:: + + sage: E = EllipticCurve("389a"); E.rank() + 2 + sage: Z = LFunctionZeroSum(E) + sage: E.lseries().zeros(3) + [0.000000000, 0.000000000, 2.87609907] + sage: Z.zerosum(Delta=1,function="sincsquared_fast") # tol 1.0e-13 + 2.037500084595065 + sage: Z.zerosum(Delta=1,function="sincsquared_parallel") # tol 1.0e-11 + 2.037500084595065 + sage: Z.zerosum(Delta=1,function="sincsquared") # tol 1.0e-13 + 2.0375000845950644 + sage: Z.zerosum(Delta=1,tau=2.876,function="sincsquared") # tol 1.0e-13 + 1.075551295651154 + sage: Z.zerosum(Delta=1,tau=1.2,function="sincsquared") # tol 1.0e-13 + 0.10831555377490683 + sage: Z.zerosum(Delta=1,function="gaussian") # tol 1.0e-13 + 2.056890425029435 + + """ + + # If Delta>6.95, then exp(2*pi*Delta)>sys.maxint, so we get overflow + # when summing over the logarithmic derivative coefficients + if Delta > 6.95: + raise ValueError("Delta value too large; will result in overflow") + + if function=="sincsquared_parallel": + return self._zerosum_sincsquared_parallel(Delta=Delta,ncpus=ncpus) + elif function=="sincsquared_fast": + return self._zerosum_sincsquared_fast(Delta=Delta) + elif function=="sincsquared": + return self._zerosum_sincsquared(Delta=Delta,tau=tau) + elif function=="gaussian": + return self._zerosum_gaussian(Delta=Delta) + elif function=="cauchy": + return self._zerosum_cauchy(Delta=Delta,tau=tau) + else: + raise ValueError("Input function not recognized.") + + def _zerosum_sincsquared(self, Delta=1, tau=0): + r""" + Bound from above the analytic rank of the form attached to self + by computing `\sum_{\gamma} f(\Delta \cdot (\gamma-\tau))`, + where `\gamma` ranges over the imaginary parts of the zeros of `L_E(s)` + along the critical strip, and `f(x) = \sin(\pi x)/(\pi x)` + + If `\tau=0`, then as `\Delta` increases this sum limits from above to + the analytic rank of the `L`-function, as `f(0) = 1` is counted with + multiplicity `r`, and the other terms all go to 0 uniformly. + + INPUT: + + - ``Delta`` -- positive real number (default: 1) parameter denoting the + tightness of the zero sum. + + - ``tau`` -- real parameter (default: 0) denoting the offset of the sum + to be computed. When tau=0 the sum will converge from above to the + analytic rank of the `L`-function as `\Delta` is increased. If tau + is the value of the imaginary part of a noncentral zero, the limit + will be 1 (assuming GRH, the zero is simple); otherwise the limit + will be 0. + + .. WARNING:: + + Computation time is exponential in `\Delta`, roughly doubling for + every increase of 0.1 thereof. Using `\Delta=1` will yield a + computation time of a few milliseconds; `\Delta=2` takes a few + seconds, and `\Delta=3` takes upwards of an hour. Increase at your + own risk beyond this! + + .. WARNING:: + + This method has *not* been optimized with Cython; as such + computing with this method is slower than the central sum + (i.e `\tau=0`) versions of self._zerosum_sincsquared_fast() and + self._zerosum_sincsquared_parallel(). + + OUTPUT: + + A positive real number that bounds from above the number of zeros with + imaginary part equal to tau. When tau=0 this is an upper bound for the + `L`-function's analytic rank. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum` + for the public method that calls this private method. + + EXAMPLES:: + + sage: E = EllipticCurve("37a"); E.rank() + 1 + sage: Z = LFunctionZeroSum(E) + sage: E.lseries().zeros(2) + [0.000000000, 5.00317001] + + E is a rank 1 curve; the lowest noncentral zero has imaginary part + ~5.003. The zero sum with tau=0 indicates the probable existence of + a zero at or very close to the central point. + + :: + + sage: Z._zerosum_sincsquared(Delta=1,tau=0) # tol 1.0e-13 + 1.0103840698356257 + + The zero sum also detects a zero at or near 5.003, as expected. + + :: + + sage: Z._zerosum_sincsquared(Delta=1,tau=5.003) # tol 1.0e-13 + 1.0168124546878288 + + However, there is definitely no zero with imaginary part near 2.5, + as the sum would have to be at least 1. + + :: + + sage: Z._zerosum_sincsquared(Delta=1,tau=2.5) # tol 1.0e-13 + 0.058058210806477814 + + """ + + npi = self._pi + twopi = 2*npi + eg = self._euler_gamma + + t = RDF(Delta*twopi) + expt = RDF(exp(t)) + + u = t*self.C0() + + # No offset: formulae are simpler + if tau==0: + w = npi**2/6-(RDF(1)/expt).dilog() + + y = RDF(0) + n = int(1) + while n < expt: + cn = self.cn(n) + if cn!=0: + logn = log(RDF(n)) + y += cn*(t-logn) + n += 1 + # When offset is nonzero, the digamma transform (w) must + # be computed as an infinite sum + else: + tau = RDF(tau) + cos_tau_t = (tau*t).cos() + sin_tau_t = (tau*t).sin() + w = RDF(0) + for k in range(1, 1001): + a1 = tau**2/(k*(k**2+tau**2)) + a2 = (k**2-tau**2)/(k**2+tau**2)**2 + a3 = (2*k*tau)/(k**2+tau**2)**2 + + w0 = a1*t + a2 + w0 -= (a2*cos_tau_t-a3*sin_tau_t)*exp(-k*t) + w += w0 + + y = RDF(0) + n = int(1) + while n < expt: + cn = self.cn(n) + if cn!=0: + logn = log(RDF(n)) + y += cn*(t-logn)*(tau*logn).cos() + n += 1 + + return (u+w+y)*2/(t**2) + + def _zerosum_gaussian(self, Delta=1): + r""" + Return an upper bound on the analytic rank of the L-series attached + to self by computing `\sum_{\gamma} f(\Delta*\gamma)`, + where `\gamma` ranges over the imaginary parts of the zeros of `L_E(s)` + along the critical strip, and `f(x) = \exp(-x^2)`. + + As `\Delta` increases this sum limits from above to the analytic rank + of the form, as `f(0) = 1` is counted with multiplicity `r`, and the + other terms all go to 0 uniformly. + + INPUT: + + - ``Delta`` -- positive real number (default: 1) parameter defining the + tightness of the zero sum, and thus the closeness of the returned + estimate to the actual analytic rank of the form attached to self. + + .. WARNING:: + + Computation time is exponential in `\Delta`, roughly doubling for + every increase of 0.1 thereof. Using `\Delta=1` will yield a + computation time of a few milliseconds; `\Delta=2` takes a few + seconds, and `\Delta=3` takes upwards of an hour. Increase at your + own risk beyond this! + + OUTPUT: + + A positive real number that bounds the analytic rank of the modular form + attached to self from above. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum` + for the public method that calls this private method. + + EXAMPLES:: + + sage: E = EllipticCurve("37a"); E.rank() + 1 + sage: Z = LFunctionZeroSum(E) + sage: Z._zerosum_gaussian(Delta=1) # tol 1.0e-13 + 1.0563950773441664 + + """ + # imported here so as to avoid importing Numpy on Sage startup + from scipy.special import erfcx + + npi = self._pi + eg = self._euler_gamma + Deltasqrtpi = Delta*npi.sqrt() + + t = RDF(Delta*npi*2) + expt = RDF(exp(t)) + + u = self.C0() + + w = RDF(0) + for k in range(1, 1001): + w += RDF(1)/k - erfcx(Delta*k)*Deltasqrtpi + + y = RDF(0) + n = int(1) + + # TO DO: Error analysis to make sure this bound is good enough to + # avoid non-negligible trucation error + while n < expt: + cn = self.cn(n) + if cn != 0: + logn = log(RDF(n)) + y += cn*exp(-(logn/(2*Delta))**2) + n += 1 + # y is the truncation of an infinite sum, so we must add a value which + # exceeds the max amount we could have left out. + # WARNING: Truncation error analysis has *not* been done; the value of + # 0.1 is empirical. + return RDF(u+w+y+0.1)/Deltasqrtpi + + def _zerosum_cauchy(self, Delta=1, tau=0, num_terms=None): + r""" + Bound from above the analytic rank of the form attached to self + by computing `\sum_{\gamma} f(\Delta*(\gamma-\tau))`, + where `\gamma` ranges over the imaginary parts of the zeros of `L_E(s)` + along the critical strip, and `f(x) = \frac{1}{1+x^2}`. + + If `\tau=0`, then as `\Delta` increases this sum converges from above + to the analytic rank of the `L`-function, as `f(0) = 1` is counted + with multiplicity `r`, and the other terms all go to 0 uniformly. + + INPUT: + + - ``Delta`` -- positive real number (default: 1) parameter denoting the + tightness of the zero sum. + + - ``tau`` -- real parameter (default: 0) denoting the offset of the sum + to be computed. When tau=0 the sum will converge from above to the + analytic rank of the `L`-function as `\Delta` is increased. If tau is + the value of the imaginary part of a noncentral zero, the limit will + be 1 (assuming GRH, the zero is simple); otherwise the limit will + be 0. + + - ``num_terms`` -- positive integer (default: None): the number of + terms computed in the truncated Dirichlet series for the L-function + attached to self. If left at None, this is set to + `\ceil(e^{2 \pi \Delta})`, the same number of terms used in the other + zero sum methods for this value of Delta. + Increase num_terms to get more accuracy. + + .. WARNING:: + + This value can only be provably computed when Delta < 2; an error + will be thrown if a Delta value larger than 2 is supplied. + Furthermore, beware that computation time is exponential in + `\Delta`, roughly doubling for every increase of 0.1 thereof. + Using `\Delta=1` will yield a computation time of a few + milliseconds, while `\Delta=2` takes a few seconds. + + OUTPUT: + + A positive real number that bounds from above the number of zeros with + imaginary part equal to tau. When tau=0 this is an upper bound for the + `L`-function's analytic rank. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum` + for the public method that calls this private method. + + EXAMPLES:: + + sage: E = EllipticCurve("11a") + sage: E.lseries().zeros(2) + [6.36261389, 8.60353962] + + E is a rank zero curve; the lowest zero has imaginary part ~6.36. The + zero sum with tau=0 indicates that there are no zeros at the central + point (otherwise the returned value would be at least 1). + + :: + + sage: Z = LFunctionZeroSum(E) + sage: Z._zerosum_cauchy(Delta=1,tau=0) # tol 1.0e-13 + 0.9701073984459051 + + The zero sum with tau=6.36 indicates there might be a zero in the + vicinity. + + :: + + sage: Z._zerosum_cauchy(Delta=1,tau=6.36261389) # tol 1.0e-13 + 2.180904626331156 + + However, there are no zeros with imaginary part close to 1.5. + + :: + + sage: Z._zerosum_cauchy(Delta=1,tau=1.5) # tol 1.0e-13 + 0.9827072037553375 + + Because of the weak convergence of the Dirichlet series close to the + critical line, the bound will in general get *worse* for larger Delta. + This can be mitigated somewhat by increasing the number of terms. + + :: + + sage: Z._zerosum_cauchy(Delta=1.5) # tol 1.0e-13 + 12.93835258975716 + sage: Z._zerosum_cauchy(Delta=1.5,num_terms=100000) # tol 1.0e-13 + 10.395183960836599 + + An error will be thrown if a Delta value >= 2 is passed. + + :: + + sage: Z._zerosum_cauchy(Delta=2) + Traceback (most recent call last): + ... + ValueError: Bound not provably computable for Delta >= 2 + + """ + + if Delta >= 2: + raise ValueError("Bound not provably computable for Delta >= 2") + Del = RDF(Delta) + if num_terms is None: + num_terms = int(exp(2*self._pi*Del)) + + if tau==0: + one = RDF(1) + s = one/Del+one + u,err = self.completed_logarithmic_derivative(s, num_terms) + else: + one = CDF(1) + s = CDF(one/Del+one, tau) + u,err = self.completed_logarithmic_derivative(s, num_terms) + u = u.real() + + return (u+err)/Del + +cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): + r""" + Subclass for computing certain sums over zeros of an elliptic curve L-function + without having to determine the zeros themselves. + + """ + cdef _E # The Elliptic curve attached to self + cdef _e # PARI ellcurve object used to compute a_p values + + def __init__(self, E, N=None, ncpus=1): + r""" + Initializes self. + + INPUT: + + - ``E`` -- An elliptic curve defined over the rational numbers + + - ``N`` -- (default: None) If not None, a positive integer equal to + the conductor of E. This is passable so that rank estimation + can be done for curves whose (large) conductor has been precomputed. + + - ``ncpus`` -- (default: 1) The number of CPUs to use for computations. + If set to None, the max available amount will be used. + + EXAMPLES:: + + sage: from sage.lfunctions.zero_sums import LFunctionZeroSum_EllipticCurve + sage: E = EllipticCurve([1,0,0,3,-4]) + sage: Z = LFunctionZeroSum_EllipticCurve(E); Z + Zero sum estimator for L-function attached to Elliptic Curve defined by y^2 + x*y = x^3 + 3*x - 4 over Rational Field + sage: E = EllipticCurve("5077a") + sage: Z = LFunctionZeroSum_EllipticCurve(E); Z + Zero sum estimator for L-function attached to Elliptic Curve defined by y^2 + y = x^3 - 7*x + 6 over Rational Field + + """ + + self._k = ZZ(2) + self._E = E + if N is not None: + self._N = N + else: + self._N = E.conductor() + # PARI minicurve for computing a_p coefficients + self._e = E.pari_mincurve() + + self._pi = RDF(pi) + 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._C0 = self._C1 - self._euler_gamma + + # Number of CPUs to use for computations + if ncpus is None: + self._ncpus = num_cpus() + NCPUS = self._ncpus + else: + self._ncpus = ncpus + NCPUS = self._ncpus + + def __repr__(self): + r""" + Representation of self. + + EXAMPLES:: + + sage: Z = LFunctionZeroSum(EllipticCurve("37a")); Z + Zero sum estimator for L-function attached to Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field + + """ + s = "Zero sum estimator for L-function attached to " + return s+str(self._E) + + def elliptic_curve(self): + r""" + Return the elliptic curve associated with self. + + EXAMPLES:: + + sage: E = EllipticCurve([23,100]) + sage: Z = LFunctionZeroSum(E) + sage: Z.elliptic_curve() + Elliptic Curve defined by y^2 = x^3 + 23*x + 100 over Rational Field + + """ + return self._E + + def lseries(self): + r""" + Return the `L`-series associated with self. + + EXAMPLES:: + + sage: E = EllipticCurve([23,100]) + sage: Z = LFunctionZeroSum(E) + sage: Z.lseries() + Complex L-series of the Elliptic Curve defined by y^2 = x^3 + 23*x + 100 over Rational Field + + """ + return self._E.lseries() + + def cn(self, n): + r""" + Return the nth Dirichlet coefficient of the logarithmic + derivative of the L-function attached to self, shifted so that + the critical line lies on the imaginary axis. The returned value is + zero if `n` is not a perfect prime power; + when `n=p^e` for `p` a prime of bad reduction it is `-a_p^e log(p)/p^e`, + where `a_p` is `+1, -1` or `0` according to the reduction type of $p$; + and when `n=p^e` for a prime `p` of good reduction, the value + is `-(\alpha_p^e + \beta_p^e) \log(p)/p^e`, where `\alpha_p` + and `\beta_p` are the two complex roots of the characteristic equation + of Frobenius at `p` on `E`. + + INPUT: + + - ``n`` -- non-negative integer + + OUTPUT: + + A real number which (by Hasse's Theorem) is at + most `2\frac{log(n)}{\sqrt{n}}` in magnitude. + + EXAMPLES:: + + sage: E = EllipticCurve("11a") + sage: Z = LFunctionZeroSum(E) + sage: for n in range(12): print(n,Z.cn(n)) # tol 1.0e-13 + (0, 0.0) + (1, 0.0) + (2, 0.6931471805599453) + (3, 0.3662040962227033) + (4, 0.0) + (5, -0.32188758248682003) + (6, 0.0) + (7, 0.555974328301518) + (8, -0.34657359027997264) + (9, 0.6103401603711721) + (10, 0.0) + (11, -0.21799047934530644) + + """ + n = ZZ(n) + if n==0 or n==1: + return RDF(0) + if not n.is_prime_power(): + return RDF(0) + + n_float = RDF(n) + if n.is_prime(): + logn = log(n_float) + ap = self._E.ap(n) + return -ap*logn/n_float + else: + p,e = n.perfect_power() + ap = self._E.ap(p) + logp = log(RDF(p)) + if p.divides(self._N): + return - ap**e*logp/n_float + a,b = ap,2 + # Coefficients for higher powers obey recursion relation + for n in range(2, e+1): + a,b = ap*a-p*b, a + return -a*logp/n_float + + cdef double _sincsquared_summand_1(self, + unsigned long n, + double t, + int ap, + double p, + double logp, + double thetap, + double sqrtp, + double logq, + double thetaq, + double sqrtq, + double z): + r""" + Private cdef method to compute the logarithmic derivative + summand for the sinc^2 sum at prime values for when + n <= sqrt(bound), bound = exp(t) + Called in self._zerosum_sincsquared_fast() method + + """ + ap = self._e.ellap(n) + p = n + sqrtp = c_sqrt(p) + thetap = c_acos(ap/(2*sqrtp)) + logp = c_log(p) + + sqrtq = 1 + thetaq = 0 + logq = logp + + z = 0 + while logq < t: + sqrtq *= sqrtp + thetaq += thetap + z += 2*c_cos(thetaq)*(t-logq)/sqrtq + logq += logp + return -z*logp + + cdef double _sincsquared_summand_2(self, + unsigned long n, + double t, + int ap, + double p, + double logp): + r""" + Private cdef method to compute the logarithmic derivative + summand for the sinc^2 sum at prime values for when + sqrt(bound) < n < bound, bound = exp(t) + Called in self._zerosum_sincsquared_fast() method + + """ + ap = self._e.ellap(n) + p = n + logp = c_log(p) + return -(t-logp)*(logp/p)*ap + + cpdef _zerosum_sincsquared_fast(self, Delta=1, bad_primes=None): + r""" + A faster cythonized implementation of self._zerosum_sincsquared(). + + .. NOTE:: + + This will only produce correct output if self._E is given by its + global minimal model, i.e., if self._E.is_minimal()==True. + + INPUT: + + - ``Delta`` -- positive real parameter defining the + tightness of the zero sum, and thus the closeness of the returned + estimate to the actual analytic rank of the form attached to self + + - ``bad_primes`` -- (default: None) If not None, a list of primes dividing + the level of the form attached to self. This is passable so that this + method can be run on curves whose conductor is large enough to warrant + precomputing bad primes. + + OUTPUT: + + A positive real number that bounds the analytic rank of the modular form + attached to self from above. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum_sincsquared` + for the more general but slower version of this method. + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum` + for the public method that calls this private method. + + EXAMPLES:: + + sage: E = EllipticCurve("37a") + sage: Z = LFunctionZeroSum(E) + sage: print(E.rank(),Z._zerosum_sincsquared_fast(Delta=1)) # tol 1.0e-13 + (1, 1.0103840698356263) + sage: E = EllipticCurve("121a") + sage: Z = LFunctionZeroSum(E); + sage: print(E.rank(),Z._zerosum_sincsquared_fast(Delta=1.5)) # tol 1.0e-13 + (0, 0.0104712060086507) + + """ + # If Delta>6.619, then we will most likely get overflow: some ap values + # will be too large to fit into a c int + if Delta > 6.619: + raise ValueError("Delta value too large; will result in overflow") + + cdef double npi = self._pi + cdef double twopi = npi*2 + cdef double eg = self._euler_gamma + + cdef double t, u, w, y, z, expt, bound1, logp, logq + cdef double thetap, thetaq, sqrtp, sqrtq, p, q + cdef int ap, aq + + cdef unsigned long n + cdef double N_double = self._N + + t = twopi*Delta + expt = c_exp(t) + + u = t*(-eg + c_log(N_double)/2 - c_log(twopi)) + w = npi**2/6-(RDF(1)/expt).dilog() + + y = 0 + # 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 = [prime for prime in bad_primes if prime bound1: + if n_is_prime(n-4): + y += self._sincsquared_summand_1(n-4, t, ap, p, logp, thetap, + sqrtp, logq, thetaq, sqrtq, z) + if n <= expt and n_is_prime(n): + y += self._sincsquared_summand_2(n, t, ap, p, logp) + n += 6 + # Now sqrt(bound)< n < bound, so we don't need to consider higher + # prime power logarithmic derivative coefficients + while n <= expt: + if n_is_prime(n-4): + y += self._sincsquared_summand_2(n-4, t, ap, p, logp) + if n_is_prime(n): + y += self._sincsquared_summand_2(n, t, ap, p, logp) + n += 6 + # Case where n-4 <= t but n isn't + n = n-4 + if n <= expt and n_is_prime(n): + y += self._sincsquared_summand_2(n, t, ap, p, logp) + + return RDF(2*(u+w+y)/(t**2)) + + def _get_residue_data(self, n): + r""" + Method called by self._zerosum_sincsquared_parallel() to determine + the optimal residue class breakdown when sieving for primes. + Returns a list of small primes, the product thereof, and a list of + residues coprime to the product. + + INPUT: + + - ``n`` -- Positive integer denoting the number of required chunks. + + OUTPUT: + + A triple ``(small_primes, M, residue_chunks)`` such that + + - ``small_primes`` -- a list of small primes + + - ``modulus`` -- the product of the small primes + + - ``residue_chunks`` -- a list of lists consisting of all integers + less than the modulus that are coprime to it, broken into n + sublists of approximately equal size. + + EXAMPLES:: + + sage: E = EllipticCurve("37a"); Z = LFunctionZeroSum(E) + sage: Z._get_residue_data(8) + ([2, 3, 5, 7], + 210, + [[1, 37, 71, 107, 143, 179], + [11, 41, 73, 109, 149, 181], + [13, 43, 79, 113, 151, 187], + [17, 47, 83, 121, 157, 191], + [19, 53, 89, 127, 163, 193], + [23, 59, 97, 131, 167, 197], + [29, 61, 101, 137, 169, 199], + [31, 67, 103, 139, 173, 209]]) + + """ + # If n <=48, primes are sieved for modulo 210 + if n <= 48: + small_primes = [2, 3, 5, 7] + modulus = 210 + residue_list = [1, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, + 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, + 107, 109, 113, 121, 127, 131, 137, 139, 143, 149, + 151, 157, 163, 167, 169, 173, 179, 181, 187, 191, + 193, 197, 199, 209] + # General case for n > 480 + else: + from sage.rings.finite_rings.integer_mod import mod + from sage.rings.arith import next_prime + + modulus,p = 2,2 + small_primes,residue_list = [2],[1] + num_residues = 1 + # Enlarge residue_list by repeatedly applying Chinese Remainder + # Theorem + while num_residues= len_increment_list: + i = 0 + + # when bound1 <= n < expt, we don't need to consider higher powers of p + while n= len_increment_list: + i = 0 + + return y + + def _zerosum_sincsquared_parallel(self, + Delta=1, + bad_primes=None, + ncpus=None): + r""" + Parallelized implementation of self._zerosum_sincsquared_fast(). + Faster than self._zerosum_sincsquared_fast() when Delta >= ~1.75. + + .. NOTE:: + + This will only produce correct output if self._E is given by its + global minimal model, i.e. if self._E.is_minimal()==True. + + INPUT: + + - ``Delta`` -- positive real parameter defining the + tightness of the zero sum, and thus the closeness of the returned + estimate to the actual analytic rank of the form attached to self. + + - ``bad_primes`` -- (default: None) If not None, a list of primes dividing + the level of the form attached to self. This is passable so that this + method can be run on curves whose conductor is large enough to warrant + precomputing bad primes. + + - ``ncpus`` - (default: None) If not None, a positive integer + defining the number of CPUs to be used for the computation. If left as + None, the maximum available number of CPUs will be used. + + OUTPUT: + + A positive real number that bounds the analytic rank of the modular form + attached to self from above. + + .. SEEALSO:: + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum_sincsquared` + for the more general but slower version of this method. + + :meth:`~sage.lfunctions.zero_sums.LFunctionZeroSum_abstract.zerosum` + for the public method that calls this private method. + + EXAMPLES:: + + sage: E = EllipticCurve("37a"); print(E.rank()) + 1 + sage: Z = LFunctionZeroSum(E) + sage: print(Z._zerosum_sincsquared_parallel(Delta=1)) # tol 1.0e-11 + 1.0103840698356263 + sage: E = EllipticCurve("121a"); print(E.rank()) + 0 + sage: Z = LFunctionZeroSum(E); + sage: print(Z._zerosum_sincsquared_parallel(Delta=1.5,ncpus=2)) # tol 1.0e-11 + 0.01047120600865063 + + """ + # If Delta>6.619, then we will most likely get overflow: some ap values + # will be too large to fit into a c int + if Delta > 6.619: + raise ValueError("Delta value too large; will result in overflow") + + cdef double npi = self._pi + cdef double twopi = npi*2 + cdef double eg = self._euler_gamma + cdef double N_double = self._N + + cdef double t, u, w, y, z, expt, bound1, logp, logq + cdef double thetap, thetaq, sqrtp, sqrtq, p, q + cdef int ap, aq + cdef unsigned long n + + # Compute bounds and smooth part of sum + t = twopi*Delta + expt = c_exp(t) + bound1 = c_exp(t/2) + u = t*(-eg + c_log(N_double)/2 - c_log(twopi)) + w = npi**2/6-(RDF(1)/expt).dilog() + + # Oscillating part of sum + y = 0 + # Do bad primes first. Add correct contributions and subtract + # incorrect contribution; the latter are added back later on. + if bad_primes is None: + bad_primes = self._N.prime_divisors() + bad_primes = [prime for prime in bad_primes if prime= 1.75. + + .. NOTE:: + + Output will be incorrect if the incorrect root number is specified. + + .. WARNING:: + + Zero sum computation time is exponential in the tightness parameter + `\Delta`, roughly doubling for every increase of 0.1 thereof. + Using `\Delta=1` (and adaptive=False) will yield a runtime of a few + milliseconds; `\Delta=2` takes a few seconds, and `\Delta=3` may + take upwards of an hour. Increase beyond this at your own risk! + + OUTPUT: + + A non-negative integer greater than or equal to the analytic rank of + self. If the returned value is 0 or 1 (the latter if parity is not + False), then this is the true analytic rank of self. + + .. NOTE:: + + If you use set_verbose(1), extra information about the computation + will be printed. + + .. SEEALSO:: + + :func:`LFunctionZeroSum` + :meth:`EllipticCurve.root_number` + :func:`set_verbose` + + EXAMPLES: + + For most elliptic curves with small conductor the central zero(s) + of `L_E(s)` are fairly isolated, so small values of `\Delta` + will yield tight rank estimates. + + :: + + sage: E = EllipticCurve("11a") + sage: E.rank() + 0 + sage: Z = LFunctionZeroSum(E) + sage: Z.analytic_rank_upper_bound(max_Delta=1,ncpus=1) + 0 + + sage: E = EllipticCurve([-39,123]) + sage: E.rank() + 1 + sage: Z = LFunctionZeroSum(E) + sage: Z.analytic_rank_upper_bound(max_Delta=1) + 1 + + This is especially true for elliptic curves with large rank. + + :: + + sage: for r in range(9): + ....: E = elliptic_curves.rank(r)[0] + ....: print(r,E.analytic_rank_upper_bound(max_Delta=1, + ....: adaptive=False,root_number="ignore")) + ....: + (0, 0) + (1, 1) + (2, 2) + (3, 3) + (4, 4) + (5, 5) + (6, 6) + (7, 7) + (8, 8) + + However, some curves have `L`-functions with low-lying zeroes, and for these + larger values of `\Delta` must be used to get tight estimates. + + :: + + sage: E = EllipticCurve("974b1") + sage: r = E.rank(); r + 0 + sage: Z = LFunctionZeroSum(E) + sage: Z.analytic_rank_upper_bound(max_Delta=1,root_number="ignore") + 1 + sage: Z.analytic_rank_upper_bound(max_Delta=1.3,root_number="ignore") + 0 + + Knowing the root number of E allows us to use smaller Delta values + to get tight bounds, thus speeding up runtime considerably. + + :: + + sage: Z.analytic_rank_upper_bound(max_Delta=0.6,root_number="compute") + 0 + + The are a small number of curves which have pathologically low-lying + zeroes. For these curves, this method will produce a bound that is + strictly larger than the analytic rank, unless very large values of + Delta are used. The following curve ("256944c1" in the Cremona tables) + is a rank 0 curve with a zero at 0.0256...; the smallest Delta value + for which the zero sum is strictly less than 2 is ~2.815. + + :: + + sage: E = EllipticCurve([0, -1, 0, -7460362000712, -7842981500851012704]) + sage: N,r = E.conductor(),E.analytic_rank(); N, r + (256944, 0) + sage: E.analytic_rank_upper_bound(max_Delta=1,adaptive=False) + 2 + sage: E.analytic_rank_upper_bound(max_Delta=2,adaptive=False) + 2 + + This method is can be called on curves with large conductor. + + :: + + sage: E = EllipticCurve([-2934,19238]) + sage: Z = LFunctionZeroSum(E) + sage: Z.analytic_rank_upper_bound() + 1 + + And it can bound rank on curves with *very* large conductor, so long as + you know beforehand/can easily compute the conductor and primes of bad + reduction less than `e^{2\pi\Delta}`. The example below is of the rank + 28 curve discovered by Elkies that is the elliptic curve of (currently) + largest known rank. + + :: + + sage: a4 = -20067762415575526585033208209338542750930230312178956502 + sage: a6 = 34481611795030556467032985690390720374855944359319180361266008296291939448732243429 + sage: E = EllipticCurve([1,-1,1,a4,a6]) + sage: bad_primes = [2,3,5,7,11,13,17,19,48463] + sage: N = 3455601108357547341532253864901605231198511505793733138900595189472144724781456635380154149870961231592352897621963802238155192936274322687070 + sage: Z = LFunctionZeroSum(E,N) + sage: Z.analytic_rank_upper_bound(max_Delta=2.37,adaptive=False, # long time + ....: root_number=1,bad_primes=bad_primes,ncpus=2) # long time + 32 + + REFERENCES: + + .. [Bob-13] J.W. Bober. Conditionally bounding analytic ranks of elliptic curves. + ANTS 10. http://msp.org/obs/2013/1-1/obs-v1-n1-p07-s.pdf + + """ + #Helper function: compute zero sum and apply parity if not False + def run_computation(Delta): + verbose("Computing zero sum with Delta = %s"%Delta) + # Empirically, the non-parallelized zero sum method runs faster + # for Delta <= 1.75, regardless of the number of available CPUs. + if Delta <= 1.75: + bound = self._zerosum_sincsquared_fast(Delta=Delta, + bad_primes=bad_primes) + else: + bound = self._zerosum_sincsquared_parallel(Delta=Delta, + bad_primes=bad_primes, + ncpus=ncpus) + verbose("Sum value is %s"%bound) + bound = bound.floor() + # parity is set to -1 when we're not taking root number into + # account + if parity==-1: + verbose("Without invoking parity, rank bound is %s"%bound) + return bound + # parity is 0 if E has even analytic rank, and 1 if odd + # analytic rank. The returned value must have the same parity + # as the parity parameter. + if bound%2!=parity: + bound -= 1 + verbose("Invoking parity, rank bound is %s"%bound) + return bound + + # Get/compute parity + if root_number==1 or root_number==-1: + parity = (1-root_number)//2 + verbose("Parity set to %s."%parity) + elif root_number=="compute": + verbose("Computing curve parity...") + parity = (1-self._e.ellrootno())//2 + verbose("Curve has parity %s."%parity) + elif root_number=="ignore": + verbose("Curve parity ignored.") + parity = -1 + else: + raise ValueError("root_number parameter not recognized") + if parity==1: + halt_bound = 1 + verbose("Computation will halt if at any point bound is <= 1.") + else: + halt_bound = 0 + verbose("Computation will halt if at any point bound is 0.") + + # Compute max_Delta if necessary + if max_Delta is None: + verbose("Computing maximum Delta value") + pi, eg = self._pi, self._euler_gamma + #1000 is arbitrary - increases Delta for small N + max_Delta = (log(RDF(self._N+1000))/2-log(2*pi)-eg)/pi + if max_Delta > 2.5: + max_Delta = 2.5 + verbose("Computed max Delta value too big; setting to 2.5") + else: + verbose("Maximum Delta value to be used set at %s"%max_Delta) + else: + verbose("Maximum Delta value to be used set at %s"%max_Delta) + + # When max_Delta <= 1 it's not worth running the computation + # multiple times, as it's so quick anyway + if not adaptive or max_Delta<=1: + return run_computation(max_Delta) + else: + bound_list = [] + # Find starting value. This loop won't ever take long, + # since max_Delta is never > 7. + Delta = max_Delta + while Delta > 1: + Delta -= 0.2 + # Now go back up the sequence of Deltas + while Delta <= max_Delta: + bound = run_computation(Delta) + if bound <= halt_bound: + verbose("computed bound <= halt_bound, so halting") + return bound + else: + bound_list.append(bound) + # Incrementing Delta by 0.2 each step means runtime + # will increase by a factor of ~3.7 + Delta += 0.2 + + # Since the zero sum is not strictly decreasing in Delta, + # the last value is not necessarily the smallest + smallest_bound = min(bound_list) + verbose("Smallest bound computed is %s"%smallest_bound) + return smallest_bound + +def LFunctionZeroSum(X, *args, **kwds): + r""" + Constructor for the LFunctionZeroSum class. + + INPUT: + + - ``X`` -- A motivic object. Currently only implemented for X = an elliptic curve + over the rational numbers. + + OUTPUT: + + An LFunctionZeroSum object. + + EXAMPLES:: + + sage: E = EllipticCurve("389a") + sage: Z = LFunctionZeroSum(E); Z + Zero sum estimator for L-function attached to Elliptic Curve defined by y^2 + y = x^3 + x^2 - 2*x over Rational Field + + TESTS:: + + sage: E = EllipticCurve([1.2,3.8]) + sage: LFunctionZeroSum(E) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for elliptic curves over QQ + + sage: f = Newforms(46)[0] + sage: LFunctionZeroSum(f) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for elliptic curves over QQ + + """ + + # Here to avoid import recursion + from sage.schemes.elliptic_curves.ell_rational_field import EllipticCurve_rational_field + + if isinstance(X,EllipticCurve_rational_field): + return LFunctionZeroSum_EllipticCurve(X, *args, **kwds) + + raise NotImplementedError("Currently only implemented for elliptic curves over QQ") diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 8922c537d5c..a62734b71d5 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -552,19 +552,6 @@ cdef class GapElement(RingElement): if not self._compare_by_id: raise ValueError('requires a libGAP objects whose comparison is by "id"') - - def __richcmp__(left, right, int op): - """ - Boilerplate for Cython class comparison. - - EXAMPLES:: - - sage: a = libgap(123) - sage: a == a - True - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Make hashable. @@ -594,6 +581,8 @@ cdef class GapElement(RingElement): EXAMPLES:: sage: a = libgap(123) + sage: a == a + True sage: b = libgap('string') sage: a._richcmp_(b, 0) 1 diff --git a/src/sage/libs/ntl/error.pyx b/src/sage/libs/ntl/error.pyx index 64aedf4a0b0..bf8ea17a12f 100644 --- a/src/sage/libs/ntl/error.pyx +++ b/src/sage/libs/ntl/error.pyx @@ -4,6 +4,9 @@ NTL error handler AUTHOR: - Jeroen Demeyer (2015-02-15): initial version, see :trac:`17784` + +- Jeroen Demeyer (2015-07-09): use standard NTL ``ErrorMsgCallback``, + see :trac:`18875` """ #***************************************************************************** @@ -18,8 +21,8 @@ AUTHOR: include "sage/ext/interrupt.pxi" -from ntl_tools cimport SetErrorCallbackFunction -from cpython.exc cimport PyErr_SetString +from ntl_tools cimport ErrorMsgCallback + class NTLError(RuntimeError): """ @@ -34,9 +37,10 @@ class NTLError(RuntimeError): NTLError: DivRem: division by zero """ -cdef void NTL_error_callback(const char* s, void* context): - PyErr_SetString(NTLError, s) - sig_error() + +cdef void NTL_error_callback(const char* s) except *: + raise NTLError(s) + def setup_NTL_error_callback(): """ @@ -47,4 +51,5 @@ def setup_NTL_error_callback(): sage: from sage.libs.ntl.error import setup_NTL_error_callback sage: setup_NTL_error_callback() """ - SetErrorCallbackFunction(NTL_error_callback, NULL) + global ErrorMsgCallback + ErrorMsgCallback = NTL_error_callback diff --git a/src/sage/libs/ntl/ntl_tools.pxd b/src/sage/libs/ntl/ntl_tools.pxd index 096e8783651..3cb8c0567f2 100644 --- a/src/sage/libs/ntl/ntl_tools.pxd +++ b/src/sage/libs/ntl/ntl_tools.pxd @@ -1,2 +1,2 @@ cdef extern from "NTL/tools.h" namespace "NTL": - void SetErrorCallbackFunction(void (*func)(const char *s, void *context), void *context) + void (*ErrorMsgCallback)(const char *) except * diff --git a/src/sage/libs/pari/closure.pyx b/src/sage/libs/pari/closure.pyx index 2ab576753f1..50849984818 100644 --- a/src/sage/libs/pari/closure.pyx +++ b/src/sage/libs/pari/closure.pyx @@ -34,7 +34,7 @@ from cpython.object cimport PyObject_Call from cpython.ref cimport Py_INCREF include "sage/ext/interrupt.pxi" -include "sage/libs/pari/decl.pxi" +from .paridecl cimport * include "sage/libs/pari/pari_err.pxi" from pari_instance cimport pari_instance diff --git a/src/sage/libs/pari/declinl.pxi b/src/sage/libs/pari/declinl.pxi index 86a33f68b51..7d2e017114b 100644 --- a/src/sage/libs/pari/declinl.pxi +++ b/src/sage/libs/pari/declinl.pxi @@ -3,7 +3,7 @@ Declarations for inline functions from PARI. This file contains all declarations from headers/pariinl.h from the PARI distribution. All these functions are simple inline functions. -This file is included by sage/libs/pari/decl.pxi +This file is included by sage/libs/pari/paridecl.pxd AUTHORS: diff --git a/src/sage/libs/pari/gen.pxd b/src/sage/libs/pari/gen.pxd index 72f4d17d2d3..06d6d345c56 100644 --- a/src/sage/libs/pari/gen.pxd +++ b/src/sage/libs/pari/gen.pxd @@ -1,5 +1,4 @@ -include 'decl.pxi' - +from .types cimport * from sage.structure.element cimport RingElement cimport cython diff --git a/src/sage/libs/pari/gen.pyx b/src/sage/libs/pari/gen.pyx index b08f81d56b8..25a7222e7a0 100644 --- a/src/sage/libs/pari/gen.pyx +++ b/src/sage/libs/pari/gen.pyx @@ -57,6 +57,8 @@ from sage.misc.randstate cimport randstate, current_randstate from sage.structure.sage_object cimport rich_to_bool from sage.misc.superseded import deprecation, deprecated_function_alias +from .paridecl cimport * +from .paripriv cimport * include 'pari_err.pxi' include 'sage/ext/stdsage.pxi' include 'sage/ext/python.pxi' @@ -1050,9 +1052,6 @@ cdef class gen(gen_auto): # comparisons ########################################### - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right`` using ``op``. @@ -1126,9 +1125,6 @@ cdef class gen(gen_auto): pari_catch_sig_off() return r - def __cmp__(left, right): - return (left)._cmp(right) - cpdef int _cmp_(left, Element right) except -2: """ Compare ``left`` and ``right``. diff --git a/src/sage/libs/pari/handle_error.pxd b/src/sage/libs/pari/handle_error.pxd index 5cdd9604ff5..62aa4764d54 100644 --- a/src/sage/libs/pari/handle_error.pxd +++ b/src/sage/libs/pari/handle_error.pxd @@ -1,4 +1,4 @@ -include 'sage/libs/pari/decl.pxi' +from .types cimport GEN cdef void _pari_init_error_handling() cdef int _pari_err_handle(GEN E) except 0 diff --git a/src/sage/libs/pari/handle_error.pyx b/src/sage/libs/pari/handle_error.pyx index ad3d965bacb..9dfaeffb619 100644 --- a/src/sage/libs/pari/handle_error.pyx +++ b/src/sage/libs/pari/handle_error.pyx @@ -20,8 +20,10 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** + +from .paridecl cimport * +from .paripriv cimport * include "sage/ext/interrupt.pxi" -include "decl.pxi" from cpython cimport PyErr_Occurred from pari_instance cimport pari_instance diff --git a/src/sage/libs/pari/pari_instance.pxd b/src/sage/libs/pari/pari_instance.pxd index e5909ccc4dc..950e2bfda23 100644 --- a/src/sage/libs/pari/pari_instance.pxd +++ b/src/sage/libs/pari/pari_instance.pxd @@ -1,5 +1,4 @@ -include 'decl.pxi' - +from .types cimport * from sage.libs.gmp.types cimport * from sage.libs.flint.types cimport fmpz_mat_t from sage.structure.parent_base cimport ParentWithBase diff --git a/src/sage/libs/pari/pari_instance.pyx b/src/sage/libs/pari/pari_instance.pyx index a51b36388fb..39e5a6b936a 100644 --- a/src/sage/libs/pari/pari_instance.pyx +++ b/src/sage/libs/pari/pari_instance.pyx @@ -162,6 +162,17 @@ Sage (:trac:`9636`):: """ +#***************************************************************************** +# 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 .paridecl cimport * +from .paripriv cimport * include 'pari_err.pxi' include 'sage/ext/interrupt.pxi' cdef extern from *: diff --git a/src/sage/libs/pari/paridecl.pxd b/src/sage/libs/pari/paridecl.pxd index 3b8baf12fd1..fac8f363ccd 100644 --- a/src/sage/libs/pari/paridecl.pxd +++ b/src/sage/libs/pari/paridecl.pxd @@ -23,6 +23,7 @@ AUTHORS: """ +from .types cimport * from libc.stdio cimport FILE cdef extern from '': @@ -31,43 +32,7 @@ cdef extern from '': cdef extern from "sage/libs/pari/parisage.h": char* PARIVERSION - ctypedef unsigned long ulong "pari_ulong" - - ctypedef long* GEN - ctypedef char* byteptr - ctypedef unsigned long pari_sp - - # Various structures that we don't use in Sage but which need to be - # declared, otherwise Cython complains. - struct bb_group - struct bb_field - struct bb_ring - struct bb_algebra - struct qfr_data - struct nfmaxord_t - struct forcomposite_t - struct forpart_t - struct forprime_t - struct forvec_t - struct entree - struct gp_context - struct pariFILE - struct pari_mt - struct pari_thread - struct pari_timer - struct GENbin - struct hashentry - struct hashtable - - # PARI types: these are actually an enum type, but that doesn't - # matter for Cython. - extern int t_INT, t_REAL, t_INTMOD, t_FRAC, t_FFELT, t_COMPLEX, t_PADIC, t_QUAD, \ - t_POLMOD, t_POL, t_SER, t_RFRAC, t_QFR, t_QFI, t_VEC, t_COL, \ - t_MAT, t_LIST, t_STR, t_VECSMALL, t_CLOSURE, \ - t_FF_FpXQ, t_FF_Flxq, t_FF_F2xq - # parierr.h - int e_SYNTAX, e_BUG, \ e_ALARM, e_FILE, \ e_MISC, e_FLAG, e_IMPL, e_ARCH, e_PACKAGE, e_NOTFUNC, \ @@ -82,40 +47,6 @@ cdef extern from "sage/libs/pari/parisage.h": int warner, warnprec, warnfile, warnmem, warnuser - # parigen.h - - extern int BITS_IN_LONG - extern int DEFAULTPREC # 64 bits precision - extern int MEDDEFAULTPREC # 128 bits precision - extern int BIGDEFAULTPREC # 192 bits precision - long typ(GEN x) - long settyp(GEN x, long s) - long isclone(GEN x) - long setisclone(GEN x) - long unsetisclone(GEN x) - long lg(GEN x) - long setlg(GEN x, long s) - long signe(GEN x) - long setsigne(GEN x, long s) - long lgefint(GEN x) # macro - long setlgefint(GEN x, long s) # macro - long expo(GEN x) - long setexpo(GEN x, long s) - long valp(GEN x) - long setvalp(GEN x, long s) - long precp(GEN x) - long setprecp(GEN x, long s) - long varn(GEN x) - long setvarn(GEN x, long s) - long evaltyp(long x) - long evallg(long x) - long evalvarn(long x) - long evalsigne(long x) - long evalprecp(long x) - long evalvalp(long x) - long evalexpo(long x) - long evallgefint(long x) - # paricast.h long mael2(GEN, long, long) @@ -135,22 +66,24 @@ cdef extern from "sage/libs/pari/parisage.h": # paricom.h - GEN gpi - GEN geuler - GEN gen_m1 GEN gen_1 + GEN gen_m1 GEN gen_2 + GEN gen_m2 GEN ghalf - GEN gi GEN gen_0 GEN gnil - extern int INIT_JMPm, INIT_SIGm, INIT_DFTm - extern int new_galois_format, precdl + int PARI_SIGINT_block, PARI_SIGINT_pending + void NEXT_PRIME_VIADIFF(long, byteptr) + void PREC_PRIME_VIADIFF(long, byteptr) + int INIT_JMPm, INIT_SIGm, INIT_DFTm, INIT_noPRIMEm, INIT_noIMTm + int new_galois_format, factor_add_primes, factor_proven + int precdl # The "except 0" here is to ensure compatibility with # _pari_err_handle() in handle_error.pyx - extern int (*cb_pari_err_handle)(GEN) except 0 - extern int (*cb_pari_handle_exception)(long) except 0 - extern void (*cb_pari_err_recover)(long) + int (*cb_pari_err_handle)(GEN) except 0 + int (*cb_pari_handle_exception)(long) except 0 + void (*cb_pari_err_recover)(long) # kernel/gmp/int.h @@ -4248,24 +4181,3 @@ cdef extern from "sage/libs/pari/parisage.h": # Inline functions in separate file include 'declinl.pxi' - - -cdef extern from 'pari/paripriv.h': - int gpd_QUIET, gpd_TEST, gpd_EMACS, gpd_TEXMACS - - struct pariout_t: - char format # e,f,g - long fieldw # 0 (ignored) or field width - long sigd # -1 (all) or number of significant digits printed - int sp # 0 = suppress whitespace from output - int prettyp # output style: raw, prettyprint, etc - int TeXstyle - - struct gp_data: - pariout_t *fmt - unsigned long flags - - extern gp_data* GP_DATA - -cdef extern from 'pari/anal.h': - char* closure_func_err() diff --git a/src/sage/libs/pari/paripriv.pxd b/src/sage/libs/pari/paripriv.pxd new file mode 100644 index 00000000000..b6ea38180e1 --- /dev/null +++ b/src/sage/libs/pari/paripriv.pxd @@ -0,0 +1,30 @@ +# distutils: libraries = gmp pari +""" +Declarations for private functions from PARI + +Ideally, we shouldn't use these, but for technical reasons, we have to. +""" + +from .types cimport * + +cdef extern from "pari/paripriv.h": + int t_FF_FpXQ, t_FF_Flxq, t_FF_F2xq + + int gpd_QUIET, gpd_TEST, gpd_EMACS, gpd_TEXMACS + + struct pariout_t: + char format # e,f,g + long fieldw # 0 (ignored) or field width + long sigd # -1 (all) or number of significant digits printed + int sp # 0 = suppress whitespace from output + int prettyp # output style: raw, prettyprint, etc + int TeXstyle + + struct gp_data: + pariout_t *fmt + unsigned long flags + + extern gp_data* GP_DATA + +cdef extern from "pari/anal.h": + char* closure_func_err() diff --git a/src/sage/libs/pari/types.pxd b/src/sage/libs/pari/types.pxd new file mode 100644 index 00000000000..95d9eab2b03 --- /dev/null +++ b/src/sage/libs/pari/types.pxd @@ -0,0 +1,82 @@ +""" +Declarations for types used by PARI + +This includes both the C types as well as the PARI types (and a few +macros for dealing with those). + +It is important that the functionality in this file does not call any +PARI library functions. The reason is that we want to allow just using +these types (for example, to define a Cython extension type) without +linking to PARI. This file should consist only of typedefs and macros +from PARI's include files. +""" + +cdef extern from "sage/libs/pari/parisage.h": + ctypedef unsigned long ulong "pari_ulong" + + ctypedef long* GEN + ctypedef char* byteptr + ctypedef unsigned long pari_sp + + # PARI types: these are actually an enum type, but that doesn't + # matter for Cython. + int t_INT, t_REAL, t_INTMOD, t_FRAC, t_FFELT, t_COMPLEX, t_PADIC, \ + t_QUAD, t_POLMOD, t_POL, t_SER, t_RFRAC, t_QFR, t_QFI, t_VEC, \ + t_COL, t_MAT, t_LIST, t_STR, t_VECSMALL, t_CLOSURE, t_ERROR, \ + t_INFINITY + + int BITS_IN_LONG + int DEFAULTPREC # 64 bits precision + int MEDDEFAULTPREC # 128 bits precision + int BIGDEFAULTPREC # 192 bits precision + + long typ(GEN x) + long settyp(GEN x, long s) + long isclone(GEN x) + long setisclone(GEN x) + long unsetisclone(GEN x) + long lg(GEN x) + long setlg(GEN x, long s) + long signe(GEN x) + long setsigne(GEN x, long s) + long lgefint(GEN x) + long setlgefint(GEN x, long s) + long expo(GEN x) + long setexpo(GEN x, long s) + long valp(GEN x) + long setvalp(GEN x, long s) + long precp(GEN x) + long setprecp(GEN x, long s) + long varn(GEN x) + long setvarn(GEN x, long s) + long evaltyp(long x) + long evallg(long x) + long evalvarn(long x) + long evalsigne(long x) + long evalprecp(long x) + long evalvalp(long x) + long evalexpo(long x) + long evallgefint(long x) + + # Various structures that we don't use in Sage but which need to be + # declared, such that Cython understands the declarations of + # functions using these types. + struct bb_group + struct bb_field + struct bb_ring + struct bb_algebra + struct qfr_data + struct nfmaxord_t + struct forcomposite_t + struct forpart_t + struct forprime_t + struct forvec_t + struct entree + struct gp_context + struct pariFILE + struct pari_mt + struct pari_thread + struct pari_timer + struct GENbin + struct hashentry + struct hashtable diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index d00ac22dd91..8cd59a6f066 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -75,7 +75,7 @@ from sage.libs.pari.pari_instance cimport PariInstance, INT_to_mpz import sage.libs.pari.pari_instance cdef PariInstance pari = sage.libs.pari.pari_instance.pari -include "sage/libs/pari/decl.pxi" +from sage.libs.pari.paridecl cimport * include "sage/libs/pari/pari_err.pxi" ######################################################### diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index ba2770ef997..04ed1d200fa 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -86,7 +86,7 @@ from sage.libs.pari.pari_instance cimport PariInstance, INTFRAC_to_mpq import sage.libs.pari.pari_instance cdef PariInstance pari = sage.libs.pari.pari_instance.pari -include "sage/libs/pari/decl.pxi" +from sage.libs.pari.paridecl cimport * include "sage/libs/pari/pari_err.pxi" ######################################################### diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index de0895996d1..effca2ad439 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -1094,8 +1094,152 @@ cdef class BasisExchangeMatroid(Matroid): for i in xrange(self.full_rank()): bitset_free(comp[i]) return res - - + + cpdef _link(self, S, T): + r""" + Given disjoint subsets `S` and `T`, return a connector `I` and a separation `X`, + which are optimal dual solutions in Tutte's Linking Theorem: + + .. MATH:: + + \max \{ r_N(S) + r_N(T) - r(N) \mid N = M/I\setminus J, E(N) = S\cup T\}=\\ + \min \{ r_M(X) + r_M(Y) - r_M(E) \mid X \subseteq S, Y \subseteq T, + E = X\cup Y, X\cap Y = \emptyset \}. + + Here `M` denotes this matroid. + + Internal version that does not verify that ``S`` and ``T`` + are sets, are disjoint, are subsets of the groundset. + + INPUT: + + - ``S`` -- a subset of the ground set + - ``T`` -- a subset of the ground set disjoint from ``S`` + + OUTPUT: + + A tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset ``X``. + + ALGORITHM: + + Compute a maximum-cardinality common independent set `I` of + of `M / S \setminus T` and `M \setminus S / T`. + + EXAMPLES:: + + sage: M = matroids.named_matroids.BetsyRoss() + sage: S = set('ab') + sage: T = set('cd') + sage: I, X = M._link(S, T) + sage: M.connectivity(X) + 2 + sage: J = M.groundset()-(S|T|I) + sage: N = M/I\J + sage: N.connectivity(S) + 2 + """ + # compute maximal common independent set of self\S/T and self/T\S + cdef bitset_t SS, TT + bitset_init(SS, self._groundset_size) + bitset_init(TT, self._groundset_size) + self.__pack(SS,S) + self.__pack(TT,T) + #F = set(self.groundset()) - (S | T) + cdef bitset_t F, I + bitset_init(F, self._groundset_size) + bitset_init(I, self._groundset_size) + bitset_union(self._input, SS, TT) + bitset_complement(F, self._input) + #I = self._augment(S|T, F) + self.__augment(I, self._input, F) + cdef bitset_t X, X1, X2, next_layer, todo, out_neighbors, R + bitset_init(X, self._groundset_size) + bitset_init(X1, self._groundset_size) + bitset_init(X2, self._groundset_size) + bitset_init(next_layer, self._groundset_size) + bitset_init(todo, self._groundset_size) + bitset_init(out_neighbors, self._groundset_size) + bitset_init(R, self._groundset_size) + cdef long* predecessor + predecessor = sage_malloc(self._groundset_size*sizeof(long)) + cdef long e, u, y + cdef bint found_path = True + while found_path: + #X = F - I + bitset_difference(X,F,I) + #X1 = X - self._closure(T|I) + bitset_union(self._input, TT, I) + self.__closure(X1, self._input) + bitset_difference(X1,X,X1) + #X2 = X - self._closure(S|I) + bitset_union(self._input, SS, I) + self.__closure(X2, self._input) + bitset_difference(X2,X,X2) + bitset_intersection(R, X1, X2) + e = bitset_first(R) + if e >= 0: + bitset_add(I, e) + continue + #predecessor = {x: None for x in X1} + e = bitset_first(X1) + while e>=0: + predecessor[e] = -1 + e = bitset_next(X1, e+1) + #next_layer = set(X1) + bitset_copy(next_layer, X1) + bitset_union(R, SS, X1) + found_path = False + while not bitset_isempty(next_layer) and not found_path: + #todo = next_layer + bitset_copy(todo,next_layer) + #next_layer = {} + bitset_clear(next_layer) + u = bitset_first(todo) + while u>=0 and not found_path: + if bitset_in(X,u): + #out_neighbors = self._circuit(I|S.union([u])) - S.union([u]) + bitset_union(self._input, I, SS) + bitset_add(self._input, u) + self.__circuit(out_neighbors, self._input) + bitset_discard(out_neighbors, u) + else: + #out_neighbors = X - self._closure(I|T - set([u])) + bitset_union(self._input, I, TT) + bitset_discard(self._input, u) + self.__closure(out_neighbors, self._input) + bitset_difference(out_neighbors, X, out_neighbors) + bitset_difference(out_neighbors, out_neighbors, R) + y = bitset_first(out_neighbors) + while y>=0: + predecessor[y] = u + if bitset_in(X2, y): + found_path = True + while y>=0: + bitset_flip(I, y) + y = predecessor[y] + break + bitset_add(R, y) + bitset_add(next_layer, y) + y = bitset_next(out_neighbors, y+1) + u = bitset_next(todo, u+1) + II = self.__unpack(I) + RR = self.__unpack(R) + + bitset_free(SS) + bitset_free(TT) + bitset_free(F) + bitset_free(I) + bitset_free(X) + bitset_free(X1) + bitset_free(X2) + bitset_free(next_layer) + bitset_free(todo) + bitset_free(out_neighbors) + bitset_free(R) + sage_free(predecessor) + + return II, RR + # enumeration cpdef f_vector(self): diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 9cc50a9c228..415c02f26c9 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -134,9 +134,12 @@ cdef class Matroid(SageObject): cpdef is_simple(self) cpdef is_cosimple(self) cpdef components(self) - cpdef is_connected(self) + cpdef is_connected(self, certificate=*) cpdef connectivity(self, S, T=*) cpdef _connectivity(self, S, T) + cpdef is_kconnected(self, k, certificate=*) + cpdef link(self, S, T) + cpdef _link(self, S, T) cpdef is_3connected(self, certificate=*, algorithm=*, separation=*) cpdef _is_3connected_CE(self, certificate=*) cpdef _is_3connected_BC(self, certificate=*) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index d10b626d6bc..0f9a3dfde30 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -4525,7 +4525,7 @@ cdef class Matroid(SageObject): components = components2 return components - cpdef is_connected(self): + cpdef is_connected(self, certificate=False): """ Test if the matroid is connected. @@ -4553,7 +4553,17 @@ cdef class Matroid(SageObject): True """ - return len(self.components()) == 1 + components = self.components() + if len(components) == 1: + if certificate: + return True, None + else: + return True + else: + if certificate: + return False, components[0] + else: + return False cpdef connectivity(self, S, T=None): r""" @@ -4588,12 +4598,16 @@ cdef class Matroid(SageObject): 2 """ S = set(S) + if not S.issubset(self.groundset()): + raise ValueError("S is not a subset of the ground set") if T is None: - return self.rank(S) + self.rank(self.groundset()-S) - self.full_rank() + return self._rank(S) + self._rank(self.groundset()-S) - self.full_rank() T = set(T) + if not T.issubset(self.groundset()): + raise ValueError("S is not a subset of the ground set") if S.intersection(T): - raise ValueError("no well-defined matroid connectivity between intersecting sets") - return self._connectivity(S, T) + raise ValueError("S and T are not disjoint") + return len(self._link(S, T)[0]) - self.full_rank() + self._rank(S) + self._rank(T) cpdef _connectivity(self, S, T): r""" @@ -4629,12 +4643,265 @@ cdef class Matroid(SageObject): EXAMPLES:: sage: M = matroids.named_matroids.BetsyRoss() - sage: M._connectivity('ab', 'cd') + sage: M._connectivity(set('ab'), set('cd')) + 2 + """ + return len(self._link(S,T)[0]) - self.full_rank() + self.rank(S) + self.rank(T) + + cpdef link(self, S, T): + r""" + Given disjoint subsets `S` and `T`, return a connector `I` and a separation `X`, + which are optimal dual solutions in Tutte's Linking Theorem: + + .. MATH:: + + \max \{ r_N(S) + r_N(T) - r(N) \mid N = M/I\setminus J, E(N) = S\cup T\}=\\ + \min \{ r_M(X) + r_M(Y) - r_M(E) \mid X \subseteq S, Y \subseteq T, + E = X\cup Y, X\cap Y = \emptyset \}. + + Here `M` denotes this matroid. + + INPUT: + + - ``S`` -- a subset of the ground set + - ``T`` -- a subset of the ground set disjoint from ``S`` + + OUTPUT: + + A tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset ``X``. + + ALGORITHM: + + Compute a maximum-cardinality common independent set `I` of + of `M / S \setminus T` and `M \setminus S / T`. + + EXAMPLES:: + + sage: M = matroids.named_matroids.BetsyRoss() + sage: S = set('ab') + sage: T = set('cd') + sage: I, X = M.link(S, T) + sage: M.connectivity(X) + 2 + sage: J = M.groundset()-(S|T|I) + sage: N = M/I\J + sage: N.connectivity(S) + 2 + """ + if not self.groundset().issuperset(S): + raise ValueError("S is not a subset of the ground set") + if not self.groundset().issuperset(T): + raise ValueError("T is not a subset of the ground set") + if not S.isdisjoint(T): + raise ValueError("S and T are not disjoint") + return self._link(S, T) + + cpdef _link(self, S, T): + r""" + Given disjoint subsets `S` and `T`, return a connector `I` and a separation `X`, + which are optimal dual solutions in Tutte's Linking Theorem: + + .. MATH:: + + \max \{ r_N(S) + r_N(T) - r(N) \mid N = M/I\setminus J, E(N) = S\cup T\}=\\ + \min \{ r_M(X) + r_M(Y) - r_M(E) \mid X \subseteq S, Y \subseteq T, + E = X\cup Y, X\cap Y = \emptyset \}. + + Here `M` denotes this matroid. + + Internal version that does not verify that ``S`` and ``T`` + are sets, are disjoint, are subsets of the groundset. + + INPUT: + + - ``S`` -- a subset of the ground set + - ``T`` -- a subset of the ground set disjoint from ``S`` + + OUTPUT: + + A tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset ``X``. + + ALGORITHM: + + Compute a maximum-cardinality common independent set `I` of + of `M / S \setminus T` and `M \setminus S / T`. + + EXAMPLES:: + + sage: M = matroids.named_matroids.BetsyRoss() + sage: S = set('ab') + sage: T = set('cd') + sage: I, X = M._link(S, T) + sage: M.connectivity(X) + 2 + sage: J = M.groundset()-(S|T|I) + sage: N = M/I\J + sage: N.connectivity(S) 2 """ - N1 = self.minor(T,S) - N2 = self.minor(S,T) - return len(N1.intersection(N2)) - self.full_rank() + self.rank(S) + self.rank(T) + # compute maximal common independent set of self\S/T and self/T\S + F = set(self.groundset()) - (S | T) + I = self._augment(S|T, F) + found_path = True + while found_path: + X = F - I + X1 = X - self._closure(T|I) + X2 = X - self._closure(S|I) + Y = X1.intersection(X2) + if Y: + y = min(Y) + I = I.union([y]) + continue + predecessor = {x: None for x in X1} + next_layer = set(X1) + found_path = False + while next_layer and not found_path: + todo = next_layer + next_layer = set() + while todo and not found_path: + u = todo.pop() + if u in X: + out_neighbors = self._circuit(I|S.union([u])) - S.union([u]) + else: + out_neighbors = X - self._closure((I|T) - set([u])) + out_neighbors= out_neighbors-set(predecessor) + for y in out_neighbors: + predecessor[y] = u + if y in X2: + found_path = True + break + next_layer.add(y) + if found_path: + path = set([y]) # reconstruct path + while predecessor[y] is not None: + y = predecessor[y] + path.add(y) + I = I.symmetric_difference(path) + return frozenset(I), frozenset(predecessor)|S + + cpdef is_kconnected(self, k, certificate=False): + r""" + Return ``True`` if the matroid is `k`-connected, ``False`` otherwise. It can + optionally return a separator as a witness. + + INPUT: + + - ``k`` -- a integer greater or equal to 1. + - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + then return ``True, None`` if the matroid is is k-connected, + and ``False, X`` otherwise, where ``X`` is a `1: + if certificate: + for X in C: + return False, X + else: + return False + # now 2-separations are exact + # test if groundset size is at least 4 E = set(self.groundset()) - e = E.pop() - f = E.pop() - S = {e, f} - w = {e:1 for e in E} - I = set() - for T in combinations(E, 2): - T = set(T) - N1 = self.minor(T,S) - N2 = self.minor(S,T) - # make previous I a common independent set of current N1, N2 - I = I - T - I = N1.max_independent(I) - I = N2.max_independent(I) - J = N1._intersection_augmentation(N2, w, I) - while J[0]: - I = I.symmetric_difference(J[1]) - J = N1._intersection_augmentation(N2, w, I) - # check if connectivity between S,T is <2 - if len(I) - self.full_rank() + self.rank(S) + self.rank(T) < 2: + if len(E)<4: + if certificate: + return True, None + # there is no partition A,B of the ground set such that |A|, |B| >= 2 + else: + return True + # now there exist two disjoint pairs + # test if there are any parallel pairs + for e in E: + X = self._closure([e]) + if len(X)>1: if certificate: - return False, S.union(J[1]) + return False, self._circuit(X) + # r(C) + r(E\C) - r(E) <= r(C) < 2, |C| = 2, |E\C| >= 2 else: return False - for g in E: - S = {e, g} - I = I - S - for h in E: - if g is h: - continue - T = {f, h} - T = set(T) - N1 = self.minor(T,S) - N2 = self.minor(S,T) - # make previous I a common independent set of current N1, N2 - I = I - T - I = N1.max_independent(I) - I = N2.max_independent(I) - J = N1._intersection_augmentation(N2, w, I) - while J[0]: - I = I.symmetric_difference(J[1]) - J = N1._intersection_augmentation(N2, w, I) - # check if connectivity between S,T is <2 - if len(I) - self.full_rank() + self.rank(S) + self.rank(T) < 2: + # now each pair has rank 2 + e = E.pop() + f = E.pop() + # check 2-separations with e,f on the same side + S = {e, f} + G = set(E) + while G: + g = G.pop() + H = set(G) + while H: + h = H.pop() + T = {g, h} + I, X = self._link(S, T) + # check if connectivity between S,T is < 2 + if len(I) + 2 < self.full_rank(): # note: rank(S) = rank(T) = 2 if certificate: - return False, S.union(J[1]) + return False, X else: return False + # if h' is not spanned by I+g, then I is a connector for {e,f}, {g,h'} + H.intersection_update(self._closure(I.union([g]))) + g = E.pop() + # check 2-separations with e,g on one side, f on the other + S = {e, g} + H = set(E) + while H: + h = H.pop() + T = {f, h} + I, X = self._link(S, T) + # check if connectivity between S,T is < 2 + if len(I) + 2 < self.full_rank(): # note: rank(S) = rank(T) = 2 + if certificate: + return False, X + else: + return False + # if h' is not spanned by I + f, then I is a connector for {e, g}, {f, h'} + H.intersection_update(self._closure(I.union([f]))) + # check all 2-separations with f,g on one side, e on the other + S = {f, g} + H = set(E) + while H: + h = H.pop() + T = {e, h} + I, X = self._link(S, T) + # check if connectivity between S,T is < 2 + if len(I) + 2 < self.full_rank(): # note: rank(S) = rank(T) = 2 + if certificate: + return False, X + else: + return False + # if h' is not spanned by I + e, then I is a connector for {f, g}, {e, h'} + H.intersection_update(self._closure(I.union([e]))) if certificate: return True, None else: diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index e6a287fb99e..ba1c2aa9041 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -487,7 +487,7 @@ def lift_cross_ratios(A, lift_map = None): cr = -cr if not cr ==plus_one1: monomial[cr] = 1 - if monomial.has_key(minus_one1): + if minus_one1 in monomial: monomial[minus_one1] = monomial[minus_one1] + 1 else: monomial[minus_one1] = 1 @@ -499,13 +499,13 @@ def lift_cross_ratios(A, lift_map = None): for entry2 in entries: if div: for cr, degree in F[entry2].iteritems(): - if monomial.has_key(cr): + if cr in monomial: monomial[cr] = monomial[cr]+ degree else: monomial[cr] = degree else: for cr, degree in F[entry2].iteritems(): - if monomial.has_key(cr): + if cr in monomial: monomial[cr] = monomial[cr] - degree else: monomial[cr] = -degree diff --git a/src/sage/misc/abstract_method.py b/src/sage/misc/abstract_method.py index f5e5b242331..563a2481f77 100644 --- a/src/sage/misc/abstract_method.py +++ b/src/sage/misc/abstract_method.py @@ -161,8 +161,8 @@ def __init__(self, f, optional = False): assert isinstance(optional, bool) self._f = f self._optional = optional - self.__doc__ = f.func_doc - self.__name__ = f.func_name + self.__doc__ = f.__doc__ + self.__name__ = f.__name__ try: self.__module__ = f.__module__ except AttributeError: diff --git a/src/sage/misc/binary_tree.pyx b/src/sage/misc/binary_tree.pyx index 5b18c93ecf9..c4eb345ee53 100644 --- a/src/sage/misc/binary_tree.pyx +++ b/src/sage/misc/binary_tree.pyx @@ -24,13 +24,11 @@ cdef void free_binary_tree_node(binary_tree_node *self): Py_DECREF(self.value) sage_free(self) -cdef void binary_tree_dealloc(binary_tree_node *self): - if self.left != NULL: +cdef inline void binary_tree_dealloc(binary_tree_node *self): + if self != NULL: binary_tree_dealloc(self.left) - free_binary_tree_node(self.left) - if self.right != NULL: binary_tree_dealloc(self.right) - free_binary_tree_node(self.right) + free_binary_tree_node(self) cdef void binary_tree_insert(binary_tree_node *self, int key, object value): @@ -185,12 +183,31 @@ cdef class BinaryTree: """ A simple binary tree with integer keys. """ - def __init__(BinaryTree self): + def __cinit__(BinaryTree self): self.head = NULL def __dealloc__(BinaryTree self): - if self.head != NULL: - binary_tree_dealloc(self.head) - sage_free(self.head) + """ + TESTS: + + We test that :trac:`18897` is fixed:: + + sage: def test(): + ....: from sage.rings.polynomial.polynomial_compiled import CompiledPolynomialFunction + ....: import gc + ....: from collections import Counter + ....: gc.collect() + ....: pre={id(c) for c in gc.get_objects()} + ....: L = [-1, 9, -22, 21, -8, 1] + ....: for _ in range(100): + ....: CompiledPolynomialFunction(L) # this creates and deallocs a binary tree + ....: gc.collect() + ....: post=Counter(type(o) for o in gc.get_objects() if id(o) not in pre) + ....: return [(k,v) for (k,v) in post.iteritems() if v>10] + sage: test() # indirect doctest + [] + + """ + binary_tree_dealloc(self.head) def insert(BinaryTree self, object key, object value = None): """ diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 5aba74dacb0..66932184b3c 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -210,10 +210,6 @@ hardly by used. ....: " return '<%s>'%self.x", ....: " def __hash__(self):", ....: " return hash(self.x)", - ....: " def __cmp__(left, right):", - ....: " return (left)._cmp(right)", - ....: " def __richcmp__(left, right, op):", - ....: " return (left)._richcmp(right,op)", ....: " cpdef int _cmp_(left, Element right) except -2:", ....: " return cmp(left.x,right.x)", ....: " def raw_test(self):", @@ -229,10 +225,6 @@ hardly by used. ....: " return '<%s>'%self.x", ....: " def __hash__(self):", ....: " return hash(self.x)", - ....: " def __cmp__(left, right):", - ....: " return (left)._cmp(right)", - ....: " def __richcmp__(left, right, op):", - ....: " return (left)._richcmp(right,op)", ....: " cpdef int _cmp_(left, Element right) except -2:", ....: " return cmp(left.x,right.x)", ....: " def raw_test(self):", diff --git a/src/sage/misc/cython_metaclass.h b/src/sage/misc/cython_metaclass.h index 2fcd76591eb..cc620a4dac7 100644 --- a/src/sage/misc/cython_metaclass.h +++ b/src/sage/misc/cython_metaclass.h @@ -12,7 +12,7 @@ static PyObject* NoneNoneNone; /* All args flags of a PyMethod */ -#define METH_ALLARGS (METH_OLDARGS|METH_VARARGS|METH_KEYWORDS|METH_NOARGS|METH_O) +#define METH_ALLARGS (METH_VARARGS|METH_KEYWORDS|METH_NOARGS|METH_O) /* Given an unbound method "desc" (this is not checked!) with only a * single "self" argument, call "desc(self)" without checking "self". diff --git a/src/sage/misc/lazy_format.py b/src/sage/misc/lazy_format.py index 2c583cf09de..d5d4d3b7555 100644 --- a/src/sage/misc/lazy_format.py +++ b/src/sage/misc/lazy_format.py @@ -1,12 +1,15 @@ """ Lazy format strings """ -from copy import copy class LazyFormat(str): """ Lazy format strings + .. NOTE:: + + We recommend to use :func:`sage.misc.lazy_string.lazy_string` instead, + which is both faster and more flexible. An instance of :class:`LazyFormat` behaves like a usual format string, except that the evaluation of the ``__repr__`` method of @@ -26,8 +29,8 @@ class LazyFormat(str): ``__repr__`` method:: sage: class IDontLikeBeingPrinted(object): - ... def __repr__(self): - ... raise ValueError("Don't ever try to print me !") + ....: def __repr__(self): + ....: raise ValueError("Don't ever try to print me !") There is no error when binding a lazy format with the broken object:: @@ -49,14 +52,14 @@ class LazyFormat(str): messages in :mod:`unittest` or :class:`TestSuite` executions:: sage: QQ._tester().assertTrue(0 in QQ, - ... "%s doesn't contain 0"%QQ) + ....: "%s doesn't contain 0"%QQ) In the above ``QQ.__repr__()`` has been called, and the result immediately discarded. To demonstrate this we replace ``QQ`` in the format string argument with our broken object:: sage: QQ._tester().assertTrue(True, - ... "%s doesn't contain 0"%IDontLikeBeingPrinted()) + ....: "%s doesn't contain 0"%IDontLikeBeingPrinted()) Traceback (most recent call last): ... ValueError: Don't ever try to print me ! @@ -69,9 +72,9 @@ class LazyFormat(str): We now check that :class:`LazyFormat` indeed solves the assertion problem:: sage: QQ._tester().assertTrue(True, - ... LazyFormat("%s is wrong")%IDontLikeBeingPrinted()) + ....: LazyFormat("%s is wrong")%IDontLikeBeingPrinted()) sage: QQ._tester().assertTrue(False, - ... LazyFormat("%s is wrong")%IDontLikeBeingPrinted()) + ....: LazyFormat("%s is wrong")%IDontLikeBeingPrinted()) Traceback (most recent call last): ... AssertionError: @@ -84,14 +87,14 @@ def __mod__(self, args): EXAMPLES:: sage: from sage.misc.lazy_format import LazyFormat - sage: form = LazyFormat("<%s>"); + sage: form = LazyFormat("<%s>") sage: form unbound LazyFormat("<%s>") sage: form%"params" """ if hasattr(self, "_args"): # self is already bound... - self = copy(self) + self = LazyFormat(""+self) self._args = args return self diff --git a/src/sage/misc/lazy_string.pxd b/src/sage/misc/lazy_string.pxd new file mode 100644 index 00000000000..205276f6fed --- /dev/null +++ b/src/sage/misc/lazy_string.pxd @@ -0,0 +1,6 @@ +cdef class _LazyString(object): + cdef func + cdef args + cdef kwargs + cdef value(self) + cpdef update_lazy_string(self, args, kwds) diff --git a/src/sage/misc/lazy_string.py b/src/sage/misc/lazy_string.pyx similarity index 60% rename from src/sage/misc/lazy_string.py rename to src/sage/misc/lazy_string.pyx index 7c5148ae483..f5ca99f288b 100644 --- a/src/sage/misc/lazy_string.py +++ b/src/sage/misc/lazy_string.pyx @@ -62,6 +62,8 @@ #(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from cpython.object cimport PyObject_Call, PyObject_RichCompare + import types def is_lazy_string(obj): @@ -78,18 +80,46 @@ def is_lazy_string(obj): """ return isinstance(obj, _LazyString) -def lazy_string(func, *args, **kwargs): +def lazy_string(f, *args, **kwargs): """ - Creates a lazy string by invoking func with args. + Creates a lazy string. + + INPUT: + + - ``f``, either a callable or a (format) string + - positional arguments that are given to ``f``, either by calling or by + applying it as a format string + - named arguments, that are forwarded to ``f`` if it is not a string + EXAMPLES:: sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f); s - l'laziness' + sage: f = lambda x: "laziness in "+str(x) + sage: s = lazy_string(f, ZZ); s + l'laziness in Integer Ring' + + Here, we demonstrate that the evaluation is postponed until the value is + needed, and that the result is not cached:: + + sage: class C: + ....: def __repr__(self): + ....: print "determining string representation" + ....: return "a test" + sage: c = C() + sage: s = lazy_string("this is %s", c) + sage: s + determining string representation + l'this is a test' + sage: s == 'this is a test' + determining string representation + True + sage: unicode(s) + determining string representation + u'this is a test' + """ - return _LazyString(func, args, kwargs) + return _LazyString(f, args, kwargs) def _make_lazy_string(ftype, fpickle, args, kwargs): """ @@ -109,36 +139,110 @@ def _make_lazy_string(ftype, fpickle, args, kwargs): f = fpickle return _LazyString(f, args, kwargs) -class _LazyString(object): +cdef class _LazyString(object): """ - Class for strings created by a function call. + Lazy class for strings created by a function call or a format string. + + INPUT: - The proxy implementation attempts to be as complete as possible, so that - the lazy objects should mostly work as expected, for example for sorting. + - ``f``, either a callable or a (format) string + - ``args``, a tuple of arguments that are given to ``f``, either by calling + or by applying it as a format string + - ``kwargs``, a dictionary of optional arguments, that are forwarded to ``f`` + if it is a callable. + + .. NOTE:: + + Evaluation of ``f`` is postponed until it becomes necessary, e.g., for + comparison. The result of evaluation is not cached. The proxy + implementation attempts to be as complete as possible, so that the + lazy objects should mostly work as expected, for example for sorting. + + The function :func:`lazy_string` creates lazy strings in a slightly more + convenient way, because it is then not needed to provide the arguments as + tuple and dictionary. EXAMPLES:: - sage: from sage.misc.lazy_string import lazy_string + sage: from sage.misc.lazy_string import lazy_string, _LazyString sage: f = lambda x: "laziness in the " + repr(x) sage: s = lazy_string(f, ZZ); s l'laziness in the Integer Ring' + sage: lazy_string("laziness in the %s", ZZ) + l'laziness in the Integer Ring' + + Here, we demonstrate that the evaluation is postponed until the value is + needed, and that the result is not cached. Also, we create a lazy string directly, + without calling :func:`lazy_string`:: + + sage: class C: + ....: def __repr__(self): + ....: print "determining string representation" + ....: return "a test" + sage: c = C() + sage: s = _LazyString("this is %s", (c,), {}) + sage: s + determining string representation + l'this is a test' + sage: s == 'this is a test' + determining string representation + True + sage: unicode(s) + determining string representation + u'this is a test' + """ - __slots__ = ('_func', '_args', '_kwargs') - def __init__(self, func, args, kwargs): + def __init__(self, f, args, kwargs): """ + INPUT: + + - ``f``, either a callable or a (format) string + - ``args``, a tuple of arguments that are given to ``f``, either by calling + or by applying it as a format string + - ``kwargs``, a dictionary of optional arguments, that are forwarded to ``f`` + if it is a callable. + EXAMPLES:: sage: from sage.misc.lazy_string import lazy_string sage: f = lambda x: "laziness" + repr(x) sage: s = lazy_string(f, 5); s l'laziness5' + sage: lazy_string("This is %s", ZZ) + l'This is Integer Ring' + sage: lazy_string(u"This is %s", ZZ) + lu'This is Integer Ring' """ - self._func = func - self._args = args - self._kwargs = kwargs + self.func = f + self.args = args + self.kwargs = kwargs + + cdef value(self): + cdef f = self.func + if isinstance(f, basestring): + return f % self.args + return PyObject_Call(f, self.args, self.kwargs) + + property value: + def __get__(self): + """ + Return the value of this lazy string, as an ordinary string. - value = property(lambda x: x._func(*x._args, **x._kwargs)) + EXAMPLES:: + + sage: from sage.misc.lazy_string import lazy_string + sage: f = lambda: "laziness" + sage: lazy_string(f).value + 'laziness' + + :: + + sage: from sage.misc.lazy_string import lazy_string + sage: lazy_string("%s", "laziness").value + 'laziness' + """ + return self.value() def __contains__(self, key): """ @@ -152,7 +256,7 @@ def __contains__(self, key): sage: 'ni' in s False """ - return key in self.value + return key in self.value() def __nonzero__(self): """ @@ -166,7 +270,7 @@ def __nonzero__(self): sage: bool(lazy_string(f)) False """ - return bool(self.value) + return bool(self.value()) def __dir__(self): """ @@ -193,7 +297,7 @@ def __iter__(self): sage: "".join(list(s)) # indirect doctest 'laziness' """ - return iter(self.value) + return iter(self.value()) def __len__(self): """ @@ -205,7 +309,7 @@ def __len__(self): sage: len(s) 8 """ - return len(self.value) + return len(self.value()) def __str__(self): """ @@ -217,7 +321,7 @@ def __str__(self): sage: str(s) # indirect doctest 'laziness' """ - return str(self.value) + return str(self.value()) def __unicode__(self): """ @@ -229,7 +333,7 @@ def __unicode__(self): sage: unicode(s) # indirect doctest u'laziness' """ - return unicode(self.value) + return unicode(self.value()) def __add__(self, other): """ @@ -241,19 +345,10 @@ def __add__(self, other): sage: s + " supreme" 'laziness supreme' """ - return self.value + other - - def __radd__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) - sage: "no " + s - 'no laziness' - """ - return other + self.value + if isinstance(self, _LazyString): + return (<_LazyString>self).value() + other + else: + return self + (<_LazyString>other).value() def __mod__(self, other): """ @@ -264,20 +359,16 @@ def __mod__(self, other): sage: s = lazy_string(f) sage: s % "ine" 'laziness' - """ - return self.value % other - - def __rmod__(self, other): - """ - EXAMPLES:: - sage: from sage.misc.lazy_string import lazy_string sage: f = lambda: "ine" sage: s = lazy_string(f) sage: "laz%sss" % s 'laziness' """ - return other % self.value + if isinstance(self, _LazyString): + return (<_LazyString>self).value() % other + else: + return self % (<_LazyString>other).value() def __mul__(self, other): """ @@ -288,22 +379,15 @@ def __mul__(self, other): sage: s = lazy_string(f) sage: s * 2 'lazinesslaziness' - """ - return self.value * other - - def __rmul__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: 2 * s 'lazinesslaziness' """ - return other * self.value + if isinstance(self, _LazyString): + return (<_LazyString>self).value() * other + else: + return self * (<_LazyString>other).value() - def __lt__(self, other): + def __richcmp__(self, other, int op): """ EXAMPLES:: @@ -316,80 +400,30 @@ def __lt__(self, other): False sage: s < s False - """ - return self.value < other - - def __le__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: s <= 'laziness' True sage: s <= 'azi' False sage: s <= s True - """ - return self.value <= other - - def __eq__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: s == 'laziness' True sage: s == 'azi' False sage: s == s True - """ - return self.value == other - - def __ne__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: s != 'laziness' False sage: s != 'azi' True sage: s != s False - """ - return self.value != other - - def __gt__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: s > 'laziness' False sage: s > 'azi' True sage: s > s False - """ - return self.value > other - - def __ge__(self, other): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) sage: s >= 'laziness' True sage: s >= 'azi' @@ -397,7 +431,8 @@ def __ge__(self, other): sage: s >= s True """ - return self.value >= other + self = (<_LazyString?>self).value() + return PyObject_RichCompare(self, other, op) def __getattr__(self, name): """ @@ -415,7 +450,7 @@ def __getattr__(self, name): """ if name == '__members__': return self.__dir__() - return getattr(self.value, name) + return getattr(self.value(), name) def __reduce__(self): """ @@ -428,15 +463,14 @@ def __reduce__(self): sage: s = lazy_string(f) sage: TestSuite(s).run() # indirect doctest """ - import types - if isinstance(self._func, types.FunctionType): + if isinstance(self.func, types.FunctionType): from sage.misc.fpickle import pickle_function - f = pickle_function(self._func) + f = pickle_function(self.func) ftype = 'func' else: f = self.func ftype = None - return _make_lazy_string, (ftype, f, self._args, self._kwargs) + return _make_lazy_string, (ftype, f, self.args, self.kwargs) def __getitem__(self, key): """ @@ -448,7 +482,7 @@ def __getitem__(self, key): sage: s[4] 'n' """ - return self.value[key] + return self.value()[key] def __copy__(self): """ @@ -473,6 +507,46 @@ def __repr__(self): l'laziness' """ try: - return 'l' + repr(self.value) + return 'l' + repr(self.value()) except Exception: return '<%s broken>' % self.__class__.__name__ + + cpdef update_lazy_string(self, args, kwds): + """ + Change this lazy string in-place. + + INPUT: + + - ``args``, a tuple + - ``kwds``, a dict + + .. NOTE:: + + Lazy strings are not hashable, and thus an in-place change is + allowed. + + EXAMPLES:: + + sage: from sage.misc.lazy_string import lazy_string + sage: f = lambda op,A,B:"unsupported operand parent(s) for '%s': '%s' and '%s'"%(op,A,B) + sage: R = GF(5) + sage: S = GF(3) + sage: D = lazy_string(f, '+', R, S) + sage: D + l"unsupported operand parent(s) for '+': 'Finite Field of size 5' and 'Finite Field of size 3'" + sage: D.update_lazy_string(('+', S, R), {}) + + Apparently, the lazy string got changed in-place:: + + sage: D + l"unsupported operand parent(s) for '+': 'Finite Field of size 3' and 'Finite Field of size 5'" + + TESTS:: + + sage: D.update_lazy_string(None, None) + Traceback (most recent call last): + ... + TypeError: Expected tuple, got NoneType + """ + self.args = args + self.kwargs = kwds diff --git a/src/sage/misc/rest_index_of_methods.py b/src/sage/misc/rest_index_of_methods.py new file mode 100644 index 00000000000..6c74764b983 --- /dev/null +++ b/src/sage/misc/rest_index_of_methods.py @@ -0,0 +1,146 @@ +r""" +ReST index of functions + +This module contains a function that generates a ReST index table of functions +for use in doc-strings. + +{INDEX_OF_FUNCTIONS} + +""" +def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): + r""" + Return a ReST table describing a list of functions. + + The list of functions can either be given explicitly, or implicitly as the + functions/methods of a module or class. + + In the case of a class, only non-inherited methods are listed. + + INPUT: + + - ``list_of_entries`` -- a list of functions, a module or a class. If given + a list of functions, the generated table will consist of these. If given a + module or a class, all functions/methods it defines will be listed, except + deprecated or those starting with an underscore. In the case of a class, + note that inherited methods are not displayed. + + - ``sort`` (boolean; ``True``) -- whether to sort the list of methods + lexicographically. + + - ``only_local_functions`` (boolean; ``True``) -- if ``list_of_entries`` 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_rest_table_index + sage: print gen_rest_table_index([graphs.PetersenGraph]) + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :func:`~sage.graphs.generators.smallgraphs.PetersenGraph` | The Petersen Graph is a named graph that consists of 10 vertices... + + The table of a module:: + + sage: print gen_rest_table_index(sage.misc.rest_index_of_methods) + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :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:: + + sage: print gen_rest_table_index(Graph) + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + ... + :meth:`~sage.graphs.graph.Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string. + ... + + TESTS: + + The inherited methods do not show up:: + + sage: gen_rest_table_index(sage.combinat.posets.lattices.FiniteLatticePoset).count('\n') < 50 + True + sage: from sage.graphs.generic_graph import GenericGraph + sage: A = gen_rest_table_index(Graph).count('\n') + sage: B = gen_rest_table_index(GenericGraph).count('\n') + sage: A < B + True + + When ``only_local_functions`` is ``False``, we do not include + ``gen_rest_table_index`` itself:: + + sage: print gen_rest_table_index(sage.misc.rest_index_of_methods, only_local_functions=True) + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :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: | + + + + """ + import inspect + + # 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 + )] + + assert isinstance(list_of_entries,list) + + s = (".. csv-table::\n" + " :class: contentstable\n" + " :widths: 30, 70\n" + " :delim: |\n\n") + + if sort: + list_of_entries.sort(key=lambda x:getattr(x,'__name__','')) + + for e in list_of_entries: + + if inspect.ismethod(e): + link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+e.__name__+"`" + elif inspect.isfunction(e): + link = ":func:`~"+str(e.__module__)+"."+str(e.__name__)+"`" + else: + continue + + # Descriptions of the method/function + if e.__doc__: + desc = e.__doc__.splitlines() + desc = desc[0] if desc[0] else desc[1] + else: + desc = "NO DOCSTRING" + + s += " {} | {}\n".format(link,desc.lstrip()) + + return s+'\n' + +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index([gen_rest_table_index])) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index c1cb41d455e..0419400dbf3 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -352,7 +352,7 @@ def process_extlinks(s, embedded=False): if embedded: return s oldpath = sys.path - sys.path = oldpath + [os.path.join(SAGE_DOC, 'common')] + sys.path = [os.path.join(SAGE_DOC, 'common')] + oldpath from conf import pythonversion, extlinks sys.path = oldpath for key in extlinks: @@ -797,7 +797,7 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', if re.search(string, line, flags)] for extra in [extra1, extra2, extra3, extra4, extra5]: if extra: - match_list = [s for s in match_list + match_list = [s for s in match_list if re.search(extra, s[1], re.MULTILINE | flags)] for num, line in match_list: results += ':'.join([filename[strip:].lstrip("/"), str(num+1), line]) @@ -943,9 +943,9 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', ``search_src(string, interact=False).splitlines()`` gives the number of matches. :: - sage: len(search_src('log', 'derivative', interact=False).splitlines()) < 10 + sage: len(search_src('log', 'derivative', interact=False).splitlines()) < 40 True - sage: len(search_src('log', 'derivative', interact=False, multiline=True).splitlines()) > 30 + sage: len(search_src('log', 'derivative', interact=False, multiline=True).splitlines()) > 70 True A little recursive narcissism: let's do a doctest that searches for @@ -965,8 +965,8 @@ def search_src(string, extra1='', extra2='', extra3='', extra4='', 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:... len(search_src('log', 'derivative', interact=False).splitlines()) < 10 - misc/sagedoc.py:... len(search_src('log', 'derivative', interact=False, multiline=True).splitlines()) > 30 + 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 misc/sagedoc.py:... len(search_src("matrix", interact=False).splitlines()) > 9000 # long time misc/sagedoc.py:... print search_src('matrix', 'column', 'row', 'sub', 'start', 'index', interact=False) # random # long time @@ -1446,7 +1446,7 @@ def help(module=None): if hasattr(module, '_sage_doc_'): from sage.misc.sageinspect import sage_getdef, _sage_getdoc_unformatted docstr = 'Help on ' + str(module) + '\n' - docstr += 'Definition: ' + module.__name__ + sage_getdef(module) + '\n' + docstr += 'Definition: ' + module.__name__ + sage_getdef(module) + '\n' pydoc.pager(docstr + _sage_getdoc_unformatted(module)) else: python_help(module) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 9a078ee799f..744b874c3c5 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -1439,11 +1439,11 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return argspec = _extract_embedded_signature(docstring, name)[1] if argspec is not None: return argspec - if hasattr(obj, 'func_code'): + if hasattr(obj, '__code__'): # Note that this may give a wrong result for the constants! try: - args, varargs, varkw = inspect.getargs(obj.func_code) - return inspect.ArgSpec(args, varargs, varkw, obj.func_defaults) + args, varargs, varkw = inspect.getargs(obj.__code__) + return inspect.ArgSpec(args, varargs, varkw, obj.__defaults__) except (TypeError, AttributeError): pass if isclassinstance(obj): diff --git a/src/sage/modular/arithgroup/arithgroup_element.pyx b/src/sage/modular/arithgroup/arithgroup_element.pyx index 78fa74f27d9..3402a75d254 100644 --- a/src/sage/modular/arithgroup/arithgroup_element.pyx +++ b/src/sage/modular/arithgroup/arithgroup_element.pyx @@ -158,17 +158,6 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): """ return '%s' % self.__x._latex_() - def __richcmp__(left, right, int op): - r""" - Rich comparison. - - EXAMPLE:: - - sage: SL2Z.0 > None - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: """ Compare self to right, where right is guaranteed to have the same @@ -176,6 +165,9 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): EXAMPLES:: + sage: SL2Z.0 > None + True + sage: x = Gamma0(18)([19,1,18,1]) sage: cmp(x, 3) is not 0 True @@ -186,7 +178,7 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): sage: x == 0 False - This once caused a segfault (see trac #5443):: + This once caused a segfault (see :trac:`5443`):: sage: r,s,t,u,v = map(SL2Z, [[1, 1, 0, 1], [-1, 0, 0, -1], [1, -1, 0, 1], [1, -1, 2, -1], [-1, 1, -2, 1]]) sage: v == s*u diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 81d64fb52c3..bc97fb98b2f 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -1879,14 +1879,14 @@ def create_key(self, N, base_ring=None, zeta=None, zeta_order=None, names=None, if base_ring is None: if not (zeta is None and zeta_order is None): - raise ValueError, "zeta and zeta_order must be None if base_ring not specified." + raise ValueError("zeta and zeta_order must be None if base_ring not specified.") e = rings.IntegerModRing(modulus).unit_group_exponent() base_ring = rings.CyclotomicField(e) if integral: base_ring = base_ring.ring_of_integers() if not is_Ring(base_ring): - raise TypeError, "base_ring (=%s) must be a ring"%base_ring + raise TypeError("base_ring (=%s) must be a ring"%base_ring) if zeta is None: e = rings.IntegerModRing(modulus).unit_group_exponent() diff --git a/src/sage/modular/modform_hecketriangle/functors.py b/src/sage/modular/modform_hecketriangle/functors.py index f7d42eb9a2e..fc3001c58ac 100644 --- a/src/sage/modular/modform_hecketriangle/functors.py +++ b/src/sage/modular/modform_hecketriangle/functors.py @@ -298,9 +298,9 @@ def __eq__(self, other): False """ - if ( type(self) == type(other)\ - and self._ambient_space_functor == other._ambient_space_functor\ - and self._generators == other._generators ): + if (type(self) is type(other) and + self._ambient_space_functor == other._ambient_space_functor and + self._generators == other._generators): return True else: return False @@ -485,11 +485,11 @@ def __eq__(self, other): False """ - if ( type(self) == type(other)\ - and self._group == other._group\ - and self._analytic_type == other._analytic_type\ - and self._k == other._k\ - and self._ep == other._ep ): + if (type(self) is type(other) and + self._group == other._group and + self._analytic_type == other._analytic_type and + self._k == other._k and + self._ep == other._ep): return True else: return False @@ -672,10 +672,10 @@ def __eq__(self, other): False """ - if ( type(self) == type(other)\ - and self._group == other._group\ - and self._analytic_type == other._analytic_type\ - and self._red_hom == other._red_hom ): + if (type(self) is type(other) and + self._group == other._group and + self._analytic_type == other._analytic_type and + self._red_hom == other._red_hom): return True else: return False diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index b115b3fb2ba..c1a05835135 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -2100,7 +2100,7 @@ def evaluate(self, tau, prec = None, num_prec = None, check=False): q_exp = self.q_expansion_fixed_d(prec=prec, d_num_prec=num_prec) (A, w) = self.group().get_FD(tau) aut_factor = self.reduce(force=True).parent().aut_factor(A, w) - if (type(q_exp) == LaurentSeries): + if (type(q_exp) is LaurentSeries): return q_exp.laurent_polynomial()(exp((2 * pi * i).n(num_prec) / self.group().lam() * w)) * aut_factor else: return q_exp.polynomial()(exp((2 * pi * i).n(num_prec) / self.group().lam() * w)) * aut_factor diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index b1b103633fb..0fb54c234ae 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -1022,6 +1022,7 @@ cdef class FreeModuleElement(Vector): # abstract base class Return hash of this vector. Only mutable vectors are hashable. EXAMPLES:: + sage: v = vector([1,2/3,pi]) sage: v.__hash__() Traceback (most recent call last): @@ -1382,6 +1383,7 @@ cdef class FreeModuleElement(Vector): # abstract base class is mutable). EXAMPLES:: + sage: v = vector([1,2/3,pi]) sage: v.__hash__() Traceback (most recent call last): @@ -1684,17 +1686,7 @@ cdef class FreeModuleElement(Vector): # abstract base class True sage: w > v False - """ - cdef Py_ssize_t i - cdef int c - for i in range(left._degree): - c = cmp(left[i], right[i]) - if c: return c - return 0 - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ TESTS:: sage: F. = PolynomialRing(QQ, 'y') @@ -1705,7 +1697,12 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: vector(F, [0,0,0,0]) == vector(F, [0,2,0,y]) False """ - return (left)._richcmp(right, op) + cdef Py_ssize_t i + cdef int c + for i in range(left._degree): + c = cmp(left[i], right[i]) + if c: return c + return 0 def __getitem__(self, i): """ @@ -3797,6 +3794,19 @@ cdef class FreeModuleElement_generic_dense(FreeModuleElement): sage: v = (QQ['x']^3).0 sage: loads(dumps(v)) == v True + + :: + + sage: v = vector([1,2/3,pi]) + sage: v == v + True + + :: + + sage: v = vector(RR, [1,2/3,pi]) + sage: v.set_immutable() + sage: isinstance(hash(v), int) + True """ cdef _new_c(self, object v): """ @@ -4024,29 +4034,6 @@ cdef class FreeModuleElement_generic_dense(FreeModuleElement): v = [( a[i])._mul_( b[i]) for i in range(left._degree)] return left._new_c(v) - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ - TESTS:: - - sage: v = vector([1,2/3,pi]) - sage: v == v - True - """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TESTS:: - - sage: v = vector(RR, [1,2/3,pi]) - sage: v.set_immutable() - sage: isinstance(hash(v), int) - True - """ - return FreeModuleElement.__hash__(self) - def __reduce__(self): """ EXAMPLES:: @@ -4233,7 +4220,12 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): A generic sparse free module element is a dictionary with keys ints i and entries in the base ring. - EXAMPLES: + TESTS:: + + sage: v = vector([1,2/3,pi], sparse=True) + sage: v.set_immutable() + sage: isinstance(hash(v), int) + True Pickling works:: @@ -4566,30 +4558,6 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): return cmp(a, b) - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ - TESTS:: - - sage: v = vector([1,2/3,pi], sparse=True) - sage: v == v - True - """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TESTS:: - - sage: v = vector([1,2/3,pi], sparse=True) - sage: v.set_immutable() - sage: isinstance(hash(v), int) - True - """ - return FreeModuleElement.__hash__(self) - - def iteritems(self): """ Return iterator over the entries of self. diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 78de0a680fa..cb5301852a8 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -200,9 +200,9 @@ def _call_with_args(self, x, args=(), kwds={}): sage: f((1, 0)) Traceback (most recent call last): ... - TypeError: Unable to coerce entries (=[1.00000000000000*I, 0]) to coefficients in Real Field with 53 bits of precision + TypeError: Unable to coerce entries (=[1.00000000000000*I, 0.000000000000000]) to coefficients in Real Field with 53 bits of precision sage: f((1, 0), coerce=False) - (1.00000000000000*I, 0) + (1.00000000000000*I, 0.000000000000000) """ if self.domain().is_ambient(): diff --git a/src/sage/modules/vector_integer_dense.pyx b/src/sage/modules/vector_integer_dense.pyx index 562bc37fd48..e73a97afe91 100644 --- a/src/sage/modules/vector_integer_dense.pyx +++ b/src/sage/modules/vector_integer_dense.pyx @@ -36,14 +36,23 @@ TESTS:: sage: v = vector(ZZ, [1,2,3,4]) sage: loads(dumps(v)) == v True + + sage: w = vector(ZZ, [-1,0,0,0]) + sage: w.set_immutable() + sage: isinstance(hash(w), int) + True """ -############################################################################### -# Sage: System for Algebra and Geometry Experimentation +#***************************************************************************** # Copyright (C) 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/ -############################################################################### +#***************************************************************************** + include 'sage/ext/interrupt.pxi' include 'sage/ext/stdsage.pxi' @@ -133,6 +142,8 @@ cdef class Vector_integer_dense(free_module_element.FreeModuleElement): sage: v == v True sage: w = vector(ZZ, [-1,0,0,0]) + sage: w == w + True sage: w < v True sage: w > v @@ -148,29 +159,6 @@ cdef class Vector_integer_dense(free_module_element.FreeModuleElement): return 1 return 0 - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ - TEST:: - - sage: w = vector(ZZ, [-1,0,0,0]) - sage: w == w - True - """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TEST:: - - sage: w = vector(ZZ, [-1,0,0,0]) - sage: w.set_immutable() - sage: isinstance(hash(w), int) - True - """ - return free_module_element.FreeModuleElement.__hash__(self) - cdef get_unsafe(self, Py_ssize_t i): """ EXAMPLES:: diff --git a/src/sage/modules/vector_mod2_dense.pyx b/src/sage/modules/vector_mod2_dense.pyx index bbee594761f..396a03a9672 100644 --- a/src/sage/modules/vector_mod2_dense.pyx +++ b/src/sage/modules/vector_mod2_dense.pyx @@ -15,14 +15,24 @@ EXAMPLES:: (0, 1, 1) sage: e + f (1, 1, 1) + +TESTS:: + + sage: w = vector(GF(2), [-1,0,0,0]) + sage: w.set_immutable() + sage: isinstance(hash(w), int) + True """ -############################################################################## +#***************************************************************************** # Copyright (C) 2009 Martin Albrecht -# 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/interrupt.pxi' @@ -199,33 +209,13 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): False sage: w > v True - """ - if left._degree == 0: - return 0 - return mzd_cmp(left._entries, (right)._entries) - - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ - TEST:: - sage: w = vector(GF(2), [-1,0,0,0]) sage: w == w True """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TEST:: - - sage: w = vector(GF(2), [-1,0,0,0]) - sage: w.set_immutable() - sage: isinstance(hash(w), int) - True - """ - return free_module_element.FreeModuleElement.__hash__(self) + if left._degree == 0: + return 0 + return mzd_cmp(left._entries, (right)._entries) cdef get_unsafe(self, Py_ssize_t i): """ diff --git a/src/sage/modules/vector_modn_dense.pyx b/src/sage/modules/vector_modn_dense.pyx index 71308fe575a..a358c96d2b9 100644 --- a/src/sage/modules/vector_modn_dense.pyx +++ b/src/sage/modules/vector_modn_dense.pyx @@ -1,10 +1,8 @@ """ Vectors with integer mod n entries, with n small. -AUTHOR: - -- William Stein (2007) +EXAMPLES:: -EXAMPLES: sage: v = vector(Integers(8),[1,2,3,4,5]) sage: type(v) @@ -37,20 +35,23 @@ EXAMPLES: sage: u - v (0, 0, 0, 0, 4) -We make a large zero vector: +We make a large zero vector:: + sage: k = Integers(8)^100000; k Ambient free module of rank 100000 over Ring of integers modulo 8 sage: v = k(0) sage: v[:10] (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) -We multiply a vector by a matrix: +We multiply a vector by a matrix:: + sage: a = (GF(97)^5)(range(5)) sage: m = matrix(GF(97),5,range(25)) sage: a*m (53, 63, 73, 83, 93) -TESTS: +TESTS:: + sage: v = vector(Integers(8), [1,2,3,4,5]) sage: loads(dumps(v)) == v True @@ -72,14 +73,26 @@ TESTS: sage: ~v[0] 1482786336 + + sage: w = vector(GF(11), [-1,0,0,0]) + sage: w.set_immutable() + sage: isinstance(hash(w), int) + True + +AUTHOR: + +- William Stein (2007) """ -############################################################################### -# Sage: System for Algebra and Geometry Experimentation +#***************************************************************************** # Copyright (C) 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/ -############################################################################### +#***************************************************************************** include 'sage/ext/interrupt.pxi' include 'sage/ext/stdsage.pxi' @@ -175,6 +188,9 @@ cdef class Vector_modn_dense(free_module_element.FreeModuleElement): False sage: v == v True + sage: w = vector(GF(11), [-1,0,0,0]) + sage: w == w + True """ cdef Py_ssize_t i cdef mod_int l, r @@ -187,28 +203,6 @@ cdef class Vector_modn_dense(free_module_element.FreeModuleElement): return 1 return 0 - def __richcmp__(left, right, int op): - """ - TEST:: - - sage: w = vector(GF(11), [-1,0,0,0]) - sage: w == w - True - """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TEST:: - - sage: w = vector(GF(11), [-1,0,0,0]) - sage: w.set_immutable() - sage: isinstance(hash(w), int) - True - """ - return free_module_element.FreeModuleElement.__hash__(self) - cdef get_unsafe(self, Py_ssize_t i): """ EXAMPLES:: diff --git a/src/sage/modules/vector_rational_dense.pyx b/src/sage/modules/vector_rational_dense.pyx index 8d1918e5023..e74cacb40a7 100644 --- a/src/sage/modules/vector_rational_dense.pyx +++ b/src/sage/modules/vector_rational_dense.pyx @@ -37,6 +37,11 @@ TESTS:: sage: v = vector(QQ, [1,2/5,-3/8,4]) sage: loads(dumps(v)) == v True + + sage: w = vector(QQ, [-1,0,0,0]) + sage: w.set_immutable() + sage: isinstance(hash(w), int) + True """ ############################################################################### @@ -162,6 +167,9 @@ cdef class Vector_rational_dense(free_module_element.FreeModuleElement): True sage: w > v False + sage: w = vector(QQ, [-1,0,0,0]) + sage: w == w + True """ cdef Py_ssize_t i cdef int c @@ -173,29 +181,6 @@ cdef class Vector_rational_dense(free_module_element.FreeModuleElement): return 1 return 0 - # see sage/structure/element.pyx - def __richcmp__(left, right, int op): - """ - TEST:: - - sage: w = vector(QQ, [-1,0,0,0]) - sage: w == w - True - """ - return (left)._richcmp(right, op) - - # __hash__ is not properly inherited if comparison is changed - def __hash__(self): - """ - TEST:: - - sage: w = vector(QQ, [-1,0,0,0]) - sage: w.set_immutable() - sage: isinstance(hash(w), int) - True - """ - return free_module_element.FreeModuleElement.__hash__(self) - cdef get_unsafe(self, Py_ssize_t i): """ EXAMPLES:: diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index e145e10a87b..6de2afe134d 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -996,7 +996,7 @@ def coreduced(self, y): """ G = self.codomain() if G.base_ring() not in Fields and not self._unitriangular: - raise NotImplementedError, "coreduce for a triangular but not unitriangular morphism over a ring" + raise NotImplementedError("coreduce for a triangular but not unitriangular morphism over a ring") on_basis = self.on_basis() assert y in G @@ -1068,9 +1068,9 @@ def cokernel_basis_indices(self): """ R = self.domain().base_ring() if R not in Fields and not self._unitriangular: - raise NotImplementedError, "cokernel_basis_indices for a triangular but not unitriangular morphism over a ring" + raise NotImplementedError("cokernel_basis_indices for a triangular but not unitriangular morphism over a ring") if self.codomain() not in Modules(R).FiniteDimensional(): - raise NotImplementedError, "cokernel_basis_indices implemented only for morphisms with a finite dimensional codomain" + raise NotImplementedError("cokernel_basis_indices implemented only for morphisms with a finite dimensional codomain") return [i for i in self.codomain().basis().keys() if self._inverse_on_support(i) is None] def cokernel_projection(self, category = None): diff --git a/src/sage/monoids/automatic_semigroup.py b/src/sage/monoids/automatic_semigroup.py index 8eaa2058c05..da5cf127bc6 100644 --- a/src/sage/monoids/automatic_semigroup.py +++ b/src/sage/monoids/automatic_semigroup.py @@ -994,7 +994,7 @@ def one(self): return self._one # This method takes the monoid generators and adds the unit - semigroup_generators = Monoids.ParentMethods.semigroup_generators.im_func + semigroup_generators = Monoids.ParentMethods.semigroup_generators.__func__ def monoid_generators(self): """ diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index a0119e1d250..44f9b6e0694 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -1430,7 +1430,8 @@ end_scene""" % (render_params.antialiasing, sage: assert open(gif).read().startswith('GIF') """ ext = os.path.splitext(filename)[1].lower() - if ext not in ['.bmp', '.png', '.gif', '.ppm', '.tiff', '.tif', '.jpg', '.jpeg']: + if ext not in ['.bmp', '.png', '.gif', '.ppm', '.tiff', '.tif', + '.jpg', '.jpeg']: raise ValueError('unknown image file type: {0}'.format(ext)) if ext == '.png': self._save_image_png(filename, **kwds) @@ -1442,11 +1443,22 @@ end_scene""" % (render_params.antialiasing, def save(self, filename, **kwds): """ - Save to file. + Save the graphic in a file. - Save the graphic to an image file (of type: PNG, BMP, GIF, PPM, or TIFF) - rendered using Tachyon, or pickle it (stored as an SOBJ so you can load it - later) depending on the file extension you give the filename. + The file type depends on the file extension you give in the + filename. This can be either: + + - an image file (of type: PNG, BMP, GIF, PPM, or TIFF) rendered + using Tachyon, + + - a Sage object file (of type ``.sobj``) that you can load back later + (a pickle), + + - a data file (of type: X3D, STL, AMF, PLY) for export and use in + other software. + + For data files, the support is only partial. For instance STL and + AMF only works for triangulated surfaces. The prefered format is X3D. INPUT: @@ -1461,7 +1473,7 @@ end_scene""" % (render_params.antialiasing, EXAMPLES:: - sage: f = tmp_filename() + '.png' + sage: f = tmp_filename(ext='.png') sage: G = sphere() sage: G.save(f) @@ -1479,28 +1491,253 @@ end_scene""" % (render_params.antialiasing, alternate formats:: sage: cube().save(tmp_filename(ext='.gif')) + + Here is how to save in one of the data formats:: + + sage: f = tmp_filename(ext='.x3d') + sage: cube().save(f) + + sage: open(f).read().splitlines()[7] + "" """ ext = os.path.splitext(filename)[1].lower() if ext == '' or ext == '.sobj': SageObject.save(self, filename) - elif ext in ['.bmp', '.png', '.gif', '.ppm', '.tiff', '.tif', '.jpg', '.jpeg']: + elif ext in ['.bmp', '.png', '.gif', '.ppm', '.tiff', '.tif', + '.jpg', '.jpeg']: self.save_image(filename) elif filename.endswith('.spt.zip'): scene = self._rich_repr_jmol(**kwds) scene.jmol.save(filename) + elif ext == '.x3d': + outfile = file(filename, 'w') + outfile.write(self.x3d()) + outfile.close() + elif ext == '.stl': + outfile = file(filename, 'w') + outfile.write(self.stl_ascii_string()) + outfile.close() + elif ext == '.amf': + # todo : zip the output file ? + outfile = file(filename, 'w') + outfile.write(self.amf_ascii_string()) + outfile.close() + elif ext == '.ply': + outfile = file(filename, 'w') + outfile.write(self.ply_ascii_string()) + outfile.close() else: - raise ValueError('filetype not supported by save()') + raise ValueError('filetype {} not supported by save()'.format(ext)) + + def stl_ascii_string(self, name="surface"): + """ + Return an STL (STereoLithography) representation of the surface. + + .. WARNING:: + + This only works for triangulated surfaces! + + INPUT: + + - ``name`` (string, default: "surface") -- name of the surface. + + OUTPUT: + + A string that represents the surface in the STL format. + + See :wikipedia:`STL_(file_format)` + + EXAMPLES:: + + sage: x,y,z = var('x,y,z') + sage: a = implicit_plot3d(x^2+y^2+z^2-9,[x,-5,5],[y,-5,5],[z,-5,5]) + sage: astl = a.stl_ascii_string() + sage: astl.splitlines()[:7] + ['solid surface', + 'facet normal 0.973328526785 -0.162221421131 -0.162221421131', + ' outer loop', + ' vertex 2.94871794872 -0.384615384615 -0.39358974359', + ' vertex 2.95021367521 -0.384615384615 -0.384615384615', + ' vertex 2.94871794872 -0.39358974359 -0.384615384615', + ' endloop'] + + sage: p = polygon3d([[0,0,0], [1,2,3], [3,0,0]]) + sage: print p.stl_ascii_string(name='triangle') + solid triangle + facet normal 0.0 0.832050294338 -0.554700196225 + outer loop + vertex 0.0 0.0 0.0 + vertex 1.0 2.0 3.0 + vertex 3.0 0.0 0.0 + endloop + endfacet + endsolid triangle + """ + from sage.modules.free_module import FreeModule + RR3 = FreeModule(RDF, 3) + + faces = self.face_list() + if not faces: + self.triangulate() + faces = self.face_list() + + if len(faces[0]) > 3: + raise ValueError('not made of triangles') + + code = ("facet normal {} {} {}\n" + " outer loop\n" + " vertex {} {} {}\n" + " vertex {} {} {}\n" + " vertex {} {} {}\n" + " endloop\n" + "endfacet\n") + + string_list = ["solid {}\n".format(name)] + for i, j, k in faces: + ij = RR3(j) - RR3(i) + ik = RR3(k) - RR3(i) + n = ij.cross_product(ik) + n = n / n.norm() + string_list += [code.format(n[0], n[1], n[2], + i[0], i[1], i[2], + j[0], j[1], j[2], + k[0], k[1], k[2])] + string_list += ["endsolid {}".format(name)] + return "".join(string_list) + + def ply_ascii_string(self, name="surface"): + """ + Return a PLY (Polygon File Format) representation of the surface. + + INPUT: + + - ``name`` (string, default: "surface") -- name of the surface. + + OUTPUT: + + A string that represents the surface in the PLY format. + + See :wikipedia:`PLY_(file_format)` + + EXAMPLES:: + + sage: x,y,z = var('x,y,z') + sage: a = implicit_plot3d(x^2+y^2+z^2-9,[x,-5,5],[y,-5,5],[z,-5,5]) + sage: astl = a.ply_ascii_string() + sage: astl.splitlines()[:10] + ['ply', + 'format ascii 1.0', + 'comment surface', + 'element vertex 15540', + 'property float x', + 'property float y', + 'property float z', + 'element face 5180', + 'property list uchar int vertex_indices', + 'end_header'] + + sage: p = polygon3d([[0,0,0], [1,2,3], [3,0,0]]) + sage: print p.ply_ascii_string(name='triangle') + ply + format ascii 1.0 + comment triangle + element vertex 3 + property float x + property float y + property float z + element face 1 + property list uchar int vertex_indices + end_header + 0.0 0.0 0.0 + 1.0 2.0 3.0 + 3.0 0.0 0.0 + 3 0 1 2 + """ + faces = self.index_faces() + if not faces: + self.triangulate() + faces = self.index_faces() + + string_list = ["ply\nformat ascii 1.0\ncomment {}\nelement vertex {}\nproperty float x\nproperty float y\nproperty float z\nelement face {}\nproperty list uchar int vertex_indices\nend_header\n".format(name, len(self.vertex_list()), len(faces))] + + vertex_template = '{} {} {}\n' + for v in self.vertices(): + string_list += [vertex_template.format(*v)] + + for f in faces: + string_list += [str(len(f)) + + ''.join(' {}'.format(k) for k in f) + '\n'] + + return "".join(string_list) + + def amf_ascii_string(self, name="surface"): + """ + Return an AMF (Additive Manufacturing File Format) representation of + the surface. + + .. WARNING:: + + This only works for triangulated surfaces! + + INPUT: + + - ``name`` (string, default: "surface") -- name of the surface. + + OUTPUT: + + A string that represents the surface in the AMF format. + + See :wikipedia:`Additive_Manufacturing_File_Format` + + .. TODO:: + + This should rather be saved as a ZIP archive to save space. + + EXAMPLES:: + + sage: x,y,z = var('x,y,z') + sage: a = implicit_plot3d(x^2+y^2+z^2-9,[x,-5,5],[y,-5,5],[z,-5,5]) + sage: a_amf = a.amf_ascii_string() + sage: a_amf[:160] + '2.94871794872-0.384615384615-0.39358974359' + + sage: p = polygon3d([[0,0,0], [1,2,3], [3,0,0]]) + sage: print p.amf_ascii_string(name='triangle') + 0.00.00.01.02.03.03.00.00.0012 + """ + faces = self.index_faces() + if not faces: + self.triangulate() + faces = self.index_faces() + + if len(faces[0]) > 3: + raise ValueError('not made of triangles') + + string_list = [''.format(name)] + + string_list += [''] + vertex_template = '{}{}{}' + for v in self.vertices(): + string_list += [vertex_template.format(*v)] + string_list += [''] + + face_template = '{}{}{}' + for i, j, k in faces: + string_list += face_template.format(i, j, k) + + string_list += [''] + return "".join(string_list) # if you add any default parameters you must update some code below -SHOW_DEFAULTS = {'viewer':'jmol', - 'verbosity':0, - 'figsize':5, - 'aspect_ratio':"automatic", - 'frame_aspect_ratio':"automatic", - 'zoom':1, - 'frame':True, - 'axes':False} +SHOW_DEFAULTS = {'viewer': 'jmol', + 'verbosity': 0, + 'figsize': 5, + 'aspect_ratio': "automatic", + 'frame_aspect_ratio': "automatic", + 'zoom': 1, + 'frame': True, + 'axes': False} class Graphics3dGroup(Graphics3d): diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index cc52410fdc6..4e928ce6894 100644 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -669,8 +669,7 @@ def small_prime_value(self, Bmax=1000): B = 10 while True: llist = list(Set([self(x,y) for x in srange(-B,B) for y in srange(B)])) - llist = [l for l in llist if l.is_prime()] - llist.sort() + llist = sorted([l for l in llist if l.is_prime()]) if llist: return llist[0] if B >= Bmax: diff --git a/src/sage/quivers/paths.pyx b/src/sage/quivers/paths.pyx index 4fd834a0b72..200055de4cc 100644 --- a/src/sage/quivers/paths.pyx +++ b/src/sage/quivers/paths.pyx @@ -260,9 +260,21 @@ cdef class QuiverPath(MonoidElement): """ return self._path.length != 0 - def __cmp__(left, right): + cpdef int _cmp_(left, Element right) except -2: """ - Generic comparison code, copied from :class:`~sage.structure.element.Element`. + Comparison for :class:`QuiverPaths`. + + The following data (listed in order of preferance) is used for + comparison: + + - **Negative** length of the paths + - initial and terminal vertices of the paths + - Edge sequence of the paths, by reverse lexicographical ordering. + + .. NOTE:: + + This code is used by :class:`CombinatorialFreeModule` to order + the monomials when printing elements of path algebras. EXAMPLES: @@ -284,25 +296,6 @@ cdef class QuiverPath(MonoidElement): sage: D(1) == 1 False - """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element right) except -2: - """ - Comparison for :class:`QuiverPaths`. - - The following data (listed in order of preferance) is used for - comparison: - - - **Negative** length of the paths - - initial and terminal vertices of the paths - - Edge sequence of the paths, by reverse lexicographical ordering. - - .. NOTE:: - - This code is used by :class:`CombinatorialFreeModule` to order - the monomials when printing elements of path algebras. - TESTS:: sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c'], 4:['d']}}).path_semigroup() diff --git a/src/sage/repl/rich_output/display_manager.py b/src/sage/repl/rich_output/display_manager.py index 4e2c35cc27d..c2164cc0b25 100644 --- a/src/sage/repl/rich_output/display_manager.py +++ b/src/sage/repl/rich_output/display_manager.py @@ -125,7 +125,7 @@ def __init__(self, display_manager, output_classes): """ self._display_manager = display_manager self._output_classes = frozenset(output_classes) - + def __enter__(self): """ Enter the restricted output context @@ -158,7 +158,7 @@ def __enter__(self): self._original_prefs = DisplayPreferences(dm.preferences) dm.preferences.graphics = 'disable' dm.preferences.supplemental_plot = 'never' - + def __exit__(self, exception_type, value, traceback): """ Exit the restricted output context @@ -261,7 +261,7 @@ def types(self): """ import sage.repl.rich_output.output_catalog return sage.repl.rich_output.output_catalog - + def switch_backend(self, backend, **kwds): """ Switch to a new backend @@ -307,7 +307,7 @@ def switch_backend(self, backend, **kwds): self._backend.uninstall() except AttributeError: pass # first time we switch - # clear caches + # clear caches self._output_promotions = dict() self._supported_output = frozenset( map(self._demote_output_class, backend.supported_output())) @@ -372,7 +372,7 @@ def check_backend_class(self, backend_class): """ if not isinstance(self._backend, backend_class): raise RuntimeError('check failed: current backend is invalid') - + def _demote_output_class(self, output_class): """ Helper for :meth:`switch_backend`. @@ -385,7 +385,7 @@ def _demote_output_class(self, output_class): OUTPUT: The underlying container class that it was derived from. - + EXAMPLES:: sage: from sage.repl.rich_output import get_display_manager @@ -449,7 +449,7 @@ def _promote_output(self, output): return output output.__class__ = specialized_class return output - + def _preferred_text_formatter(self, obj, plain_text=None, **kwds): """ Return the preferred textual representation @@ -494,7 +494,7 @@ def _preferred_text_formatter(self, obj, plain_text=None, **kwds): OutputUnicodeArt container sage: dm.preferences.text = 'latex' - sage: dm._preferred_text_formatter([1/42]) + sage: dm._preferred_text_formatter([1/42]) \newcommand{\Bold}[1]{\mathbf{#1}}\verb|OutputLatex|\phantom{\verb!x!}\verb|container| sage: del dm.preferences.text # reset to default @@ -503,35 +503,35 @@ def _preferred_text_formatter(self, obj, plain_text=None, **kwds): supported = self._backend.supported_output() if want == 'ascii_art' and OutputAsciiArt in supported: out = self._backend.ascii_art_formatter(obj, **kwds) - if type(out) != OutputAsciiArt: + if type(out) is not OutputAsciiArt: raise OutputTypeException('backend returned wrong output type, require AsciiArt') return out if want == 'unicode_art' and OutputUnicodeArt in supported: out = self._backend.unicode_art_formatter(obj, **kwds) - if type(out) != OutputUnicodeArt: + if type(out) is not OutputUnicodeArt: raise OutputTypeException('backend returned wrong output type, require UnicodeArt') return out if want == 'latex' and OutputLatex in supported: out = self._backend.latex_formatter(obj, **kwds) - if type(out) != OutputLatex: + if type(out) is not OutputLatex: raise OutputTypeException('backend returned wrong output type, require Latex') return out if plain_text is not None: - if type(plain_text) != OutputPlainText: + if type(plain_text) is not OutputPlainText: raise OutputTypeException('backend returned wrong output type, require PlainText') return plain_text out = self._backend.plain_text_formatter(obj, **kwds) - if type(out) != OutputPlainText: + if type(out) is not OutputPlainText: raise OutputTypeException('backend returned wrong output type, require PlainText') return out def _call_rich_repr(self, obj, rich_repr_kwds): """ Call the ``_rich_repr_`` method - + This method calls ``obj._rich_repr_``. If this raises an exception, it is caught and a suitable warning is displayed. - + INPUT: - ``obj`` -- anything. @@ -623,19 +623,19 @@ def _rich_output_formatter(self, obj, rich_repr_kwds): raise OutputTypeException( 'output container not supported: {0}'.format(type(rich_output))) return plain_text, rich_output - - def graphics_from_save(self, save_function, save_kwds, + + def graphics_from_save(self, save_function, save_kwds, file_extension, output_container, figsize=None, dpi=None): r""" Helper to construct graphics. - + This method can be used to simplify the implementation of a ``_rich_repr_`` method of a graphics object if there is already a function to save graphics to a file. INPUT: - + - ``save_function`` -- callable that can save graphics to a file and accepts options like :meth:`sage.plot.graphics.Graphics.save`. @@ -650,16 +650,16 @@ def graphics_from_save(self, save_function, save_kwds, :class:`sage.repl.rich_output.output_basic.OutputBase`. The output container to use. Must be one of the types in :meth:`supported_output`. - + - ``figsize`` -- pair of integers (optional). The desired graphics size in pixels. Suggested, but need not be respected by the output. - + - ``dpi`` -- integer (optional). The desired resolution in dots per inch. Suggested, but need not be respected by the output. - + OUTPUT: - + Return an instance of ``output_container``. EXAMPLES:: @@ -692,11 +692,11 @@ def graphics_from_save(self, save_function, save_kwds, from sage.repl.rich_output.buffer import OutputBuffer buf = OutputBuffer.from_file(filename) return output_container(buf) - + def supported_output(self): """ Return the output container classes that can be used. - + OUTPUT: Frozen set of subclasses of @@ -718,7 +718,7 @@ def supported_output(self): def displayhook(self, obj): """ Implementation of the displayhook - + Every backend must pass the value of the last statement of a line / cell to this method. See also :meth:`display_immediately` if you want do display rich output @@ -753,7 +753,7 @@ def display_immediately(self, obj, **rich_repr_kwds): object when we are not returning to the command line prompt, for example during program execution. Typically, it is being called by :meth:`sage.plot.graphics.Graphics.show`. - + INPUT: - ``obj`` -- anything. The object to be shown. @@ -772,6 +772,6 @@ def display_immediately(self, obj, **rich_repr_kwds): self._backend.display_immediately(plain_text, rich_output) - + get_display_manager = DisplayManager.get_instance diff --git a/src/sage/rings/complex_double.pxd b/src/sage/rings/complex_double.pxd index 6edc1fd23f9..3ef3a386794 100644 --- a/src/sage/rings/complex_double.pxd +++ b/src/sage/rings/complex_double.pxd @@ -2,13 +2,9 @@ from sage.libs.gsl.types cimport gsl_complex cimport sage.structure.element cimport sage.rings.ring - -import sage.structure.element cimport sage.structure.element from sage.structure.element cimport RingElement, ModuleElement - -cdef extern from "pari/pari.h": - ctypedef long* GEN +from sage.libs.pari.types cimport GEN cdef class ComplexDoubleField_class(sage.rings.ring.Field): diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 60f5eca94dd..2dbd9b83612 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -66,8 +66,8 @@ import operator from sage.misc.randstate cimport randstate, current_randstate +from sage.libs.pari.paridecl cimport * include 'sage/ext/interrupt.pxi' -include 'sage/libs/pari/decl.pxi' include 'sage/libs/pari/pari_err.pxi' from sage.libs.gsl.complex cimport * diff --git a/src/sage/rings/continued_fraction.py b/src/sage/rings/continued_fraction.py index e0317b6ba76..0acc38d99ca 100644 --- a/src/sage/rings/continued_fraction.py +++ b/src/sage/rings/continued_fraction.py @@ -2189,6 +2189,14 @@ def continued_fraction_list(x, type="std", partial_convergents=False, bits=None, [1, 100000000000000000000, 2688, 8, 1] sage: continued_fraction_list(1 + 10^-20 - e^-100, nterms=5) [1, 100000000000000000000, 2688, 8, 1] + + Fixed :trac:`18901`:: + + sage: a = 1.575709393346379 + sage: type(a) + + sage: continued_fraction_list(a) + [1, 1, 1, 2, 1, 4, 18, 1, 5, 2, 25037802, 7, 1, 3, 1, 28, 1, 8, 2] """ from rational_field import QQ @@ -2225,7 +2233,10 @@ def continued_fraction_list(x, type="std", partial_convergents=False, bits=None, cf = None - from sage.rings.real_mpfi import is_RealIntervalField + from sage.rings.real_mpfi import RealIntervalField, is_RealIntervalField + from sage.rings.real_mpfr import RealLiteral + if isinstance(x, RealLiteral): + x = RealIntervalField(x.prec())(x) if is_RealIntervalField(x.parent()): cf = continued_fraction(rat_interval_cf_list( x.lower().exact_rational(), @@ -2364,6 +2375,14 @@ def continued_fraction(x, value=None): ....: cff = continued_fraction(x) ....: cfe = QQ(x).continued_fraction() ....: assert cff == cfe, "%s %s %s"%(x,cff,cfe) + + TESTS: + + Fixed :trac:`18901`. For RealLiteral, continued_fraction calls + continued_fraction_list:: + + sage: continued_fraction(1.575709393346379) + [1; 1, 1, 2, 1, 4, 18, 1, 5, 2, 25037802, 7, 1, 3, 1, 28, 1, 8, 2] """ if isinstance(x, ContinuedFraction_base): @@ -2396,9 +2415,16 @@ def continued_fraction(x, value=None): # input for numbers #TODO: the approach used below might be not what the user expects as we - # have currently in sage (version 6.2) - # sage: RR.random_element() in QQ - # True + # have currently in sage (version 6.8) + # + # sage: RR.random_element() in QQ + # True + # + # But, be careful with real literals + # + # sage: a = 1.575709393346379 + # sage: a in QQ + # False from rational_field import QQ if x in QQ: return QQ(x).continued_fraction() @@ -2409,10 +2435,10 @@ def continued_fraction(x, value=None): except AttributeError: pass + from real_mpfi import RealIntervalField, RealIntervalFieldElement if is_real is False: # we can not rely on the answer of .is_real() for elements of the # symbolic ring. The thing below is a dirty temporary hack. - from real_mpfi import RealIntervalField RIF = RealIntervalField(53) try: RIF(x) @@ -2432,7 +2458,7 @@ def continued_fraction(x, value=None): if x.parent() == SR: return ContinuedFraction_real(x) - raise ValueError("does not know how to compute the continued fraction of %s, try the function continued_fraction_list instead"%x) + return continued_fraction(continued_fraction_list(x)) def Hirzebruch_Jung_continued_fraction_list(x, bits=None, nterms=None): r""" diff --git a/src/sage/rings/fast_arith.pyx b/src/sage/rings/fast_arith.pyx index cf171b09d94..0fa7ddb0f08 100644 --- a/src/sage/rings/fast_arith.pyx +++ b/src/sage/rings/fast_arith.pyx @@ -43,11 +43,8 @@ Basic arithmetic with c-integers. from sage.ext.stdsage cimport PY_NEW include "sage/ext/cdefs.pxi" -include "sage/libs/pari/decl.pxi" - -cdef extern from "pari/pari.h": - cdef long NEXT_PRIME_VIADIFF(long, byteptr) +from sage.libs.pari.paridecl cimport * from sage.libs.pari.gen cimport gen as pari_gen from sage.libs.pari.all import pari from sage.rings.integer cimport Integer diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index ac60e8729e4..c94ee5a675f 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -52,7 +52,7 @@ AUTHORS: include "sage/ext/interrupt.pxi" include "sage/libs/ntl/decl.pxi" -include "sage/libs/pari/decl.pxi" +from sage.libs.pari.paridecl cimport * include "sage/libs/pari/pari_err.pxi" from sage.misc.randstate cimport randstate, current_randstate diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 279008f7a80..ba7b6991436 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -22,7 +22,7 @@ AUTHORS: include "sage/ext/stdsage.pxi" include "sage/ext/interrupt.pxi" include "sage/libs/ntl/decl.pxi" -include "sage/libs/pari/decl.pxi" +from sage.libs.pari.paridecl cimport * include "sage/libs/pari/pari_err.pxi" from sage.structure.sage_object cimport SageObject diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pxd b/src/sage/rings/finite_rings/element_pari_ffelt.pxd index b23a21a0a5b..7387725d29e 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pxd +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pxd @@ -1,5 +1,4 @@ -include "sage/libs/pari/decl.pxi" - +from sage.libs.pari.types cimport GEN from sage.rings.finite_rings.element_base cimport FinitePolyExtElement cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pyx b/src/sage/rings/finite_rings/element_pari_ffelt.pyx index 11693190a5e..2ab45e245c5 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pyx +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pyx @@ -20,6 +20,8 @@ AUTHORS: include "sage/ext/stdsage.pxi" include "sage/ext/interrupt.pxi" +from sage.libs.pari.paridecl cimport * +from sage.libs.pari.paripriv cimport * include "sage/libs/pari/pari_err.pxi" from element_base cimport FinitePolyExtElement diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index c1a8846a509..62a257d7566 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1211,7 +1211,7 @@ def _element_constructor_(self, x): return FunctionFieldElement_rational(self, self._field(x._x)) try: x = self._field(x) - except TypeError, Err: + except TypeError as Err: try: if x.parent() is self.polynomial_ring(): return x[0] diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index ee912703176..348cfa923a9 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -151,7 +151,7 @@ from libc.stdint cimport uint64_t cimport sage.structure.element from sage.structure.element cimport Element, EuclideanDomainElement, parent_c include "sage/ext/python_debug.pxi" -include "sage/libs/pari/decl.pxi" +from sage.libs.pari.paridecl cimport * from sage.rings.rational cimport Rational from sage.libs.gmp.rational_reconstruction cimport mpq_rational_reconstruction from sage.libs.gmp.pylong cimport * diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 82bb26a1175..0ab6f0ae2e5 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -7376,8 +7376,7 @@ def embeddings(self, K): return Sequence([], immutable=True, check=False, universe=self.Hom(K)) f = self.defining_polynomial() - r = f.roots(K, multiplicities=False) - r.sort() + r = sorted(f.roots(K, multiplicities=False)) v = [self.hom([e], check=False) for e in r] # If there is an embedding that preserves variable names # then it is most natural, so we put it first. diff --git a/src/sage/rings/number_field/totallyreal.pyx b/src/sage/rings/number_field/totallyreal.pyx index 458ab9ea738..4288737ac9e 100644 --- a/src/sage/rings/number_field/totallyreal.pyx +++ b/src/sage/rings/number_field/totallyreal.pyx @@ -99,24 +99,19 @@ Authors #***************************************************************************** # Copyright (C) 2007 William Stein and John Voight # -# 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/stdsage.pxi' -include 'sage/libs/pari/decl.pxi' import math, sys from sage.libs.gmp.mpz cimport * +from sage.libs.pari.types cimport * from sage.libs.pari.pari_instance cimport PariInstance from sage.libs.pari.gen cimport gen as pari_gen diff --git a/src/sage/rings/padics/common_conversion.pyx b/src/sage/rings/padics/common_conversion.pyx index 8a9b1b1060d..5d4019879de 100644 --- a/src/sage/rings/padics/common_conversion.pyx +++ b/src/sage/rings/padics/common_conversion.pyx @@ -33,6 +33,7 @@ from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.rings.padics.padic_generic_element cimport pAdicGenericElement import sage.rings.finite_rings.integer_mod +from sage.libs.pari.types cimport * from sage.libs.pari.gen cimport gen as pari_gen from sage.rings.infinity import infinity @@ -40,7 +41,6 @@ cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 # The following Integer is used so that the functions here don't need to initialize an mpz_t. cdef Integer tmp = PY_NEW(Integer) -include "sage/libs/pari/decl.pxi" cdef long get_ordp(x, PowComputer_class prime_pow) except? -10000: """ diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index a5b3593e483..0e0f841c63f 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -1991,7 +1991,7 @@ def Zq(q, prec = DEFAULT_PREC, type = 'capped-abs', modulus = None, names=None, q = Integer(q) F = q.factor() if len(F) != 1: - raise ValueError, "q must be a prime power" + raise ValueError("q must be a prime power") else: F = q q = F[0][0]**F[0][1] diff --git a/src/sage/rings/padics/padic_template_element.pxi b/src/sage/rings/padics/padic_template_element.pxi index cc7eb3abdaa..501e341fa37 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -24,10 +24,10 @@ AUTHORS: #***************************************************************************** from cpython.int cimport * -include "sage/libs/pari/decl.pxi" from sage.libs.gmp.all cimport * import sage.rings.finite_rings.integer_mod +from sage.libs.pari.types cimport * from sage.libs.pari.gen cimport gen as pari_gen from sage.libs.pari.pari_instance cimport INT_to_mpz from sage.rings.padics.common_conversion cimport get_ordp, get_preccap diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index f17d222bdef..59e873fe00d 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -1028,8 +1028,7 @@ def symmetric_cancellation_order(self,other): # "not Fbig" is now impossible, because we only consider *global* monomial orderings. # These are the occurring shifts: Lsmall = sorted(Fsmall.keys()) - Lbig = Fbig.keys() - Lbig.sort() + Lbig = sorted(Fbig.keys()) P = range(Lbig[-1]+1) gens = xrange(PARENT.ngens()) if Lsmall[0]==0: diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index e7e6faf52ac..32dcc8fdf48 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -540,7 +540,7 @@ def completion(self, p, prec=20, extras=None): from sage.rings.laurent_series_ring import LaurentSeriesRing return LaurentSeriesRing(self.base_ring(), name=self._names[0]) else: - raise TypeError, "Cannot complete %s with respect to %s" % (self, p) + raise TypeError("Cannot complete %s with respect to %s" % (self, p)) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 0b2862ab34f..6773851b470 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -1662,8 +1662,7 @@ def factor(self, proof=True): unit = unit * v[i][0] del v[i] break - F = Factorization(v, unit=unit) - F.sort() + F = sorted(Factorization(v, unit=unit)) return F def lift(self,I): diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 0f3d2e6c791..a6691ef9a36 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -144,7 +144,7 @@ def coefficients(self,sparse=True): if sparse: return [c[1] for c in sorted(self.__coeffs.iteritems())] else: - return [self.__coeffs[i] if self.__coeffs.has_key(i) else 0 for i in xrange(self.degree() + 1)] + return [self.__coeffs[i] if i in self.__coeffs else 0 for i in xrange(self.degree() + 1)] def exponents(self): """ diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index e0d22c72d7b..13dbdd6b950 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -46,8 +46,6 @@ TESTS:: include "sage/ext/interrupt.pxi" # ctrl-c interrupt block support include "sage/ext/stdsage.pxi" include "sage/ext/python.pxi" -include "sage/libs/pari/decl.pxi" - import sys import operator @@ -59,10 +57,10 @@ import sage.misc.misc as misc import sage.rings.rational_field cimport integer -import integer from integer cimport Integer import sage.libs.pari.pari_instance +from sage.libs.pari.paridecl cimport * from sage.libs.pari.gen cimport gen as pari_gen from sage.libs.pari.pari_instance cimport PariInstance, INT_to_mpz, INTFRAC_to_mpq diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index 639c0813849..09be7f11717 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -2183,6 +2183,25 @@ cdef class RealDoubleElement(FieldElement): """ return self._new_c(gsl_sf_sin(self._value)) + def dilog(self): + r""" + Return the dilogarithm of ``self``. + + This is defined by the + series `\sum_n x^n/n^2` for `|x| \le 1`. When the absolute + value of ``self`` is greater than 1, the returned value is the + real part of (the analytic continuation to `\CC` of) the + dilogarithm of ``self``. + + EXAMPLES:: + + sage: RDF(1).dilog() # rel tol 1.0e-13 + 1.6449340668482264 + sage: RDF(2).dilog() # rel tol 1.0e-13 + 2.46740110027234 + """ + return self._new_c(gsl_sf_dilog(self._value)) + def restrict_angle(self): r""" Return a number congruent to ``self`` mod `2\pi` that lies in diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 49fd0b9d25b..867d412d6d5 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -124,7 +124,6 @@ import re include 'sage/ext/interrupt.pxi' include "sage/ext/stdsage.pxi" include "sage/ext/random.pxi" -include 'sage/libs/pari/decl.pxi' include 'sage/libs/pari/pari_err.pxi' from sage.libs.gmp.mpz cimport * @@ -140,6 +139,7 @@ import sage.misc.weak_dict import operator import sage.libs.pari.pari_instance +from sage.libs.pari.paridecl cimport * from sage.libs.pari.gen cimport gen from sage.libs.pari.pari_instance cimport PariInstance diff --git a/src/sage/sandpiles/all.py b/src/sage/sandpiles/all.py index a80239fd22d..a7c236c4cb2 100644 --- a/src/sage/sandpiles/all.py +++ b/src/sage/sandpiles/all.py @@ -1,2 +1,12 @@ -from sandpile import Sandpile, SandpileDivisor, SandpileConfig, sandlib, grid_sandpile, complete_sandpile, firing_vector, admissible_partitions, firing_graph, parallel_firing_graph, wilmes_algorithm, min_cycles, partition_sandpile, glue_graphs, random_tree, random_DAG, random_digraph, triangle_sandpile, aztec_sandpile +from sage.misc.lazy_import import lazy_import +from sandpile import Sandpile, SandpileDivisor, SandpileConfig, firing_graph, parallel_firing_graph, wilmes_algorithm, random_tree, random_digraph, random_DAG, triangle_sandpile + +lazy_import('sage.sandpiles.examples', 'sandpiles') + +lazy_import('sage.sandpiles.sandpile', 'sandlib', deprecation=(18618,'sandlib() will soon be removed. Use sandpile() instead.')) +lazy_import('sage.sandpiles.sandpile', 'grid_sandpile', deprecation=(18618,'grid_sandpile() will soon be removed. Use sandpile.Grid() instead.')) +lazy_import('sage.sandpiles.sandpile', 'complete_sandpile', deprecation=(18618,'complete_sandpile() will soon be removed. Use sandpile.Complete() instead.')) +lazy_import('sage.sandpiles.sandpile', 'firing_vector', deprecation=(18618,'firing_vector() will soon be removed. Use SandpileDivisor.is_linearly_equivalent() instead.')) + +lazy_import('sage.sandpiles.sandpile', ['admissible_partitions','partition_sandpile','min_cycles','glue_graphs','aztec_sandpile','triangle_sandpile'], deprecation=18618) diff --git a/src/sage/sandpiles/examples.py b/src/sage/sandpiles/examples.py new file mode 100644 index 00000000000..5240d9515fc --- /dev/null +++ b/src/sage/sandpiles/examples.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +""" +Examples of Sandpile + +AUTHORS: + +- David Perkinson (2015-05) [Using `examples.py` from homology as + template.] + +This file constructs some examples of Sandpiles. + +The examples are accessible by typing ``sandpiles.NAME``, where +``NAME`` is the name of the example. You can get a list by typing +``sandpiles.`` and hitting the TAB key:: + + sandpiles.Complete + sandpiles.Cycle + sandpiles.Diamond + sandpiles.Grid + sandpiles.House + +See the documentation for each particular type of example for full details. +""" + +from sage.sandpiles.sandpile import Sandpile +from sage.graphs.graph_generators import graphs + +class SandpileExamples(object): + """ + Some examples of sandpiles. + + Here are the available examples; you can also type + ``sandpiles.`` and hit tab to get a list: + + - :meth:`Complete` + - :meth:`Cycle` + - :meth:`Diamond` + - :meth:`Grid` + - :meth:`House` + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: s.invariant_factors() + [1, 4, 4] + sage: s.laplacian() + [ 3 -1 -1 -1] + [-1 3 -1 -1] + [-1 -1 3 -1] + [-1 -1 -1 3] + """ + def __call__(self): + r""" + If sandpiles() is executed, return a helpful message. + + INPUT: + + None + + OUTPUT: + + None + + EXAMPLES:: + + sage: sandpiles() + Try sandpile.FOO() where FOO is in the list: + + Complete, Cycle, Diamond, Fan, Grid, House, Wheel + """ + print 'Try sandpile.FOO() where FOO is in the list:\n' + print " " + ", ".join([str(i) for i in dir(sandpiles) if i[0]!='_']) + + def Complete(self, n): + """ + The complete sandpile graph with `n` vertices. + + INPUT: + + - ``n`` -- positive integer + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: s.group_order() + 16 + sage: sandpiles.Complete(3) == sandpiles.Cycle(3) + True + """ + return Sandpile(graphs.CompleteGraph(n),0) + + def Cycle(self, n): + """ + Sandpile on the cycle graph with `n` vertices. + + INPUT: + + - ``n`` -- a non-negative integer + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: s = sandpiles.Cycle(4) + sage: s.edges() + [(0, 1, 1), + (0, 3, 1), + (1, 0, 1), + (1, 2, 1), + (2, 1, 1), + (2, 3, 1), + (3, 0, 1), + (3, 2, 1)] + """ + return Sandpile(graphs.CycleGraph(n),0) + + def Diamond(self): + """ + Sandpile on the diamond graph. + + INPUT: + + None + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: s.invariant_factors() + [1, 1, 8] + """ + return Sandpile(graphs.DiamondGraph(),0) + + + def Fan(self, n, deg_three_verts=False): + """ + Sandpile on the Fan graph with a total of `n` vertices. + + INPUT: + + - ``n`` -- a non-negative integer + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: f = sandpiles.Fan(10) + sage: f.group_order() == fibonacci(18) + True + sage: f = sandpiles.Fan(10,True) # all nonsink vertices have deg 3 + sage: f.group_order() == fibonacci(20) + True + """ + f = graphs.WheelGraph(n) + if n>2: + f.delete_edge(1,n-1) + if deg_three_verts: + f.allow_multiple_edges(True) + f.add_edges([(0,1),(0,n-1)]) + return Sandpile(f,0) + elif n==1: + return Sandpile(f,0) + elif n==2: + if deg_three_verts: + return Sandpile({0:{1:3}, 1:{0:3}}) + else: + return Sandpile(f,0) + + def Grid(self, m, n): + """ + Sandpile on the diamond graph. + + INPUT: + + - ``m``, ``n`` -- negative integers + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: s = sandpiles.Grid(2,3) + sage: s.vertices() + [(0, 0), (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)] + sage: s.invariant_factors() + [1, 1, 1, 1, 1, 2415] + sage: s = sandpiles.Grid(1,1) + sage: s.dict() + {(0, 0): {(1, 1): 4}, (1, 1): {(0, 0): 4}} + """ + G = graphs.Grid2dGraph(m+2,n+2) + G.allow_multiple_edges(True) # to ensure each vertex ends up with degree 4 + V = [(i,j) for i in [0,m+1] for j in range(n+2)] + [(i,j) for j in [0,n+1] for i in range(m+2)] + G.merge_vertices(V) + return Sandpile(G, (0,0)) + + def House(self): + """ + Sandpile on the House graph. + + INPUT: + + None + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: s = sandpiles.House() + sage: s.invariant_factors() + [1, 1, 1, 11] + """ + return Sandpile(graphs.HouseGraph(),0) + + def Wheel(self, n): + """ + Sandpile on the wheel graph with a total of `n` vertices. + + INPUT: + + - ``n`` -- a non-negative integer + + OUTPUT: + + - Sandpile + + EXAMPLES:: + + sage: w = sandpiles.Wheel(6) + sage: w.invariant_factors() + [1, 1, 1, 11, 11] + """ + return Sandpile(graphs.WheelGraph(n),0) + +sandpiles = SandpileExamples() diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index df112801e26..05918a62801 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -1,12 +1,47 @@ -r""" +""" Sandpiles Functions and classes for mathematical sandpiles. -Version: 2.3 +Version: 2.4 AUTHOR: +- David Perkinson (June 4, 2015) Upgraded from version 2.3 to 2.4. + +MAJOR CHANGES + +1. Eliminated dependence on 4ti2, substituting the use of Polyhedron methods. Thus, no optional packages are necessary. +2. Fixed bug in ``Sandpile.__init__`` so that now multigraphs are handled correctly. +3. Created ``sandpiles`` to handle examples of Sandpiles in analogy with ``graphs``, ``simplicial_complexes``, and ``polytopes``. In the process, we implemented a much faster way of producing the sandpile grid graph. +4. Added support for open and closed sandpile Markov chains. +5. Added support for Weierstrass points. +6. Implemented the Cori-Le Borgne algorithm for computing ranks of divisors on complete graphs. + +NEW METHODS + +**Sandpile**: avalanche_polynomial, genus, group_gens, help, jacobian_representatives, markov_chain, picard_representatives, smith_form, stable_configs, stationary_density, tutte_polynomial. + +**SandpileConfig**: burst_size, help. + +**SandpileDivisor**: help, is_linearly_equivalent, is_q_reduced, is_weierstrass_pt, polytope, polytope_integer_pts, q_reduced, rank, simulate_threshold, stabilize, weierstrass_div, weierstrass_gap_seq, weierstrass_pts, weierstrass_rank_seq. + +DEPRECATED + +SandpileDivisor.linear_system, SandpileDivisor.r_of_D, sandlib method, complete_sandpile, grid_sandpile, triangle_sandpile, aztec_sandpile, random_digraph, random_tree, glue_graphs, admissible_partitions, firing_vector, min_cycles. + +MINOR CHANGES + +* The ``sink`` argument to ``Sandpile.__init__`` now defaults to the first vertex. +* A SandpileConfig or SandpileDivisor may now be multiplied by an integer. +* Sped up ``__add__`` method for SandpileConfig and SandpileDivisor. +* Enhanced string representation of a Sandpile (via ``_repr_`` and the ``name`` methods). +* Recurrents for complete graphs and cycle graphs are computed more quickly. +* The stabilization code for SandpileConfig has been made more efficient. +* Added optional probability distribution arguments to ``add_random`` methods. + +--------------------------------------- + - Marshall Hampton (2010-1-10) modified for inclusion as a module within Sage library. @@ -30,6 +65,9 @@ EXAMPLES: +For general help, enter ``Sandpile.help()``, ``SandpileConfig.help()``, and +``SandpileDivisor.help()``. Miscellaneous examples appear below. + A weighted directed graph given as a Python dictionary:: sage: from sage.sandpiles import * @@ -43,6 +81,10 @@ sage: S = Sandpile(g,0) +Or just:: + + sage: S = Sandpile(g) + A picture of the graph:: sage: S.show() @@ -96,6 +138,12 @@ sage: S.identity() {1: 2, 2: 2, 3: 2, 4: 0} +An arbitrary sandpile configuration:: + + sage: c = SandpileConfig(S,[1,0,4,-3]) + sage: c.equivalent_recurrent() + {1: 2, 2: 2, 3: 2, 4: 0} + Some group operations:: sage: m = S.max_stable() @@ -104,7 +152,7 @@ [2, 2, 2, 1] sage: i.values() [2, 2, 2, 0] - sage: m+i # coordinate-wise sum + sage: m + i # coordinate-wise sum {1: 4, 2: 4, 3: 4, 4: 1} sage: m - i {1: 0, 2: 0, 3: 0, 4: 1} @@ -154,17 +202,17 @@ The number of superstable configurations of each degree:: - sage: S.hilbert_function() - [1, 4, 8] + sage: S.h_vector() + [1, 3, 4] sage: S.postulation() 2 -the saturated, homogeneous sandpile ideal +the saturated homogeneous toppling ideal:: sage: S.ideal() Ideal (x1 - x0, x3*x2 - x0^2, x4^2 - x0^2, x2^3 - x4*x3*x0, x4*x2^2 - x3^2*x0, x3^3 - x4*x2*x0, x4*x3^2 - x2^2*x0) of Multivariate Polynomial Ring in x4, x3, x2, x1, x0 over Rational Field -its minimal free resolution +its minimal free resolution:: sage: S.resolution() 'R^1 <-- R^7 <-- R^15 <-- R^13 <-- R^4' @@ -180,26 +228,88 @@ ------------------------------------ total: 1 7 15 13 4 +Some various ways of creating Sandpiles:: + + sage: S = sandpiles.Complete(4) # for more options enter ``sandpile.TAB`` + sage: S = sandpiles.Wheel(6) + +A multidigraph with loops (vertices 0, 1, 2; for example, there is a directed +edge from vertex 2 to vertex 1 of weight 3, which can be thought of as three +directed edges of the form (2,3). There is also a single loop at vertex 2 and +an edge (2,0) of weight 2):: + + sage: S = Sandpile({0:[1,2], 1:[0,0,2], 2:[0,0,1,1,1,2], 3:[2]}) + +Using the graph library (vertex 1 is specified as the sink; omitting +this would make the sink vertex 0 by default):: + + sage: S = Sandpile(graphs.PetersenGraph(),1) + Distribution of avalanche sizes:: - sage: S = grid_sandpile(10,10) + sage: S = sandpiles.Grid(10,10) sage: m = S.max_stable() sage: a = [] sage: for i in range(1000): - ... m = m.add_random() - ... m, f = m.stabilize(True) - ... a.append(sum(f.values())) - ... + ....: m = m.add_random() + ....: m, f = m.stabilize(True) + ....: a.append(sum(f.values())) + ....: sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)]) sage: p.axes_labels(['log(N)','log(D(N))']) sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0)) sage: show(p+t,axes_labels=['log(N)','log(D(N))']) -To calculate linear systems associated with divisors, 4ti2 must be installed. -One way to do this is to run sage -i to install glpk, then 4ti2. See -http://sagemath.org/download-packages.html to get the exact names of these -packages. An alternative is to install 4ti2 separately, then point the -following variable to the correct path. + Working with sandpile divisors:: + + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S, [0,0,0,5]) + sage: E = D.stabilize(); E + {0: 1, 1: 1, 2: 1, 3: 2} + sage: D.is_linearly_equivalent(E) + True + sage: D.q_reduced() + {0: 4, 1: 0, 2: 0, 3: 1} + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S, [0,0,0,5]) + sage: E = D.stabilize(); E + {0: 1, 1: 1, 2: 1, 3: 2} + sage: D.is_linearly_equivalent(E) + True + sage: D.q_reduced() + {0: 4, 1: 0, 2: 0, 3: 1} + sage: D.rank() + 2 + sage: D.effective_div() + [{0: 0, 1: 0, 2: 0, 3: 5}, + {0: 0, 1: 4, 2: 0, 3: 1}, + {0: 0, 1: 0, 2: 4, 3: 1}, + {0: 1, 1: 1, 2: 1, 3: 2}, + {0: 4, 1: 0, 2: 0, 3: 1}] + sage: D.effective_div(False) + [[0, 0, 0, 5], [0, 4, 0, 1], [0, 0, 4, 1], [1, 1, 1, 2], [4, 0, 0, 1]] + sage: D.rank() + 2 + sage: D.rank(True) + (2, {0: 2, 1: 1, 2: 0, 3: 0}) + sage: E = D.rank(True)[1] # E proves the rank is not 3 + sage: E.values() + [2, 1, 0, 0] + sage: E.deg() + 3 + sage: rank(D - E) + -1 + sage: (D - E).effective_div() + [] + sage: D.weierstrass_pts() + (0, 1, 2, 3) + sage: D.weierstrass_rank_seq(0) + (2, 1, 0, 0, 0, -1) + sage: D.weierstrass_pts() + (0, 1, 2, 3) + sage: D.weierstrass_rank_seq(0) + (2, 1, 0, 0, 0, -1) + """ #***************************************************************************** @@ -209,30 +319,171 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -import os -from sage.symbolic.all import I, pi +from string import join +from collections import Counter +from copy import deepcopy +from inspect import getdoc +import os # CHECK: possibly unnecessary after removing 4ti2-dependent methods +from sage.calculus.functional import derivative +from sage.combinat.integer_vector import IntegerVectors +from sage.combinat.parking_functions import ParkingFunctions +from sage.combinat.set_partition import SetPartitions +from sage.combinat.vector_partition import IntegerVectorsIterator +from sage.env import SAGE_LOCAL from sage.functions.log import exp +from sage.functions.other import binomial +from sage.geometry.polyhedron.constructor import Polyhedron from sage.graphs.all import DiGraph, Graph, graphs, digraphs -from copy import deepcopy -from sage.rings.all import PolynomialRing, QQ, ZZ, lcm +from sage.gsl.probability_distribution import GeneralDiscreteDistribution +from sage.homology.simplicial_complex import SimplicialComplex +from sage.interfaces.singular import singular +from sage.matrix.constructor import matrix, identity_matrix from sage.misc.all import prod, det, forall, tmp_filename, random, randint, exists, denominator, srange +from sage.misc.sagedoc import detex +from sage.misc.superseded import deprecation from sage.modules.free_module_element import vector -from sage.matrix.constructor import matrix, identity_matrix -from sage.interfaces.singular import singular -from sage.combinat.combinat import CombinatorialClass -from sage.combinat.set_partition import SetPartitions -from sage.homology.simplicial_complex import SimplicialComplex from sage.plot.colors import rainbow -from sage.env import SAGE_LOCAL +from sage.rings.arith import falling_factorial +from sage.rings.all import Integer, PolynomialRing, QQ, ZZ, lcm +from sage.symbolic.all import I, pi +# TODO: remove the following line once 4ti2 functions are removed path_to_zsolve = os.path.join(SAGE_LOCAL,'bin','zsolve') class Sandpile(DiGraph): """ Class for Dhar's abelian sandpile model. """ + @staticmethod + def version(): + r""" + The version number of Sage Sandpiles. + + OUTPUT: + + string + + + EXAMPLES:: + + sage: Sandpile.version() + Sage Sandpiles Version 2.4 + sage: S = sandpiles.Complete(3) + sage: S.version() + Sage Sandpiles Version 2.4 + """ + print 'Sage Sandpiles Version 2.4' + + @staticmethod + def help(verbose=True): + r""" + List of Sandpile-specific methods (not inherited from Graph). If ``verbose``, include short descriptions. + + INPUT: + + ``verbose`` -- (default: ``True``) boolean + + OUTPUT: + + printed string - def __init__(self, g, sink): + EXAMPLES:: + + sage: Sandpile.help() + For detailed help with any method FOO listed below, + enter "Sandpile.FOO?" or enter "S.FOO?" for any Sandpile S. + + all_k_config -- The constant configuration with all values set to k. + all_k_div -- The divisor with all values set to k. + avalanche_polynomial -- The avalanche polynomial. + betti -- The Betti table for the homogeneous toppling ideal. + betti_complexes -- The support-complexes with non-trivial homology. + burning_config -- The minimal burning configuration. + burning_script -- A script for the minimal burning configuration. + canonical_divisor -- The canonical divisor. + dict -- A dictionary of dictionaries representing a directed graph. + genus -- The genus: (# non-loop edges) - (# vertices) + 1. + groebner -- A Groebner basis for the homogeneous toppling ideal. + group_gens -- A minimal list of generators for the sandpile group. + group_order -- The size of the sandpile group. + h_vector -- The number of superstable configurations in each degree. + help -- List of Sandpile-specific methods (not inherited from Graph). + hilbert_function -- The Hilbert function of the homogeneous toppling ideal. + ideal -- The saturated homogeneous toppling ideal. + identity -- The identity configuration. + in_degree -- The in-degree of a vertex or a list of all in-degrees. + invariant_factors -- The invariant factors of the sandpile group. + is_undirected -- Is the underlying graph undirected? + jacobian_representatives -- Representatives for the elements of the Jacobian group. + laplacian -- The Laplacian matrix of the graph. + markov_chain -- The sandpile Markov chain for configurations or divisors. + max_stable -- The maximal stable configuration. + max_stable_div -- The maximal stable divisor. + max_superstables -- The maximal superstable configurations. + min_recurrents -- The minimal recurrent elements. + nonsink_vertices -- The nonsink vertices. + nonspecial_divisors -- The nonspecial divisors. + out_degree -- The out-degree of a vertex or a list of all out-degrees. + picard_representatives -- Representatives of the divisor classes of degree d in the Picard group. + points -- Generators for the multiplicative group of zeros of the sandpile ideal. + postulation -- The postulation number of the toppling ideal. + recurrents -- The recurrent configurations. + reduced_laplacian -- The reduced Laplacian matrix of the graph. + reorder_vertices -- A copy of the sandpile with vertex names permuted. + resolution -- A minimal free resolution of the homogeneous toppling ideal. + ring -- The ring containing the homogeneous toppling ideal. + show -- Draw the underlying graph. + show3d -- Draw the underlying graph. + sink -- The sink vertex. + smith_form -- The Smith normal form for the Laplacian. + solve -- Approximations of the complex affine zeros of the sandpile ideal. + stable_configs -- Generator for all stable configurations. + stationary_density -- The stationary density of the sandpile. + superstables -- The superstable configurations. + symmetric_recurrents -- The symmetric recurrent configurations. + tutte_polynomial -- The Tutte polynomial. + unsaturated_ideal -- The unsaturated, homogeneous toppling ideal. + version -- The version number of Sage Sandpiles. + zero_config -- The all-zero configuration. + zero_div -- The all-zero divisor. + """ + # We collect the first sentence of each docstring. The sentence is, + # by definition, from the beginning of the string to the first + # occurrence of a period or question mark. If neither of these appear + # in the string, take the sentence to be the empty string. If the + # latter occurs, something should be changed. + methods = [] + for i in sorted(Sandpile.__dict__.keys()): + if i[0]!='_': + s = eval('getdoc(Sandpile.' + i +')') + period = s.find('.') + question = s.find('?') + if period==-1 and question==-1: + s = '' # Neither appears! + else: + if period==-1: + period = len(s) + 1 + if question==-1: + question = len(s) + 1 + if period < question: + s = s.split('.')[0] + s = detex(s).strip() + '.' + else: + s = s.split('?')[0] + s = detex(s).strip() + '?' + methods.append([i,s]) + print 'For detailed help with any method FOO listed below,' + print 'enter "Sandpile.FOO?" or enter "S.FOO?" for any Sandpile S.' + print '' + mlen = max([len(i[0]) for i in methods]) + if verbose: + for i in methods: + print i[0].ljust(mlen), '--', i[1] + else: + for i in methods: + print i[0] + + def __init__(self, g, sink=None): r""" Create a sandpile. @@ -240,12 +491,14 @@ def __init__(self, g, sink): INPUT: - - ``g`` - dict for directed multigraph (see NOTES) edges weighted by - nonnegative integers + - ``g`` -- dict for directed multigraph with edges weighted by + nonnegative integers (see NOTE), a Graph or DiGraph. - - ``sink`` - 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. + - ``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: @@ -253,7 +506,7 @@ def __init__(self, g, sink): EXAMPLES: - Here ``g`` represents a square with directed, multiple edges with three + Below, ``g`` represents a square with directed, multiple edges with three vertices, ``a``, ``b``, ``c``, and ``d``. The vertex ``a`` has outgoing edges to itself (weight 2), to vertex ``b`` (weight 1), and vertex ``c`` (weight 3), for example. @@ -272,19 +525,49 @@ def __init__(self, g, sink): sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]} sage: G = Sandpile(g,3) - NOTES:: + In the following example, multiple edges and loops in the dictionary + become edge weights in the Sandpile. - Loops are allowed. There are two admissible formats, both of which are - dictionaries whose keys are the vertex names. In one format, the - values are dictionaries with keys the names of vertices which are the - tails of outgoing edges and with values the weights of the edges. In - the other format, the values are lists of names of vertices which are - the tails of the outgoing edges. All weights are then automatically - assigned the value 1. + :: + + 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:: + + Loops are allowed. There are four admissible input formats. Two of + these are dictionaries whose keys are the vertex names. In one, the + values are dictionaries with keys the names of vertices which are the + heads of outgoing edges and with values the weights of the edges. In + the other format, the values are lists of names of vertices which are + the heads of the outgoing edges, with weights determined by the number + of times a name of a vertex appears in the list. Both Graphs and + DiGraphs can also be used as inputs. TESTS:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: TestSuite(S).run() Make sure we cannot make an unweighted sandpile:: @@ -294,47 +577,42 @@ def __init__(self, g, sink): ... TypeError: __init__() got an unexpected keyword argument 'weighted' """ + # set graph name + if isinstance(g, Graph) or isinstance(g, DiGraph): + name = g.name() + if name == '': + name = 'sandpile graph' + else: + p = name.lower().find('graph') + if p == -1: + name = name + ' sandpile graph' + else: + name = name[:p] + 'sandpile graph' + name[p+5:] + self._name = name + else: + self._name = 'sandpile graph' # preprocess a graph, if necessary if isinstance(g, dict) and isinstance(g.values()[0], dict): pass # this is the default format elif isinstance(g, dict) and isinstance(g.values()[0], list): - processed_g = {} - for k in g.keys(): - temp = {} - for vertex in g[k]: - temp[vertex] = 1 - processed_g[k] = temp - g = processed_g - elif isinstance(g, Graph): - processed_g = {} - for v in g.vertices(): - edges = {} - for n in g.neighbors(v): - if (isinstance(g.edge_label(v,n), int) - and g.edge_label(v,n) >= 0): - edges[n] = g.edge_label(v,n) - else: - edges[n] = 1 - processed_g[v] = edges - g = processed_g - elif isinstance(g, DiGraph): - processed_g = {} - for v in g.vertices(): - edges = {} - for n in g.neighbors_out(v): - if (isinstance(g.edge_label(v,n), int) - and g.edge_label(v,n) >= 0): - edges[n] = g.edge_label(v,n) - else: - edges[n] = 1 - processed_g[v] = edges + processed_g = {i:dict(Counter(g[i])) for i in g} g = processed_g + elif isinstance(g, Graph) or isinstance(g, DiGraph): + if not g.weighted(): + h = g.to_dictionary(multiple_edges=True) + g = {i:dict(Counter(h[i])) for i in h} + else: + vi = {v:g.vertices().index(v) for v in g.vertices()} + ad = g.weighted_adjacency_matrix() + g = {v:{w:ad[vi[v],vi[w]] for w in g.neighbors(v)} for v in g.vertices()} else: raise SyntaxError(g) # create digraph and initialize some variables DiGraph.__init__(self,g,weighted=True) self._dict = deepcopy(g) + if sink==None: + sink = self.vertices()[0] self._sink = sink # key for sink self._sink_ind = self.vertices().index(sink) self._nonsink_vertices = deepcopy(self.vertices()) @@ -355,7 +633,7 @@ def __copy__(self): EXAMPLES:: - sage: G = complete_sandpile(4) + sage: G = sandpiles.Complete(4) sage: G_copy = copy(G) sage: G_copy == G == G.__copy__() True @@ -368,15 +646,11 @@ def __getattr__(self, name): INPUT: - ``name`` - name of an internal method - - OUTPUT: - - None. + ``name`` -- name of an internal method EXAMPLES:: - sage: S = complete_sandpile(5) + sage: S = sandpiles.Complete(5) sage: S.__getattr__('_max_stable') {1: 3, 2: 3, 3: 3, 4: 3} """ @@ -408,12 +682,27 @@ def __getattr__(self, name): elif name == '_superstables': self._set_superstables() return deepcopy(self.__dict__[name]) + elif name == '_group_gens': + self._set_group_gens() + return deepcopy(self.__dict__[name]) elif name == '_group_order': self.__dict__[name] = det(self._reduced_laplacian.dense_matrix()) return self.__dict__[name] elif name == '_invariant_factors': self._set_invariant_factors() return deepcopy(self.__dict__[name]) + elif name == '_smith_form': + self._set_smith_form() + return deepcopy(self.__dict__[name]) + elif name == '_jacobian_representatives': + self._set_jacobian_representatives() + return deepcopy(self.__dict__[name]) + elif name == '_avalanche_polynomial': + self._set_avalanche_polynomial() + return deepcopy(self.__dict__[name]) + elif name == '_stationary_density': + self._set_stationary_density() + return self.__dict__[name] elif name == '_betti_complexes': self._set_betti_complexes() return deepcopy(self.__dict__[name]) @@ -440,42 +729,53 @@ def __getattr__(self, name): else: raise AttributeError(name) - def version(self): + def __str__(self): r""" - The version number of Sage Sandpiles - - INPUT: - - None + The name of the sandpile. OUTPUT: string + EXAMPLES:: + + sage: s = Sandpile(graphs.PetersenGraph(),2) + sage: str(s) + 'Petersen sandpile graph' + sage: str(sandpiles.House()) + 'House sandpile graph' + sage: str(Sandpile({0:[1,1], 1:[0]})) + 'sandpile graph' + """ + return self.name() + + def _repr_(self): + r""" + String representation of self. EXAMPLES:: - sage: S = sandlib('generic') - sage: S.version() - Sage Sandpiles Version 2.3 + sage: s = Sandpile(graphs.PetersenGraph(),2) + sage: repr(s) + 'Petersen sandpile graph: 10 vertices, sink = 2' + sage: repr(sandpiles.Complete(4)) + 'Complete sandpile graph: 4 vertices, sink = 0' + sage: repr(Sandpile({0:[1,1], 1:[0]})) + 'sandpile graph: 2 vertices, sink = 0' """ - print 'Sage Sandpiles Version 2.3' + return self._name + ': ' + str(self.num_verts()) + ' vertices, sink = ' + str(self.sink()) def show(self,**kwds): r""" - Draws the graph. + Draw the underlying graph. INPUT: - ``kwds`` - arguments passed to the show method for Graph or DiGraph - - OUTPUT: - - None + ``kwds`` -- (optional) arguments passed to the show method for Graph or DiGraph EXAMPLES:: - sage: S = sandlib('generic') + sage: S = Sandpile({0:[], 1:[0,3,4], 2:[0,3,5], 3:[2,5], 4:[1,1], 5:[2,4]}) sage: S.show() sage: S.show(graph_border=True, edge_labels=True) """ @@ -485,21 +785,17 @@ def show(self,**kwds): else: DiGraph(self).show(**kwds) - def show3d(self,**kwds): + def show3d(self, **kwds): r""" - Draws the graph. + Draw the underlying graph. INPUT: - ``kwds`` - arguments passed to the show method for Graph or DiGraph - - OUTPUT: - - None + ``kwds`` -- (optional) arguments passed to the show method for Graph or DiGraph EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: S.show3d() """ @@ -512,62 +808,47 @@ def dict(self): r""" A dictionary of dictionaries representing a directed graph. - INPUT: - - None - OUTPUT: dict - EXAMPLES:: - sage: G = sandlib('generic') - sage: G.dict() - {0: {}, - 1: {0: 1, 3: 1, 4: 1}, - 2: {0: 1, 3: 1, 5: 1}, - 3: {2: 1, 5: 1}, - 4: {1: 1, 3: 1}, - 5: {2: 1, 3: 1}} - sage: G.sink() + sage: S = sandpiles.Diamond() + sage: S.dict() + {0: {1: 1, 2: 1}, + 1: {0: 1, 2: 1, 3: 1}, + 2: {0: 1, 1: 1, 3: 1}, + 3: {1: 1, 2: 1}} + sage: S.sink() 0 """ return deepcopy(self._dict) def sink(self): r""" - The identifier for the sink vertex. - - INPUT: - - None + The sink vertex. OUTPUT: - Object (name for the sink vertex) + sink vertex EXAMPLES:: - sage: G = sandlib('generic') + sage: G = sandpiles.House() sage: G.sink() 0 - sage: H = grid_sandpile(2,2) + sage: H = sandpiles.Grid(2,2) sage: H.sink() - 'sink' + (0, 0) sage: type(H.sink()) - + """ return self._sink def laplacian(self): r""" - The Laplacian matrix of the graph. - - INPUT: - - None + The Laplacian matrix of the graph. Its *rows* encode the vertex firing rules. OUTPUT: @@ -576,19 +857,17 @@ def laplacian(self): EXAMPLES:: - sage: G = sandlib('generic') + sage: G = sandpiles.Diamond() sage: G.laplacian() - [ 0 0 0 0 0 0] - [-1 3 0 -1 -1 0] - [-1 0 3 -1 0 -1] - [ 0 0 -1 2 0 -1] - [ 0 -1 0 -1 2 0] - [ 0 0 -1 -1 0 2] + [ 2 -1 -1 0] + [-1 3 -1 -1] + [-1 -1 3 -1] + [ 0 -1 -1 2] - NOTES: + .. WARNING:: - The function ``laplacian_matrix`` should be avoided. It returns the - indegree version of the laplacian. + The function ``laplacian_matrix`` should be avoided. It returns the + indegree version of the Laplacian. """ return deepcopy(self._laplacian) @@ -596,10 +875,6 @@ def reduced_laplacian(self): r""" The reduced Laplacian matrix of the graph. - INPUT: - - None - OUTPUT: matrix @@ -607,25 +882,21 @@ def reduced_laplacian(self): EXAMPLES:: - sage: G = sandlib('generic') - sage: G.laplacian() - [ 0 0 0 0 0 0] - [-1 3 0 -1 -1 0] - [-1 0 3 -1 0 -1] - [ 0 0 -1 2 0 -1] - [ 0 -1 0 -1 2 0] - [ 0 0 -1 -1 0 2] - sage: G.reduced_laplacian() - [ 3 0 -1 -1 0] - [ 0 3 -1 0 -1] - [ 0 -1 2 0 -1] - [-1 0 -1 2 0] - [ 0 -1 -1 0 2] - - NOTES: - - This is the Laplacian matrix with the row and column indexed by the - sink vertex removed. + sage: S = sandpiles.Diamond() + sage: S.laplacian() + [ 2 -1 -1 0] + [-1 3 -1 -1] + [-1 -1 3 -1] + [ 0 -1 -1 2] + sage: S.reduced_laplacian() + [ 3 -1 -1] + [-1 3 -1] + [-1 -1 2] + + .. NOTE:: + + This is the Laplacian matrix with the row and column indexed by the + sink vertex removed. """ return deepcopy(self._reduced_laplacian) @@ -633,19 +904,15 @@ def group_order(self): r""" The size of the sandpile group. - INPUT: - - None - OUTPUT: - int + integer EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: S.group_order() - 15 + 11 """ return self._group_order @@ -653,32 +920,20 @@ def _set_max_stable(self): r""" Initialize the variable holding the maximal stable configuration. - INPUT: - - None - - OUTPUT: - - NONE - EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: S._set_max_stable() + sage: '_max_stable' in S.__dict__ + True """ - m = {} - for v in self._nonsink_vertices: - m[v] = self.out_degree(v)-1 + m = {v:self.out_degree(v)-1 for v in self._nonsink_vertices} self._max_stable = SandpileConfig(self,m) def max_stable(self): r""" The maximal stable configuration. - INPUT: - - None - OUTPUT: SandpileConfig (the maximal stable configuration) @@ -686,9 +941,9 @@ def max_stable(self): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: S.max_stable() - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} + {1: 1, 2: 2, 3: 2, 4: 1} """ return deepcopy(self._max_stable) @@ -696,44 +951,31 @@ def _set_max_stable_div(self): r""" Initialize the variable holding the maximal stable divisor. - INPUT: - - None - - OUTPUT: - - NONE - EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: S._set_max_stable_div() + sage: '_max_stable_div' in S.__dict__ + True """ - m = {} - for v in self.vertices(): - m[v] = self.out_degree(v)-1 + m = {v:self.out_degree(v)-1 for v in self.vertices()} self._max_stable_div = SandpileDivisor(self,m) def max_stable_div(self): r""" The maximal stable divisor. - INPUT: - - SandpileDivisor - OUTPUT: SandpileDivisor (the maximal stable divisor) - EXAMPLES:: - sage: S = sandlib('generic') - sage: S.max_stable_div() - {0: -1, 1: 2, 2: 2, 3: 1, 4: 1, 5: 1} - sage: S.out_degree() - {0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2} + sage: s = sandpiles.Diamond() + sage: s.max_stable_div() + {0: 1, 1: 2, 2: 2, 3: 1} + sage: s.out_degree() + {0: 2, 1: 3, 2: 3, 3: 2} """ return deepcopy(self._max_stable_div) @@ -741,22 +983,15 @@ def _set_out_degrees(self): r""" Initialize the variable holding the out-degrees. - INPUT: - - None - - OUTPUT: - - NONE - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_out_degrees() + sage: s = sandpiles.House() + sage: s._set_out_degrees() + sage: '_out_degrees' in s.__dict__ + True """ - self._out_degrees = dict(self.zero_div()) + self._out_degrees = {v:0 for v in self.vertices()} for v in self.vertices(): - self._out_degrees[v] = 0 for e in self.edges_incident(v): self._out_degrees[v] += e[2] @@ -766,7 +1001,7 @@ def out_degree(self, v=None): INPUT: - ``v`` (optional) - vertex name + ``v`` - (optional) vertex name OUTPUT: @@ -774,11 +1009,11 @@ def out_degree(self, v=None): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.out_degree(2) + sage: s = sandpiles.House() + sage: s.out_degree() + {0: 2, 1: 2, 2: 3, 3: 3, 4: 2} + sage: s.out_degree(2) 3 - sage: S.out_degree() - {0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2} """ if not v is None: return self._out_degrees[v] @@ -789,20 +1024,14 @@ def _set_in_degrees(self): """ Initialize the variable holding the in-degrees. - INPUT: - - None - - OUTPUT: - - NONE - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_in_degrees() + sage: s = sandpiles.House() + sage: s._set_in_degrees() + sage: '_in_degrees' in s.__dict__ + True """ - self._in_degrees = dict(self.zero_div()) + self._in_degrees = {v:0 for v in self.vertices()} for e in self.edges(): self._in_degrees[e[1]] += e[2] @@ -812,7 +1041,7 @@ def in_degree(self, v=None): INPUT: - ``v`` - vertex name or None + ``v`` -- (optional) vertex name OUTPUT: @@ -820,11 +1049,11 @@ def in_degree(self, v=None): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.in_degree(2) - 2 - sage: S.in_degree() - {0: 2, 1: 1, 2: 2, 3: 4, 4: 1, 5: 2} + sage: s = sandpiles.House() + sage: s.in_degree() + {0: 2, 1: 2, 2: 3, 3: 3, 4: 2} + sage: s.in_degree(2) + 3 """ if not v is None: return self._in_degrees[v] @@ -871,11 +1100,7 @@ def _set_burning_config(self): def burning_config(self): r""" - A minimal burning configuration. - - INPUT: - - None + The minimal burning configuration. OUTPUT: @@ -898,30 +1123,30 @@ def burning_config(self): sage: matrix(script)*S.reduced_laplacian() [2 0 1 1 0] - NOTES: + .. NOTE:: - The burning configuration and script are computed using a modified - version of Speer's script algorithm. This is a generalization to - directed multigraphs of Dhar's burning algorithm. + The burning configuration and script are computed using a modified + version of Speer's script algorithm. This is a generalization to + directed multigraphs of Dhar's burning algorithm. - A *burning configuration* is a nonnegative integer-linear - combination of the rows of the reduced Laplacian matrix having - nonnegative entries and such that every vertex has a path from some - vertex in its support. The corresponding *burning script* gives - the integer-linear combination needed to obtain the burning - configuration. So if `b` is the burning configuration, `\sigma` is its - script, and `\tilde{L}` is the reduced Laplacian, then `\sigma\cdot - \tilde{L} = b`. The *minimal burning configuration* is the one - with the minimal script (its components are no larger than the - components of any other script - for a burning configuration). + A *burning configuration* is a nonnegative integer-linear + combination of the rows of the reduced Laplacian matrix having + nonnegative entries and such that every vertex has a path from some + vertex in its support. The corresponding *burning script* gives + the integer-linear combination needed to obtain the burning + configuration. So if `b` is the burning configuration, `\sigma` is its + script, and `\tilde{L}` is the reduced Laplacian, then `\sigma\cdot + \tilde{L} = b`. The *minimal burning configuration* is the one + with the minimal script (its components are no larger than the + components of any other script + for a burning configuration). - The following are equivalent for a configuration `c` with burning - configuration `b` having script `\sigma`: + The following are equivalent for a configuration `c` with burning + configuration `b` having script `\sigma`: - - `c` is recurrent; - - `c+b` stabilizes to `c`; - - the firing vector for the stabilization of `c+b` is `\sigma`. + - `c` is recurrent; + - `c+b` stabilizes to `c`; + - the firing vector for the stabilization of `c+b` is `\sigma`. """ return deepcopy(self._burning_config) @@ -929,10 +1154,6 @@ def burning_script(self): r""" A script for the minimal burning configuration. - INPUT: - - None - OUTPUT: dict @@ -954,60 +1175,56 @@ def burning_script(self): sage: matrix(script)*S.reduced_laplacian() [2 0 1 1 0] - NOTES: + .. NOTE:: - The burning configuration and script are computed using a modified - version of Speer's script algorithm. This is a generalization to - directed multigraphs of Dhar's burning algorithm. + The burning configuration and script are computed using a modified + version of Speer's script algorithm. This is a generalization to + directed multigraphs of Dhar's burning algorithm. - A *burning configuration* is a nonnegative integer-linear - combination of the rows of the reduced Laplacian matrix having - nonnegative entries and such that every vertex has a path from some - vertex in its support. The corresponding *burning script* gives the - integer-linear combination needed to obtain the burning configuration. - So if `b` is the burning configuration, `s` is its script, and - `L_{\mathrm{red}}` is the reduced Laplacian, then `s\cdot - L_{\mathrm{red}}= b`. The *minimal burning configuration* is the one - with the minimal script (its components are no larger than the - components of any other script - for a burning configuration). + A *burning configuration* is a nonnegative integer-linear + combination of the rows of the reduced Laplacian matrix having + nonnegative entries and such that every vertex has a path from some + vertex in its support. The corresponding *burning script* gives the + integer-linear combination needed to obtain the burning configuration. + So if `b` is the burning configuration, `s` is its script, and + `L_{\mathrm{red}}` is the reduced Laplacian, then `s\cdot + L_{\mathrm{red}}= b`. The *minimal burning configuration* is the one + with the minimal script (its components are no larger than the + components of any other script + for a burning configuration). - The following are equivalent for a configuration `c` with burning - configuration `b` having script `s`: + The following are equivalent for a configuration `c` with burning + configuration `b` having script `s`: - - `c` is recurrent; - - `c+b` stabilizes to `c`; - - the firing vector for the stabilization of `c+b` is `s`. + - `c` is recurrent; + - `c+b` stabilizes to `c`; + - the firing vector for the stabilization of `c+b` is `s`. """ return deepcopy(self._burning_script) def nonsink_vertices(self): r""" - The names of the nonsink vertices. - - INPUT: - - None + The nonsink vertices. OUTPUT: - None + list of vertices EXAMPLES:: - sage: S = sandlib('generic') - sage: S.nonsink_vertices() - [1, 2, 3, 4, 5] + sage: s = sandpiles.Grid(2,3) + sage: s.nonsink_vertices() + [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)] """ return self._nonsink_vertices - def all_k_config(self,k): + def all_k_config(self, k): r""" - The configuration with all values set to k. + The constant configuration with all values set to `k`. INPUT: - ``k`` - integer + ``k`` -- integer OUTPUT: @@ -1015,9 +1232,9 @@ def all_k_config(self,k): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.all_k_config(7) - {1: 7, 2: 7, 3: 7, 4: 7, 5: 7} + sage: s = sandpiles.Diamond() + sage: s.all_k_config(7) + {1: 7, 2: 7, 3: 7} """ return SandpileConfig(self,[k]*(self.num_verts()-1)) @@ -1025,19 +1242,15 @@ def zero_config(self): r""" The all-zero configuration. - INPUT: - - None - OUTPUT: SandpileConfig EXAMPLES:: - sage: S = sandlib('generic') - sage: S.zero_config() - {1: 0, 2: 0, 3: 0, 4: 0, 5: 0} + sage: s = sandpiles.Diamond() + sage: s.zero_config() + {1: 0, 2: 0, 3: 0} """ return self.all_k_config(0) @@ -1058,98 +1271,116 @@ def _set_identity(self): Computes ``_identity``, the variable holding the identity configuration of the sandpile group, when ``identity()`` is first called by a user. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: S._set_identity() + sage: '_identity' in S.__dict__ + True """ m = self._max_stable self._identity = (m&m).dualize()&m - def identity(self): + def identity(self, verbose=True): r""" - The identity configuration. + The identity configuration. If ``verbose`` is ``False``, the + configuration are converted to a list of integers. INPUT: - None + ``verbose`` -- (default: ``True``) boolean OUTPUT: - dict (the identity configuration) + SandpileConfig or a list of integers If ``verbose`` is ``False``, the + configuration are converted to a list of integers. EXAMPLES:: - sage: S = sandlib('generic') - sage: e = S.identity() - sage: x = e & S.max_stable() # stable addition - sage: x - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} - sage: x == S.max_stable() + sage: s = sandpiles.Diamond() + sage: s.identity() + {1: 2, 2: 2, 3: 0} + sage: s.identity(False) + [2, 2, 0] + sage: s.identity() & s.max_stable() == s.max_stable() True """ - return deepcopy(self._identity) + if verbose: + return deepcopy(self._identity) + else: + return self._identity.values() def _set_recurrents(self): """ Computes ``_recurrents``, the variable holding the list of recurrent configurations, when ``recurrents()`` is first called by a user. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_recurrents() + sage: s = sandpiles.Diamond() + sage: s._set_recurrents() + sage: '_recurrents' in s.__dict__ + True """ - self._recurrents = [] - active = [self._max_stable] - while active != []: - c = active.pop() - self._recurrents.append(c) - for v in self._nonsink_vertices: - cnext = deepcopy(c) - cnext[v] += 1 - cnext = ~cnext - if (cnext not in active) and (cnext not in self._recurrents): - active.append(cnext) + if self.name() == 'Complete sandpile graph': + n = self.num_verts() + self._recurrents = [SandpileConfig(self,[n-1-i for i in p]) for p in ParkingFunctions(n-1)] + elif self.name() == 'Cycle sandpile graph': + n = self.num_verts() + one = [1]*(n-2) + self._recurrents = [SandpileConfig(self,[1]*(n-1))] + [SandpileConfig(self, one[:i]+[0]+one[i:]) for i in range(n-1)] + else: + self._recurrents = [] + active = [self._max_stable] + while active != []: + c = active.pop() + self._recurrents.append(c) + for v in self._nonsink_vertices: + cnext = deepcopy(c) + cnext[v] += 1 + cnext = ~cnext + if (cnext not in active) and (cnext not in self._recurrents): + active.append(cnext) + self._recurrents = self._recurrents def recurrents(self, verbose=True): r""" - The list of recurrent configurations. If ``verbose`` is - ``False``, the configurations are converted to lists of - integers. + The recurrent configurations. If ``verbose`` is ``False``, the + configurations are converted to lists of integers. INPUT: - ``verbose`` (optional) - boolean + ``verbose`` -- (default: ``True``) boolean OUTPUT: - list (of recurrent configurations) + list of recurrent configurations EXAMPLES:: - sage: S = sandlib('generic') - sage: S.recurrents() - [{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 0, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 0, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 0, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 0, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 0, 5: 1}, {1: 0, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 1, 2: 2, 3: 0, 4: 0, 5: 1}] - sage: S.recurrents(verbose=False) - [[2, 2, 1, 1, 1], [2, 2, 0, 1, 1], [0, 2, 1, 1, 0], [0, 2, 1, 1, 1], [1, 2, 1, 1, 1], [1, 2, 0, 1, 1], [2, 2, 1, 0, 1], [2, 2, 0, 0, 1], [2, 2, 1, 0, 0], [1, 2, 1, 1, 0], [1, 2, 1, 0, 0], [1, 2, 1, 0, 1], [0, 2, 0, 1, 1], [2, 2, 1, 1, 0], [1, 2, 0, 0, 1]] + sage: r = Sandpile(graphs.HouseXGraph(),0).recurrents() + sage: r[:3] + [{1: 2, 2: 3, 3: 3, 4: 1}, {1: 1, 2: 3, 3: 3, 4: 0}, {1: 1, 2: 3, 3: 3, 4: 1}] + sage: sandpiles.Complete(4).recurrents(False) + [[2, 2, 2], + [2, 2, 1], + [2, 1, 2], + [1, 2, 2], + [2, 2, 0], + [2, 0, 2], + [0, 2, 2], + [2, 1, 1], + [1, 2, 1], + [1, 1, 2], + [2, 1, 0], + [2, 0, 1], + [1, 2, 0], + [1, 0, 2], + [0, 2, 1], + [0, 1, 2]] + sage: sandpiles.Cycle(4).recurrents(False) + [[1, 1, 1], [0, 1, 1], [1, 0, 1], [1, 1, 0]] """ if verbose: return deepcopy(self._recurrents) @@ -1161,71 +1392,54 @@ def _set_superstables(self): Computes ``_superstables``, the variable holding the list of superstable configurations, when ``superstables()`` is first called by a user. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_superstables() + sage: s = sandpiles.Diamond() + sage: s._set_superstables() + sage: '_superstables' in s.__dict__ + True """ self._superstables = [c.dualize() for c in self._recurrents] def superstables(self, verbose=True): r""" - The list of superstable configurations as dictionaries if - ``verbose`` is ``True``, otherwise as lists of integers. The - superstables are also known as `G`-parking functions. + The superstable configurations. If ``verbose`` is ``False``, the + configurations are converted to lists of integers. Superstables for + undirected graphs are also known as ``G-parking functions``. INPUT: - ``verbose`` (optional) - boolean - - OUTPUT: - - list (of superstable elements) - - - EXAMPLES:: - - sage: S = sandlib('generic') - sage: S.superstables() - [{1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 2, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 2, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 1, 2: 0, 3: 0, 4: 0, 5: 0}, - {1: 1, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 1, 5: 0}, - {1: 0, 2: 0, 3: 1, 4: 1, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 1, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 1, 5: 1}, - {1: 1, 2: 0, 3: 0, 4: 1, 5: 0}, - {1: 2, 2: 0, 3: 1, 4: 0, 5: 0}, - {1: 0, 2: 0, 3: 0, 4: 0, 5: 1}, - {1: 1, 2: 0, 3: 1, 4: 1, 5: 0}] - sage: S.superstables(False) - [[0, 0, 0, 0, 0], - [0, 0, 1, 0, 0], - [2, 0, 0, 0, 1], - [2, 0, 0, 0, 0], - [1, 0, 0, 0, 0], - [1, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 1, 1, 0], - [0, 0, 0, 1, 1], - [1, 0, 0, 0, 1], - [1, 0, 0, 1, 1], - [1, 0, 0, 1, 0], - [2, 0, 1, 0, 0], - [0, 0, 0, 0, 1], - [1, 0, 1, 1, 0]] + ``verbose`` -- (default: ``True``) boolean + + OUTPUT: + + list of SandpileConfig + + + EXAMPLES:: + + sage: sp = Sandpile(graphs.HouseXGraph(),0).superstables() + sage: sp[:3] + [{1: 0, 2: 0, 3: 0, 4: 0}, {1: 1, 2: 0, 3: 0, 4: 1}, {1: 1, 2: 0, 3: 0, 4: 0}] + sage: sandpiles.Complete(4).superstables(False) + [[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [1, 0, 0], + [0, 0, 2], + [0, 2, 0], + [2, 0, 0], + [0, 1, 1], + [1, 0, 1], + [1, 1, 0], + [0, 1, 2], + [0, 2, 1], + [1, 0, 2], + [1, 2, 0], + [2, 0, 1], + [2, 1, 0]] + sage: sandpiles.Cycle(4).superstables(False) + [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]] """ if verbose: return deepcopy(self._superstables) @@ -1233,65 +1447,126 @@ def superstables(self, verbose=True): verts = self.nonsink_vertices() return [s.values() for s in self._superstables] - def is_undirected(self): + def _set_group_gens(self): r""" - ``True`` if ``(u,v)`` is and edge if and only if ``(v,u)`` is an - edges, each edge with the same weight. - - INPUT: - - None - - OUTPUT: - - boolean + A minimal list of generators for the sandpile group. EXAMPLES:: - sage: complete_sandpile(4).is_undirected() + sage: s = sandpiles.Cycle(3) + sage: s._set_group_gens() + sage: '_group_gens' in s.__dict__ True - sage: sandlib('gor').is_undirected() - False """ - return self.laplacian().is_symmetric() + D, U, _ = self.reduced_laplacian().transpose().smith_form() + F = U.inverse() + self._group_gens = [SandpileConfig(self,[Integer(j) for j in F.column(i)]).equivalent_recurrent() + for i in range(F.nrows()) if D[i][i]!=1] - def _set_min_recurrents(self): + def group_gens(self, verbose=True): r""" - Computes the minimal recurrent elements. If the underlying graph is - undirected, these are the recurrent elements of least degree. + A minimal list of generators for the sandpile group. If ``verbose`` is ``False`` + then the generators are represented as lists of integers. INPUT: - None + ``verbose`` -- (default: ``True``) boolean OUTPUT: - None + list of SandpileConfig (or of lists of integers if ``verbose`` is ``False``) EXAMPLES:: - sage: complete_sandpile(4)._set_min_recurrents() + sage: s = sandpiles.Cycle(5) + sage: s.group_gens() + [{1: 1, 2: 1, 3: 1, 4: 0}] + sage: s.group_gens()[0].order() + 5 + sage: s = sandpiles.Complete(5) + sage: s.group_gens(False) + [[2, 2, 3, 2], [2, 3, 2, 2], [3, 2, 2, 2]] + sage: [i.order() for i in s.group_gens()] + [5, 5, 5] + sage: s.invariant_factors() + [1, 5, 5, 5] """ - if self.is_undirected(): - m = min([r.deg() for r in self.recurrents()]) - rec = [r for r in self.recurrents() if r.deg()==m] + if verbose: + return deepcopy(self._group_gens) else: - rec = self.recurrents() - for r in self.recurrents(): - if exists(rec, lambda x: r>x)[0]: - rec.remove(r) - self._min_recurrents = rec + return [c.values() for c in self._group_gens] + + def genus(self): + r""" + The genus: (# non-loop edges) - (# vertices) + 1. Only defined for undirected graphs. + + OUTPUT: + + integer + + EXAMPLES:: + + sage: sandpiles.Complete(4).genus() + 3 + sage: sandpiles.Cycle(5).genus() + 1 + """ + if self.is_undirected(): + return self.laplacian().trace()/2 - self.num_verts() + 1 + else: + raise UserWarning("The underlying graph must be undirected.") + + def is_undirected(self): + r""" + Is the underlying graph undirected? ``True`` if `(u,v)` is and edge if + and only if `(v,u)` is an edge, each edge with the same weight. + + OUTPUT: + + boolean + + EXAMPLES:: + + sage: sandpiles.Complete(4).is_undirected() + True + sage: s = Sandpile({0:[1,2], 1:[0,2], 2:[0]}, 0) + sage: s.is_undirected() + False + """ + return self.laplacian().is_symmetric() + + def _set_min_recurrents(self): + r""" + Computes the minimal recurrent elements. If the underlying graph is + undirected, these are the recurrent elements of least degree. + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: s._set_min_recurrents() + sage: '_min_recurrents' in s.__dict__ + True + """ + if self.is_undirected(): + m = min([r.deg() for r in self.recurrents()]) + rec = [r for r in self.recurrents() if r.deg()==m] + else: + rec = list(self.recurrents()) + for r in self.recurrents(): + if exists(rec, lambda x: r>x)[0]: + rec.remove(r) + self._min_recurrents = rec def min_recurrents(self, verbose=True): r""" The minimal recurrent elements. If the underlying graph is - undirected, these are the recurrent elements of least degree. If - ``verbose is ``False``, the configurations are converted to lists of - integers. + undirected, these are the recurrent elements of least degree. + If ``verbose`` is ``False``, the configurations are converted + to lists of integers. INPUT: - ``verbose`` (optional) - boolean + ``verbose`` -- (default: ``True``) boolean OUTPUT: @@ -1299,23 +1574,20 @@ def min_recurrents(self, verbose=True): EXAMPLES:: - sage: S=sandlib('riemann-roch2') - sage: S.min_recurrents() - [{1: 0, 2: 0, 3: 1}, {1: 1, 2: 1, 3: 0}] - sage: S.min_recurrents(False) - [[0, 0, 1], [1, 1, 0]] - sage: S.recurrents(False) - [[1, 1, 2], - [0, 1, 1], - [0, 1, 2], - [1, 0, 1], - [1, 0, 2], - [0, 0, 2], - [1, 1, 1], - [0, 0, 1], - [1, 1, 0]] - sage: [i.deg() for i in S.recurrents()] - [4, 2, 3, 2, 3, 2, 3, 1, 2] + sage: s = sandpiles.Diamond() + sage: s.recurrents(False) + [[2, 2, 1], + [2, 2, 0], + [1, 2, 0], + [2, 0, 1], + [0, 2, 1], + [2, 1, 0], + [1, 2, 1], + [2, 1, 1]] + sage: s.min_recurrents(False) + [[1, 2, 0], [2, 0, 1], [0, 2, 1], [2, 1, 0]] + sage: [i.deg() for i in s.recurrents()] + [5, 4, 3, 3, 3, 3, 4, 4] """ if verbose: return deepcopy(self._min_recurrents) @@ -1331,29 +1603,28 @@ def max_superstables(self, verbose=True): INPUT: - ``verbose`` (optional) - boolean + ``verbose`` -- (default: ``True``) boolean OUTPUT: - list (of maximal superstables) + tuple of SandpileConfig EXAMPLES:: - sage: S=sandlib('riemann-roch2') - sage: S.max_superstables() - [{1: 1, 2: 1, 3: 1}, {1: 0, 2: 0, 3: 2}] - sage: S.superstables(False) + sage: s = sandpiles.Diamond() + sage: s.superstables(False) [[0, 0, 0], + [0, 0, 1], [1, 0, 1], - [1, 0, 0], + [0, 2, 0], + [2, 0, 0], [0, 1, 1], - [0, 1, 0], - [1, 1, 0], - [0, 0, 1], - [1, 1, 1], - [0, 0, 2]] - sage: S.h_vector() - [1, 3, 4, 1] + [1, 0, 0], + [0, 1, 0]] + sage: s.max_superstables(False) + [[1, 0, 1], [0, 2, 0], [2, 0, 0], [0, 1, 1]] + sage: s.h_vector() + [1, 3, 4] """ result = [r.dualize() for r in self.min_recurrents()] if verbose: @@ -1361,17 +1632,104 @@ def max_superstables(self, verbose=True): else: return [r.values() for r in result] + def tutte_polynomial(self): + r""" + The Tutte polynomial. Only defined for undirected sandpile graphs. + + OUTPUT: + + polynomial + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: s.tutte_polynomial() + x^3 + y^3 + 3*x^2 + 4*x*y + 3*y^2 + 2*x + 2*y + sage: s.tutte_polynomial().subs(x=1) + y^3 + 3*y^2 + 6*y + 6 + sage: s.tutte_polynomial().subs(x=1).coefficients() == s.h_vector() + True + """ + if self.is_undirected(): + return Graph(self).tutte_polynomial() + else: + raise UserWarning("The underlying graph must be undirected.") + + + def _set_avalanche_polynomial(self): + """ + Compute the avalanche polynomial. See ``self.avalanche_polynomial`` for details. + + Examples:: + + sage: s = sandpiles.Complete(4) + sage: s._set_avalanche_polynomial() + sage: '_avalanche_polynomial' in s.__dict__ + True + """ + n = self.num_verts() - 1 + R = PolynomialRing(QQ,"x",n) + A = R(0) + V = [] + for i in range(n): + c = self.zero_config() + c[self.nonsink_vertices()[i]] += 1 + V.append(c) + for r in self.recurrents(): + for i in range(n): + e = tuple((r + V[i]).stabilize(True)[1].values()) + A += R({e:1}) + self._avalanche_polynomial = A + + def avalanche_polynomial(self, multivariable=True): + r""" + The avalanche polynomial. See NOTE for details. + + INPUT: + + ``multivariable`` -- (default: ``True``) boolean + + OUTPUT: + + polynomial + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: s.avalanche_polynomial() + 9*x0*x1*x2 + 2*x0*x1 + 2*x0*x2 + 2*x1*x2 + 3*x0 + 3*x1 + 3*x2 + 24 + sage: s.avalanche_polynomial(False) + 9*x0^3 + 6*x0^2 + 9*x0 + 24 + + .. NOTE:: + + For each nonsink vertex `v`, let `x_v` be an indeterminate. + If `(r,v)` is a pair consisting of a recurrent `r` and nonsink + vertex `v`, then for each nonsink vertex `w`, let `n_w` be the + number of times vertex `w` fires in the stabilization of `r + v`. + Let `M(r,v)` be the monomial `\prod_w x_w^{n_w}`, i.e., the exponent + records the vector of `n_w` as `w` ranges over the nonsink vertices. + The avalanche polynomial is then the sum of `M(r,v)` as `r` ranges + over the recurrents and `v` ranges over the nonsink vertices. If + ``multivariable`` is ``False``, then set all the indeterminates equal + to each other (and, thus, only count the number of vertex firings in the + stabilizations, forgetting which particular vertices fired). + """ + if multivariable: + return deepcopy(self._avalanche_polynomial) + else: + R = self._avalanche_polynomial.parent() + X = R.gens() + return self._avalanche_polynomial.subs({X[i]:X[0] for i in range(1,self.num_verts()-1)}) + def nonspecial_divisors(self, verbose=True): r""" - The nonspecial divisors: those divisors of degree ``g-1`` with - empty linear system. The term is only defined for undirected graphs. - Here, ``g = |E| - |V| + 1`` is the genus of the graph. If ``verbose`` - is ``False``, the divisors are converted to lists of integers. + The nonspecial divisors. Only for undirected graphs. (See NOTE.) INPUT: - ``verbose`` (optional) - boolean + ``verbose`` -- (default: ``True``) boolean OUTPUT: @@ -1379,15 +1737,27 @@ def nonspecial_divisors(self, verbose=True): EXAMPLES:: - sage: S = complete_sandpile(4) - sage: ns = S.nonspecial_divisors() # optional - 4ti2 - sage: D = ns[0] # optional - 4ti2 - sage: D.values() # optional - 4ti2 - [-1, 1, 0, 2] - sage: D.deg() # optional - 4ti2 + sage: S = sandpiles.Complete(4) + sage: ns = S.nonspecial_divisors() + sage: D = ns[0] + sage: D.values() + [-1, 0, 1, 2] + sage: D.deg() 2 - sage: [i.effective_div() for i in ns] # optional - 4ti2 + sage: [i.effective_div() for i in ns] [[], [], [], [], [], []] + + .. NOTE:: + + The "nonspecial divisors" are those divisors of degree `g-1` with + empty linear system. The term is only defined for undirected graphs. + Here, `g = |E| - |V| + 1` is the genus of the graph (not counted loops + as part of `|E|`). If ``verbose`` is ``False``, the divisors are converted + to lists of integers. + + .. WARNING:: + + The underlying graph must be undirected. """ if self.is_undirected(): result = [] @@ -1405,12 +1775,8 @@ def nonspecial_divisors(self, verbose=True): def canonical_divisor(self): r""" - The canonical divisor: the divisor ``deg(v)-2`` grains of sand - on each vertex. Only for undirected graphs. - - INPUT: - - None + The canonical divisor. This is the divisor with `\deg(v)-2` grains of + sand on each vertex (not counting loops). Only for undirected graphs. OUTPUT: @@ -1418,12 +1784,19 @@ def canonical_divisor(self): EXAMPLES:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: S.canonical_divisor() {0: 1, 1: 1, 2: 1, 3: 1} + sage: s = Sandpile({0:[1,1],1:[0,0,1,1,1]},0) + sage: s.canonical_divisor() # loops are disregarded + {0: 0, 1: 0} + + .. WARNING:: + + The underlying graph must be undirected. """ if self.is_undirected(): - return SandpileDivisor(self,[self.out_degree(v)-2 for v in self.vertices()]) + return SandpileDivisor(self,[self.laplacian()[i][i] - 2 for i in range(self.num_verts())]) else: raise UserWarning("Only for undirected graphs.") @@ -1432,18 +1805,12 @@ def _set_invariant_factors(self): Computes the variable holding the elementary divisors of the sandpile group when ``invariant_factors()`` is first called by the user. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_invariant_factors() + sage: s = sandpiles.Grid(2,2) + sage: s._set_invariant_factors() + sage: '_invariant_factors' in s.__dict__ + True """ # Sage seems to have issues with computing the Smith normal form and # elementary divisors of a sparse matrix, so we have to convert: @@ -1452,12 +1819,7 @@ def _set_invariant_factors(self): def invariant_factors(self): r""" - The invariant factors of the sandpile group (a finite - abelian group). - - INPUT: - - None + The invariant factors of the sandpile group. OUTPUT: @@ -1465,31 +1827,25 @@ def invariant_factors(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.invariant_factors() - [1, 1, 1, 1, 15] + sage: s = sandpiles.Grid(2,2) + sage: s.invariant_factors() + [1, 1, 8, 24] """ return deepcopy(self._invariant_factors) def _set_hilbert_function(self): """ Computes the variables holding the Hilbert function of the homogeneous - homogeneous sandpile ideal, the first differences of the Hilbert + homogeneous toppling ideal, the first differences of the Hilbert function, and the postulation number for the zero-set of the sandpile ideal when any one of these is called by the user. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: S._set_hilbert_function() + sage: s = sandpiles.Diamond() + sage: s._set_hilbert_function() + sage: '_hilbert_function' in s.__dict__ + True """ v = [i.deg() for i in self._superstables] self._postulation = max(v) @@ -1501,13 +1857,9 @@ def _set_hilbert_function(self): def h_vector(self): r""" - The first differences of the Hilbert function of the homogeneous - sandpile ideal. It lists the number of superstable configurations in - each degree. - - INPUT: - - None + The number of superstable configurations in each degree. Equivalently, + this is the list of first differences of the Hilbert function of the + (homogeneous) toppling ideal. OUTPUT: @@ -1516,21 +1868,17 @@ def h_vector(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.hilbert_function() - [1, 5, 11, 15] - sage: S.h_vector() - [1, 4, 6, 4] + sage: s = sandpiles.Grid(2,2) + sage: s.hilbert_function() + [1, 5, 15, 35, 66, 106, 146, 178, 192] + sage: s.h_vector() + [1, 4, 10, 20, 31, 40, 40, 32, 14] """ return deepcopy(self._h_vector) def hilbert_function(self): r""" - The Hilbert function of the homogeneous sandpile ideal. - - INPUT: - - None + The Hilbert function of the homogeneous toppling ideal. OUTPUT: @@ -1538,53 +1886,90 @@ def hilbert_function(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.hilbert_function() - [1, 5, 11, 15] + sage: s = sandpiles.Wheel(5) + sage: s.hilbert_function() + [1, 5, 15, 31, 45] + sage: s.h_vector() + [1, 4, 10, 16, 14] """ return deepcopy(self._hilbert_function) def postulation(self): r""" - The postulation number of the sandpile ideal. This is the + The postulation number of the toppling ideal. This is the largest weight of a superstable configuration of the graph. - INPUT: - - None - OUTPUT: nonnegative integer EXAMPLES:: - sage: S = sandlib('generic') - sage: S.postulation() + sage: s = sandpiles.Complete(4) + sage: s.postulation() 3 """ return self._postulation - def reorder_vertices(self): + def _set_smith_form(self): r""" - Create a copy of the sandpile but with the vertices ordered according - to their distance from the sink, from greatest to least. + Compute the Smith Normal Form for the transpose of the Laplacian. - INPUT: + EXAMPLES:: - None + sage: s = sandpiles.Complete(3) + sage: s._set_smith_form() + sage: '_smith_form' in s.__dict__ + True + """ + self._smith_form = self.laplacian().transpose().smith_form() + + def smith_form(self): + r""" + The Smith normal form for the Laplacian. In detail: a list of integer + matrices `D, U, V` such that `ULV = D` where `L` is the transpose of the + Laplacian, `D` is diagonal, and `U` and `V` are invertible over the + integers. + + OUTPUT: + + list of integer matrices + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D,U,V = s.smith_form() + sage: D + [1 0 0 0] + [0 4 0 0] + [0 0 4 0] + [0 0 0 0] + sage: U*s.laplacian()*V == D # laplacian symmetric => tranpose not necessary + True + """ + return deepcopy(self._smith_form) + + def reorder_vertices(self): + r""" + A copy of the sandpile with vertex names permuted. After reordering, + vertex `u` comes before vertex `v` in the list of vertices if `u` is + closer to the sink. OUTPUT: Sandpile EXAMPLES:: - sage: S = sandlib('kite') + + sage: S = Sandpile({0:[1], 2:[0,1], 1:[2]}) sage: S.dict() - {0: {}, 1: {0: 1, 2: 1, 3: 1}, 2: {1: 1, 3: 1, 4: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {2: 1, 3: 1}} + {0: {1: 1}, 1: {2: 1}, 2: {0: 1, 1: 1}} sage: T = S.reorder_vertices() - sage: T.dict() - {0: {1: 1, 2: 1}, 1: {0: 1, 2: 1, 3: 1}, 2: {0: 1, 1: 1, 3: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {}} + + The vertices 1 and 2 have been swapped:: + + sage: T.dict() + {0: {1: 1}, 1: {0: 1, 2: 1}, 2: {0: 1}} """ # first order the vertices according to their distance from the sink @@ -1603,16 +1988,360 @@ def reorder_vertices(self): new[perm[i]] = entry return Sandpile(new,len(verts)-1) + def _set_jacobian_representatives(self): + r""" + Find representatives for the elements of the Jacobian group. + + EXAMPLES: + + sage: s = sandpiles.Complete(3) + sage: s._set_jacobian_representatives() + sage: '_jacobian_representatives' in s.__dict__ + True + """ + if self.is_undirected(): + easy = True + else: + ker = self.laplacian().left_kernel().basis() + tau = abs(ker[self._sink_ind]) + if tau==1: + easy = True + else: + easy = False + if easy: + result = [] + for r in self.superstables(): + D = {v:r[v] for v in self._nonsink_vertices} + D[self._sink] = - r.deg() + result.append(SandpileDivisor(self, D)) + self._jacobian_representatives = result + else: + result = [] + sr = self.superstables() + order = self.group_order()/tau + while len(result) + For detailed help with any method FOO listed below, + enter "SandpileConfig.FOO?" or enter "c.FOO?" for any SandpileConfig c. + + add_random -- Add one grain of sand to a random vertex. + burst_size -- The burst size of the configuration with respect to the given vertex. + deg -- The degree of the configuration. + dualize -- The difference with the maximal stable configuration. + equivalent_recurrent -- The recurrent configuration equivalent to the given configuration. + equivalent_superstable -- The equivalent superstable configuration. + fire_script -- Fire the given script. + fire_unstable -- Fire all unstable vertices. + fire_vertex -- Fire the given vertex. + help -- List of SandpileConfig methods. + is_recurrent -- Is the configuration recurrent? + is_stable -- Is the configuration stable? + is_superstable -- Is the configuration superstable? + is_symmetric -- Is the configuration symmetric? + order -- The order of the equivalent recurrent element. + sandpile -- The configuration's underlying sandpile. + show -- Show the configuration. + stabilize -- The stabilized configuration. + support -- The vertices containing sand. + unstable -- The unstable vertices. + values -- The values of the configuration as a list. + """ + # We collect the first sentence of each docstring. The sentence is, + # by definition, from the beginning of the string to the first + # occurrence of a period or question mark. If neither of these appear + # in the string, take the sentence to be the empty string. If the + # latter occurs, something should be changed. + methods = [] + for i in sorted(SandpileConfig.__dict__.keys()): + if i[0]!='_': + s = eval('getdoc(SandpileConfig.' + i +')') + period = s.find('.') + question = s.find('?') + if period==-1 and question==-1: + s = '' # Neither appears! + else: + if period==-1: + period = len(s) + 1 + if question==-1: + question = len(s) + 1 + if period < question: + s = s.split('.')[0] + s = detex(s).strip() + '.' + else: + s = s.split('?')[0] + s = detex(s).strip() + '?' + methods.append([i,s]) + print 'Shortcuts for SandpileConfig operations:' + print '~c -- stabilize' + print 'c & d -- add and stabilize' + print 'c * c -- add and find equivalent recurrent' + print 'c^k -- add k times and find equivalent recurrent' + print ' (taking inverse if k is negative)' + print + print 'For detailed help with any method FOO listed below,' + print 'enter "SandpileConfig.FOO?" or enter "c.FOO?" for any SandpileConfig c.' + print '' + mlen = max([len(i[0]) for i in methods]) + if verbose: + for i in methods: + print i[0].ljust(mlen), '--', i[1] + else: + for i in methods: + print i[0] def __init__(self, S, c): r""" @@ -2228,8 +2980,9 @@ def __init__(self, S, c): INPUT: - - ``S`` - Sandpile - - ``c`` - dict or list representing a configuration + - ``S`` -- Sandpile + + - ``c`` -- dict or list representing a configuration OUTPUT: @@ -2237,12 +2990,12 @@ def __init__(self, S, c): EXAMPLES:: - sage: S = sandlib('generic') - sage: C = SandpileConfig(S,[1,2,3,3,2]) - sage: C.order() - 3 - sage: ~(C+C+C)==S.identity() - True + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S,[1,1,0]) + sage: 3*c + {1: 3, 2: 3, 3: 0} + sage: ~(3*c) # stabilization + {1: 2, 2: 2, 3: 0} """ if len(c)==S.num_verts()-1: if isinstance(c, dict) or isinstance(c, SandpileConfig): @@ -2266,22 +3019,18 @@ def __deepcopy__(self, memo): INPUT: - None - - OUTPUT: - - None + ``memo`` -- (optional) dict EXAMPLES:: - sage: S = sandlib('generic') - sage: c = SandpileConfig(S, [1,2,3,4,5]) + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S,[1,1,0]) sage: d = deepcopy(c) sage: d[1] += 10 sage: c - {1: 1, 2: 2, 3: 3, 4: 4, 5: 5} + {1: 1, 2: 1, 3: 0} sage: d - {1: 11, 2: 2, 3: 3, 4: 4, 5: 5} + {1: 11, 2: 1, 3: 0} """ c = SandpileConfig(self._sandpile, dict(self)) c.__dict__.update(self.__dict__) @@ -2293,27 +3042,23 @@ def __setitem__(self, key, item): INPUT: - - ``key``, ``item`` - objects - - OUTPUT: - - None + ``key``, ``item`` -- objects EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [4,1]) sage: c.equivalent_recurrent() {1: 1, 2: 1} sage: c.__dict__ {'_equivalent_recurrent': [{1: 1, 2: 1}, {1: 2, 2: 1}], - '_sandpile': Digraph on 3 vertices, + '_sandpile': Cycle sandpile graph: 3 vertices, sink = 0, '_vertices': [1, 2]} - NOTES: + .. NOTE:: - In the example, above, changing the value of ``c`` at some vertex makes - a call to setitem, which resets some of the stored variables for ``c``. + In the example, above, changing the value of ``c`` at some vertex makes + a call to setitem, which resets some of the stored variables for ``c``. """ if key in self.keys(): dict.__setitem__(self,key,item) @@ -2329,15 +3074,11 @@ def __getattr__(self, name): INPUT: - ``name`` - name of an internal method - - OUTPUT: - - None. + ``name`` -- name of an internal method EXAMPLES:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: C = SandpileConfig(S,[1,1,1]) sage: C.__getattr__('_deg') 3 @@ -2368,19 +3109,17 @@ def _set_deg(self): r""" Compute and store the degree of the configuration. - INPUT: - - None - OUTPUT: integer EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: c._set_deg() + sage: '_deg' in c.__dict__ + True """ self._deg = sum(self.values()) @@ -2388,30 +3127,26 @@ def deg(self): r""" The degree of the configuration. - INPUT: - - None - OUTPUT: integer EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Complete(3) sage: c = SandpileConfig(S, [1,2]) sage: c.deg() 3 """ return self._deg - def __add__(self,other): + def __add__(self, other): r""" Addition of configurations. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2419,24 +3154,21 @@ def __add__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [3,2]) sage: c + d {1: 4, 2: 4} """ - result = deepcopy(self) - for v in self: - result[v] += other[v] - return result + return SandpileConfig(self.sandpile(),[i+j for i,j in zip(self.values(),other.values())]) - def __sub__(self,other): + def __sub__(self, other): r""" Subtraction of configurations. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2444,7 +3176,7 @@ def __sub__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [3,2]) sage: c - d @@ -2455,13 +3187,13 @@ def __sub__(self,other): sum[v] -= other[v] return sum - def __rsub__(self,other): + def __rsub__(self, other): r""" Right-side subtraction of configurations. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2469,7 +3201,7 @@ def __rsub__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = {1: 3, 2: 2} sage: d - c @@ -2477,7 +3209,7 @@ def __rsub__(self,other): TESTS:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = {1: 3, 2: 2} sage: c.__rsub__(d) @@ -2492,31 +3224,29 @@ def __neg__(self): r""" The additive inverse of the configuration. - INPUT: - - None - OUTPUT: SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: -c {1: -1, 2: -2} """ return SandpileConfig(self._sandpile, [-self[v] for v in self._vertices]) - # recurrent addition + # recurrent addition or multiplication on the right by an integer def __mul__(self, other): r""" - The recurrent element equivalent to the sum. + If ``other`` is an configuration, the recurrent element equivalent + to the sum. If ``other`` is an integer, the sum of configuration with + itself ``other`` times. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig or Integer OUTPUT: @@ -2524,7 +3254,7 @@ def __mul__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,0,0]) sage: c + c # ordinary addition {1: 2, 2: 0, 3: 0} @@ -2536,8 +3266,42 @@ def __mul__(self, other): True sage: c*(-c) == S.identity() True + sage: c + {1: 1, 2: 0, 3: 0} + sage: c*3 + {1: 3, 2: 0, 3: 0} + """ + if isinstance(other,SandpileConfig): + return (self+other).equivalent_recurrent() + elif isinstance(other,Integer): + return SandpileConfig(self.sandpile(),[other*i for i in self.values()]) + else: + raise TypeError(other) + + def __rmul__(self, other): + r""" + The sum of configuration with itself ``other`` times. + + INPUT: + + ``other`` -- Integer + + OUTPUT: + + SandpileConfig + + EXAMPLES:: + + sage: S = sandpiles.Cycle(4) + sage: c = SandpileConfig(S,[1,2,3]) + sage: c + {1: 1, 2: 2, 3: 3} + sage: 3*c + {1: 3, 2: 6, 3: 9} + sage: 3*c == c*3 + True """ - return (self+other).equivalent_recurrent() + return SandpileConfig(self.sandpile(),[other*i for i in self.values()]) def __le__(self, other): r""" @@ -2546,7 +3310,7 @@ def __le__(self, other): INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2554,7 +3318,7 @@ def __le__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) sage: e = SandpileConfig(S, [2,0]) @@ -2571,14 +3335,14 @@ def __le__(self, other): """ return forall(self._vertices, lambda v: self[v]<=other[v])[0] - def __lt__(self,other): + def __lt__(self, other): r""" ``True`` if every component of ``self`` is at most that of ``other`` and the two configurations are not equal. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2586,7 +3350,7 @@ def __lt__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) sage: c < c @@ -2605,7 +3369,7 @@ def __ge__(self, other): INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2613,7 +3377,7 @@ def __ge__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [2,3]) sage: e = SandpileConfig(S, [2,0]) @@ -2630,14 +3394,14 @@ def __ge__(self, other): """ return forall(self._vertices, lambda v: self[v]>=other[v])[0] - def __gt__(self,other): + def __gt__(self, other): r""" ``True`` if every component of ``self`` is at least that of ``other`` and the two configurations are not equal. INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2645,7 +3409,7 @@ def __gt__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: d = SandpileConfig(S, [1,3]) sage: c > c @@ -2661,13 +3425,13 @@ def __gt__(self,other): def __pow__(self, k): r""" The recurrent element equivalent to the sum of the - configuration with itself ``k`` times. If ``k`` is negative, do the - same for the negation of the configuration. If ``k`` is zero, return + configuration with itself `k` times. If `k` is negative, do the + same for the negation of the configuration. If `k` is zero, return the identity of the sandpile group. INPUT: - ``k`` - SandpileConfig + ``k`` -- SandpileConfig OUTPUT: @@ -2675,7 +3439,7 @@ def __pow__(self, k): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,0,0]) sage: c^3 {1: 1, 2: 1, 3: 0} @@ -2708,7 +3472,7 @@ def __and__(self, other): INPUT: - ``other`` - SandpileConfig + ``other`` -- SandpileConfig OUTPUT: @@ -2716,7 +3480,7 @@ def __and__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,0,0]) sage: c + c # ordinary addition {1: 2, 2: 0, 3: 0} @@ -2733,20 +3497,16 @@ def sandpile(self): r""" The configuration's underlying sandpile. - INPUT: - - None - OUTPUT: Sandpile EXAMPLES:: - sage: S = sandlib('genus2') + sage: S = sandpiles.Diamond() sage: c = S.identity() sage: c.sandpile() - Digraph on 4 vertices + Diamond sandpile graph: 4 vertices, sink = 0 sage: c.sandpile() == S True """ @@ -2754,12 +3514,8 @@ def sandpile(self): def values(self): r""" - The values of the configuration as a list, sorted in the order - of the vertices. - - INPUT: - - None + The values of the configuration as a list. The list is sorted in the + order of the vertices. OUTPUT: @@ -2782,12 +3538,7 @@ def values(self): def dualize(self): r""" - The difference between the maximal stable configuration and the - configuration. - - INPUT: - - None + The difference with the maximal stable configuration. OUTPUT: @@ -2795,7 +3546,7 @@ def dualize(self): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: S.max_stable() {1: 1, 2: 1} @@ -2808,11 +3559,11 @@ def dualize(self): def fire_vertex(self, v): r""" - Fire the vertex ``v``. + Fire the given vertex. INPUT: - ``v`` - vertex + ``v`` -- vertex OUTPUT: @@ -2820,7 +3571,7 @@ def fire_vertex(self, v): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: c = SandpileConfig(S, [1,2]) sage: c.fire_vertex(2) {1: 2, 2: 0} @@ -2834,12 +3585,12 @@ def fire_vertex(self, v): def fire_script(self, sigma): r""" - Fire the script ``sigma``, i.e., fire each vertex the indicated number - of times. + Fire the given script. In other words, fire each vertex the number of + times indicated by ``sigma``. INPUT: - ``sigma`` - SandpileConfig or (list or dict representing a SandpileConfig) + ``sigma`` -- SandpileConfig or (list or dict representing a SandpileConfig) OUTPUT: @@ -2847,7 +3598,7 @@ def fire_script(self, sigma): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,2,3]) sage: c.unstable() [2, 3] @@ -2870,11 +3621,7 @@ def fire_script(self, sigma): def unstable(self): r""" - List of the unstable vertices. - - INPUT: - - None + The unstable vertices. OUTPUT: @@ -2882,7 +3629,7 @@ def unstable(self): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,2,3]) sage: c.unstable() [2, 3] @@ -2894,17 +3641,13 @@ def fire_unstable(self): r""" Fire all unstable vertices. - INPUT: - - None - OUTPUT: SandpileConfig EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: c = SandpileConfig(S, [1,2,3]) sage: c.fire_unstable() {1: 2, 2: 1, 3: 2} @@ -2917,32 +3660,30 @@ def fire_unstable(self): c[e[1]]+=e[2] return SandpileConfig(self._sandpile,c) - # FIX: make this faster by not converting to SandpileConfig? def _set_stabilize(self): r""" Computes the stabilized configuration and its firing vector. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: c = S.max_stable() + S.identity() + sage: S = sandpiles.House() + sage: c = 2*S.max_stable() sage: c._set_stabilize() + sage: '_stabilize' in c.__dict__ + True """ + s = self._sandpile c = deepcopy(self) - firing_vector = self._sandpile.zero_config() + firing_vector = s.zero_config() unstable = c.unstable() while unstable: for v in unstable: - firing_vector[v] += 1 - c = c.fire_unstable() + dm = divmod(c[v],s.out_degree(v)) + c[v] = dm[1] + firing_vector[v] += dm[0] + for e in s.outgoing_edges(v): + if e[1] != s.sink(): + c[e[1]] += dm[0]* e[2] unstable = c.unstable() self._stabilize = [c, firing_vector] @@ -2951,9 +3692,9 @@ def stabilize(self, with_firing_vector=False): The stabilized configuration. Optionally returns the corresponding firing vector. - INPUT: s + INPUT: - ``with_firing_vector`` (optional) - boolean + ``with_firing_vector`` -- (default: ``False``) boolean OUTPUT: @@ -2961,16 +3702,19 @@ def stabilize(self, with_firing_vector=False): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() + sage: c = 2*S.max_stable() + sage: c._set_stabilize() + sage: '_stabilize' in c.__dict__ + True + sage: S = sandpiles.House() sage: c = S.max_stable() + S.identity() sage: c.stabilize(True) - [{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 5, 3: 7, 4: 1, 5: 6}] - sage: S.max_stable() & S.identity() - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} + [{1: 1, 2: 2, 3: 2, 4: 1}, {1: 2, 2: 2, 3: 3, 4: 3}] sage: S.max_stable() & S.identity() == c.stabilize() True - sage: ~c - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} + sage: ~c == c.stabilize() + True """ if with_firing_vector: return self._stabilize @@ -2981,21 +3725,16 @@ def __invert__(self): r""" The stabilized configuration. - INPUT: - - None - OUTPUT: ``SandpileConfig`` Returns the stabilized configuration. + EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.House() sage: c = S.max_stable() + S.identity() - sage: ~c - {1: 2, 2: 2, 3: 1, 4: 1, 5: 1} sage: ~c == c.stabilize() True """ @@ -3003,79 +3742,107 @@ def __invert__(self): def support(self): r""" - The input is a dictionary of integers. The output is a list of keys - of nonzero values of the dictionary. - - INPUT: - - None + The vertices containing sand. OUTPUT: - list - support of the config + list - support of the configuration EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: c = S.identity() - sage: c.values() - [2, 2, 1, 1, 0] + sage: c + {1: 2, 2: 2, 3: 0} sage: c.support() - [1, 2, 3, 4] - sage: S.vertices() - [0, 1, 2, 3, 4, 5] + [1, 2] """ return [i for i in self.keys() if self[i] !=0] - def add_random(self): + def add_random(self, distrib=None): r""" - Add one grain of sand to a random nonsink vertex. + Add one grain of sand to a random vertex. Optionally, a probability + distribution, ``distrib``, may be placed on the vertices or the nonsink vertices. + See NOTE for details. INPUT: - None + ``distrib`` -- (optional) list of nonnegative numbers summing to 1 (representing a prob. dist.) OUTPUT: SandpileConfig - EXAMPLES: + EXAMPLES:: - We compute the 'sizes' of the avalanches caused by adding random grains + sage: s = sandpiles.Complete(4) + sage: c = s.zero_config() + sage: c.add_random() # random + {1: 0, 2: 1, 3: 0} + sage: c + {1: 0, 2: 0, 3: 0} + sage: c.add_random([0.1,0.1,0.8]) # random + {1: 0, 2: 0, 3: 1} + sage: c.add_random([0.7,0.1,0.1,0.1]) # random + {1: 0, 2: 0, 3: 0} + + We compute the "sizes" of the avalanches caused by adding random grains of sand to the maximal stable configuration on a grid graph. The function ``stabilize()`` returns the firing vector of the - stabilization, a dictionary whose values say how many times each vertex - fires in the stabilization. - - :: + stabilization, a dictionary whose values say how many times each vertex + fires in the stabilization.:: - sage: S = grid_sandpile(10,10) + sage: S = sandpiles.Grid(10,10) sage: m = S.max_stable() sage: a = [] sage: for i in range(1000): - ... m = m.add_random() - ... m, f = m.stabilize(True) - ... a.append(sum(f.values())) - ... + ....: m = m.add_random() + ....: m, f = m.stabilize(True) + ....: a.append(sum(f.values())) + ....: sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)]) sage: p.axes_labels(['log(N)','log(D(N))']) sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0)) sage: show(p+t,axes_labels=['log(N)','log(D(N))']) + + .. NOTE:: + + If ``distrib`` is ``None``, then the probability is the uniform probability on the nonsink + vertices. Otherwise, there are two possibilities: + + (i) the length of ``distrib`` is equal to the number of vertices, and ``distrib`` represents + a probability distribution on all of the vertices. In that case, the sink may be chosen + at random, in which case, the configuration is unchanged. + + (ii) Otherwise, the length of ``distrib`` must be equal to the number of nonsink vertices, + and ``distrib`` represents a probability distribution on the nonsink vertices. + + .. WARNING:: + + If ``distrib != None``, the user is responsible for assuring the sum of its entries is + 1 and that its length is equal to the number of sink vertices or the number of nonsink vertices. """ - config = dict(self) - C = CombinatorialClass() - C.list = lambda: self.keys() - config[C.random_element()] += 1 - return SandpileConfig(self._sandpile,config) + c = deepcopy(self) + ind = self._sandpile._sink_ind + n = self._sandpile.num_verts() + if distrib==None: # default = uniform distribution on nonsink vertices + distrib = [1/(n-1)]*(n-1) + if len(distrib)==n-1: # prob. dist. on nonsink vertices + X = GeneralDiscreteDistribution(distrib) + V = self._sandpile.nonsink_vertices() + c[V[X.get_random_element()]] += 1 + else: # prob. dist. on all the vertices + X = GeneralDiscreteDistribution(distrib) + V = self._sandpile.vertices() + i = X.get_random_element() + if i!=self._sandpile._sink_ind: # not the sink + c[V[i]] += 1 + return c def order(self): r""" - The order of the recurrent element equivalent to ``config``. - - INPUT: - - ``config`` - configuration + The order of the equivalent recurrent element. OUTPUT: @@ -3083,9 +3850,19 @@ def order(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: [r.order() for r in S.recurrents()] - [3, 3, 5, 15, 15, 15, 5, 15, 15, 5, 15, 5, 15, 1, 15] + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S,[2,0,1]) + sage: c.order() + 4 + sage: ~(c + c + c + c) == S.identity() + True + sage: c = SandpileConfig(S,[1,1,0]) + sage: c.order() + 1 + sage: c.is_recurrent() + False + sage: c.equivalent_recurrent() == S.identity() + True """ v = vector(self.values()) w = v*self._sandpile.reduced_laplacian().dense_matrix()**(-1) @@ -3093,11 +3870,7 @@ def order(self): def is_stable(self): r""" - ``True`` if stable. - - INPUT: - - None + Is the configuration stable? OUTPUT: @@ -3105,10 +3878,10 @@ def is_stable(self): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: S.max_stable().is_stable() True - sage: (S.max_stable() + S.max_stable()).is_stable() + sage: (2*S.max_stable()).is_stable() False sage: (S.max_stable() & S.max_stable()).is_stable() True @@ -3123,22 +3896,15 @@ def _set_equivalent_recurrent(self): Sets the equivalent recurrent configuration and the corresponding firing vector. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: a = -S.max_stable() sage: a._set_equivalent_recurrent() + sage: '_equivalent_recurrent' in a.__dict__ + True """ old = self - # revise next line with c._sandpile.zero_config() firing_vector = self._sandpile.zero_config() done = False bs = self._sandpile.burning_script() @@ -3155,22 +3921,22 @@ def _set_equivalent_recurrent(self): def equivalent_recurrent(self, with_firing_vector=False): r""" - The recurrent configuration equivalent to the given - configuration and optionally returns the corresponding firing vector. + The recurrent configuration equivalent to the given configuration. + Optionally, return the corresponding firing vector. INPUT: - ``with_firing_vector`` (optional) - boolean + ``with_firing_vector`` -- (default: ``False``) boolean OUTPUT: - ``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` + SandpileConfig or [SandpileConfig, firing_vector] EXAMPLES:: - sage: S = sandlib('generic') - sage: c = SandpileConfig(S, [0,0,0,0,0]) + sage: S = sandpiles.Diamond() + sage: c = SandpileConfig(S, [0,0,0]) sage: c.equivalent_recurrent() == S.identity() True sage: x = c.equivalent_recurrent(True) @@ -3180,11 +3946,11 @@ def equivalent_recurrent(self, with_firing_vector=False): sage: r == cv - f*S.reduced_laplacian() True - NOTES: + .. NOTE:: - Let `L` be the reduced laplacian, `c` the initial configuration, `r` the - returned configuration, and `f` the firing vector. Then `r = c - f\cdot - L`. + Let `L` be the reduced Laplacian, `c` the initial configuration, `r` the + returned configuration, and `f` the firing vector. Then `r = c - f\cdot + L`. """ if with_firing_vector: return self._equivalent_recurrent @@ -3195,18 +3961,13 @@ def _set_is_recurrent(self): r""" Computes and stores whether the configuration is recurrent. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = sandlib('generic') - sage: S.max_stable()._set_is_recurrent() + sage: S = sandpiles.Diamond() + sage: c = S.max_stable() + sage: c._set_is_recurrent() + sage: '_is_recurrent' in c.__dict__ + True """ if '_recurrents' in self._sandpile.__dict__: self._is_recurrent = (self in self._sandpile._recurrents) @@ -3214,18 +3975,13 @@ def _set_is_recurrent(self): self._is_recurrent = (self._equivalent_recurrent == self) else: # add the burning configuration to config - # change once sandpile._burning_config is a SandpileConfig - b = SandpileConfig(self._sandpile,self._sandpile._burning_config) + b = self._sandpile._burning_config c = ~(self + b) self._is_recurrent = (c == self) def is_recurrent(self): r""" - ``True`` if the configuration is recurrent. - - INPUT: - - None + Is the configuration recurrent? OUTPUT: @@ -3233,7 +3989,7 @@ def is_recurrent(self): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: S.identity().is_recurrent() True sage: S.zero_config().is_recurrent() @@ -3246,41 +4002,39 @@ def _set_equivalent_superstable(self): Sets the superstable configuration equivalent to the given configuration and its corresponding firing vector. - INPUT: - - None - OUTPUT: - ``[configuration, firing_vector]`` + [configuration, firing_vector] EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: m = S.max_stable() sage: m._set_equivalent_superstable() + sage: '_equivalent_superstable' in m.__dict__ + True """ r, fv = self.dualize().equivalent_recurrent(with_firing_vector=True) self._equivalent_superstable = [r.dualize(), -fv] def equivalent_superstable(self, with_firing_vector=False): r""" - The equivalent superstable configuration. Optionally - returns the corresponding firing vector. + The equivalent superstable configuration. Optionally, return the + corresponding firing vector. INPUT: - ``with_firing_vector`` (optional) - boolean + ``with_firing_vector`` -- (default: ``False``) boolean OUTPUT: - ``SandpileConfig`` or ``[SandpileConfig, firing_vector]`` + SandpileConfig or [SandpileConfig, firing_vector] EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: m = S.max_stable() sage: m.equivalent_superstable().is_superstable() True @@ -3291,11 +4045,11 @@ def equivalent_superstable(self, with_firing_vector=False): sage: s == mv - f*S.reduced_laplacian() True - NOTES: + .. NOTE:: - Let `L` be the reduced laplacian, `c` the initial configuration, `s` the - returned configuration, and `f` the firing vector. Then `s = c - f\cdot - L`. + Let `L` be the reduced Laplacian, `c` the initial configuration, `s` the + returned configuration, and `f` the firing vector. Then `s = c - f\cdot + L`. """ if with_firing_vector: return self._equivalent_superstable @@ -3306,18 +4060,17 @@ def _set_is_superstable(self): r""" Computes and stores whether ``config`` is superstable. - INPUT: - - None - OUTPUT: boolean EXAMPLES:: - sage: S = sandlib('generic') - sage: S.zero_config()._set_is_superstable() + sage: S = sandpiles.Diamond() + sage: z = S.zero_config() + sage: z._set_is_superstable() + sage: '_is_superstable' in z.__dict__ + True """ if '_superstables' in self._sandpile.__dict__: self._is_superstable = (self in self._sandpile._superstables) @@ -3328,12 +4081,7 @@ def _set_is_superstable(self): def is_superstable(self): r""" - ``True`` if ``config`` is superstable, i.e., whether its dual is - recurrent. - - INPUT: - - None + Is the configuration superstable? OUTPUT: @@ -3341,7 +4089,7 @@ def is_superstable(self): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Diamond() sage: S.zero_config().is_superstable() True """ @@ -3349,12 +4097,13 @@ def is_superstable(self): def is_symmetric(self, orbits): r""" - This function checks if the values of the configuration are constant - over the vertices in each sublist of ``orbits``. + Is the configuration symmetric? Return ``True`` if the values of the + configuration are constant over the vertices in each sublist of + ``orbits``. INPUT: - ``orbits`` - list of lists of vertices + ``orbits`` -- list of lists of vertices OUTPUT: @@ -3362,13 +4111,11 @@ def is_symmetric(self, orbits): EXAMPLES:: - sage: S = sandlib('kite') - sage: S.dict() - {0: {}, - 1: {0: 1, 2: 1, 3: 1}, - 2: {1: 1, 3: 1, 4: 1}, - 3: {1: 1, 2: 1, 4: 1}, - 4: {2: 1, 3: 1}} + sage: S = Sandpile({0: {}, + ....: 1: {0: 1, 2: 1, 3: 1}, + ....: 2: {1: 1, 3: 1, 4: 1}, + ....: 3: {1: 1, 2: 1, 4: 1}, + ....: 4: {2: 1, 3: 1}}) sage: c = SandpileConfig(S, [1, 2, 2, 3]) sage: c.is_symmetric([[2,3]]) True @@ -3378,28 +4125,66 @@ def is_symmetric(self, orbits): return False return True - def show(self,sink=True,colors=True,heights=False,directed=None,**kwds): + def burst_size(self, v): r""" - Show the configuration. + The burst size of the configuration with respect to the given vertex. INPUT: - - ``sink`` - whether to show the sink - - ``colors`` - whether to color-code the amount of sand on each vertex - - ``heights`` - whether to label each vertex with the amount of sand - - ``kwds`` - arguments passed to the show method for Graph - - ``directed`` - whether to draw directed edges + ``v`` -- vertex OUTPUT: - None + integer + + EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: [i.burst_size(0) for i in s.recurrents()] + [1, 1, 1, 1, 1, 1, 1, 1] + sage: [i.burst_size(1) for i in s.recurrents()] + [0, 0, 1, 2, 1, 2, 0, 2] + + .. NOTE:: + + To define ``c.burst(v)``, if `v` is not the sink, let `c'` be the unique + recurrent for which the the stabilization of `c' + v` is `c`. The + burst size is then the amount of sand that goes into the sink during this + stabilization. If `v` is the sink, the burst size is defined to be 1. + + REFERENCES: + + .. [Levine2014]_ Lionel Levine. Threshold state and a conjecture of Poghosyan, Poghosyan, + Priezzhev and Ruelle, Communications in Mathematical Physics. + """ + if v==self.sandpile().sink(): + return 1 + else: + w = deepcopy(self) + w[v] -= 1 + w = w.equivalent_recurrent() + return w.deg() - self.deg() +1 + + def show(self, sink=True, colors=True, heights=False, directed=None, **kwds): + r""" + Show the configuration. + + INPUT: + + - ``sink`` -- (default: ``True``) whether to show the sink + + - ``colors`` -- (default: ``True``) whether to color-code the amount of sand on each vertex + + - ``heights`` -- (default: ``False``) whether to label each vertex with the amount of sand + + - ``directed`` -- (optional) whether to draw directed edges + + - ``kwds`` -- (optional) arguments passed to the show method for Graph EXAMPLES:: - sage: S=sandlib('genus2') - sage: c=S.identity() - sage: S=sandlib('genus2') - sage: c=S.identity() + sage: S = sandpiles.Diamond() + sage: c = S.identity() sage: c.show() sage: c.show(directed=False) sage: c.show(sink=False,colors=False,heights=True) @@ -3438,23 +4223,112 @@ def show(self,sink=True,colors=True,heights=False,directed=None,**kwds): else: T.show(**kwds) -####################################### +############################################### ########### SandpileDivisor Class ############# -####################################### +############################################### class SandpileDivisor(dict): r""" Class for divisors on a sandpile. """ + @staticmethod + def help(verbose=True): + r""" + List of SandpileDivisor methods. If ``verbose``, include short descriptions. + + INPUT: + + ``verbose`` -- (default: ``True``) boolean + + OUTPUT: + + printed string + + EXAMPLES:: + + sage: SandpileDivisor.help() + For detailed help with any method FOO listed below, + enter "SandpileDivisor.FOO?" or enter "D.FOO?" for any SandpileDivisor D. + + Dcomplex -- The support-complex. + add_random -- Add one grain of sand to a random vertex. + betti -- The Betti numbers for the support-complex. + deg -- The degree of the divisor. + dualize -- The difference with the maximal stable divisor. + effective_div -- All linearly equivalent effective divisors. + fire_script -- Fire the given script. + fire_unstable -- Fire all unstable vertices. + fire_vertex -- Fire the given vertex. + help -- List of SandpileDivisor methods. + is_alive -- Is the divisor stabilizable? + is_linearly_equivalent -- Is the given divisor linearly equivalent? + is_q_reduced -- Is the divisor q-reduced? + is_symmetric -- Is the divisor symmetric? + is_weierstrass_pt -- Is the given vertex a Weierstrass point? + linear_system -- The complete linear system (deprecated: use "polytope_integer_pts"). + polytope -- The polytope determinining the complete linear system. + polytope_integer_pts -- The integer points inside divisor's polytope. + q_reduced -- The linearly equivalent q-reduced divisor. + r_of_D -- The rank of the divisor (deprecated: use "rank", instead). + rank -- The rank of the divisor. + sandpile -- The divisor's underlying sandpile. + show -- Show the divisor. + simulate_threshold -- The first unstabilizable divisor in the closed Markov chain. + stabilize -- The stabilization of the divisor. + support -- List of vertices at which the divisor is nonzero. + unstable -- The unstable vertices. + values -- The values of the divisor as a list. + weierstrass_div -- The Weierstrass divisor. + weierstrass_gap_seq -- The Weierstrass gap sequence at the given vertex. + weierstrass_pts -- The Weierstrass points (vertices). + weierstrass_rank_seq -- The Weierstrass rank sequence at the given vertex. + """ + # We collect the first sentence of each docstring. The sentence is, + # by definition, from the beginning of the string to the first + # occurrence of a period or question mark. If neither of these appear + # in the string, take the sentence to be the empty string. If the + # latter occurs, something should be changed. + methods = [] + for i in sorted(SandpileDivisor.__dict__.keys()): + if i[0]!='_': + s = eval('getdoc(SandpileDivisor.' + i +')') + period = s.find('.') + question = s.find('?') + if period==-1 and question==-1: + s = '' # Neither appears! + else: + if period==-1: + period = len(s) + 1 + if question==-1: + question = len(s) + 1 + if period < question: + s = s.split('.')[0] + s = detex(s).strip() + '.' + else: + s = s.split('?')[0] + s = detex(s).strip() + '?' + methods.append([i,s]) + print 'For detailed help with any method FOO listed below,' + print 'enter "SandpileDivisor.FOO?" or enter "D.FOO?" for any SandpileDivisor D.' + print '' + mlen = max([len(i[0]) for i in methods]) + if verbose: + for i in methods: + print i[0].ljust(mlen), '--', i[1] + else: + for i in methods: + print i[0] + def __init__(self, S, D): r""" Create a divisor on a Sandpile. INPUT: - - ``S`` - Sandpile - - ``D`` - dict or list representing a divisor + - ``S`` -- Sandpile + + - ``D`` -- dict or list representing a divisor OUTPUT: @@ -3462,7 +4336,7 @@ def __init__(self, S, D): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S,[0,1,0,1,1,3]) sage: D.support() [1, 3, 4, 5] @@ -3481,6 +4355,7 @@ def __init__(self, S, D): self._sandpile = S self._vertices = S.vertices() + self._weierstrass_rank_seq = {} def __deepcopy__(self, memo): r""" @@ -3488,15 +4363,11 @@ def __deepcopy__(self, memo): INPUT: - None - - OUTPUT: - - None + memo -- (optional) dict EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S, [1,2,3,4,5,6]) sage: E = deepcopy(D) sage: E[0] += 10 @@ -3515,33 +4386,29 @@ def __setitem__(self, key, item): INPUT: - - ``key``, ``item`` - objects - - OUTPUT: - - None + ``key``, ``item`` -- objects EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [0,1,1]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: D.__dict__ # optional - 4ti2 - {'_effective_div': [{0: 2, 1: 0, 2: 0}, {0: 0, 1: 1, 2: 1}], - '_linear_system': {'homog': [[1, 1, 1], [-1, -1, -1]], - 'inhomog': [[1, 0, 0], [0, 0, 0], [0, -1, -1]], - 'num_homog': 2, - 'num_inhomog': 3}, - '_sandpile': Digraph on 3 vertices, + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S,[0,1,1]) + sage: eff = D.effective_div() + sage: D.__dict__ + {'_effective_div': [{0: 0, 1: 1, 2: 1}, {0: 2, 1: 0, 2: 0}], + '_polytope': A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices, + '_polytope_integer_pts': ((0, 0), (1, 1)), + '_sandpile': Cycle sandpile graph: 3 vertices, sink = 0, + '_vertices': [0, 1, 2], + '_weierstrass_rank_seq': {}} + sage: D[0] += 1 + sage: D.__dict__ + {'_sandpile': Cycle sandpile graph: 3 vertices, sink = 0, '_vertices': [0, 1, 2]} - sage: D[0] += 1 # optional - 4ti2 - sage: D.__dict__ # optional - 4ti2 - {'_sandpile': Digraph on 3 vertices, '_vertices': [0, 1, 2]} - NOTES: + .. NOTE:: - In the example, above, changing the value of ``D`` at some vertex makes - a call to setitem, which resets some of the stored variables for ``D``. + In the example, above, changing the value of `D` at some vertex makes + a call to setitem, which resets some of the stored variables for `D`. """ if key in self.keys(): dict.__setitem__(self,key,item) @@ -3557,15 +4424,11 @@ def __getattr__(self, name): INPUT: - ``name`` - name of an internal method - - OUTPUT: - - None. + ``name`` -- name of an internal method EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S,[0,1,0,1,1,3]) sage: D.__getattr__('_deg') 6 @@ -3574,12 +4437,27 @@ def __getattr__(self, name): if name=='_deg': self._set_deg() return self.__dict__[name] + if name=='_q_reduced': + self._set_q_reduced() + return self.__dict__[name] if name=='_linear_system': self._set_linear_system() return self.__dict__[name] if name=='_effective_div': self._set_effective_div() return self.__dict__[name] + if name=='_polytope': + self._set_polytope() + return self.__dict__[name] + if name=='_polytope_integer_pts': + self._set_polytope_integer_pts() + return self.__dict__[name] + if name=='_rank': + self._set_rank() + return self.__dict__[name] + if name=='_rank_witness': + self._set_rank(True) + return self.__dict__[name] if name=='_r_of_D': self._set_r_of_D() return self.__dict__[name] @@ -3589,6 +4467,12 @@ def __getattr__(self, name): if name=='_life': self._set_life() return self.__dict__[name] + if name=='_stabilize': + self._set_stabilize() + return self.__dict__[name] + if name=='_weierstrass_pts': + self._set_weierstrass_pts() + return self.__dict__[name] else: raise AttributeError(name) @@ -3596,19 +4480,17 @@ def _set_deg(self): r""" Compute and store the degree of the divisor. - INPUT: - - None - OUTPUT: integer EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D._set_deg() + sage: '_deg' in D.__dict__ + True """ self._deg = sum(self.values()) @@ -3616,17 +4498,13 @@ def deg(self): r""" The degree of the divisor. - INPUT: - - None - OUTPUT: integer EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.deg() 6 @@ -3639,7 +4517,7 @@ def __add__(self, other): INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3647,24 +4525,71 @@ def __add__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [3,2,1]) sage: D + E {0: 4, 1: 4, 2: 4} """ - sum = deepcopy(self) - for v in self: - sum[v] += other[v] - return sum + return SandpileDivisor(self.sandpile(),[i+j for i,j in zip(self.values(),other.values())]) + + def __mul__(self, other): + r""" + Sum of the divisor with itself ``other`` times. + + INPUT: + + ``other`` -- integer + + OUTPUT: + + SandpileDivisor + + EXAMPLES:: + + sage: S = sandpiles.Cycle(4) + sage: D = SandpileDivisor(S,[1,2,3,4]) + sage: D + {0: 1, 1: 2, 2: 3, 3: 4} + sage: 3*D + {0: 3, 1: 6, 2: 9, 3: 12} + sage: 3*D == D*3 + True + """ + return SandpileDivisor(self.sandpile(),[i*other for i in self.values()]) + + def __rmul__(self, other): + r""" + The sum of divisor with itself ``other`` times. + + INPUT: + + ``other`` -- Integer + + OUTPUT: + + SandpileDivisor - def __radd__(self,other): + EXAMPLES:: + + sage: S = sandpiles.Cycle(4) + sage: D = SandpileDivisor(S,[1,2,3,4]) + sage: D + {0: 1, 1: 2, 2: 3, 3: 4} + sage: 3*D + {0: 3, 1: 6, 2: 9, 3: 12} + sage: 3*D == D*3 + True + """ + return SandpileDivisor(self.sandpile(),[other*i for i in self.values()]) + + def __radd__(self, other): r""" Right-side addition of divisors. INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3672,7 +4597,7 @@ def __radd__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [3,2,1]) sage: D.__radd__(E) @@ -3689,7 +4614,7 @@ def __sub__(self, other): INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3697,7 +4622,7 @@ def __sub__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [3,2,1]) sage: D - E @@ -3711,9 +4636,10 @@ def __sub__(self, other): def __rsub__(self, other): r""" Right-side subtraction of divisors. + INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3721,7 +4647,7 @@ def __rsub__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = {0: 3, 1: 2, 2: 1} sage: D.__rsub__(E) @@ -3738,17 +4664,13 @@ def __neg__(self): r""" The additive inverse of the divisor. - INPUT: - - None - OUTPUT: SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: -D {0: -1, 1: -2, 2: -3} @@ -3762,7 +4684,7 @@ def __le__(self, other): INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3770,7 +4692,7 @@ def __le__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: F = SandpileDivisor(S, [2,0,4]) @@ -3787,14 +4709,14 @@ def __le__(self, other): """ return forall(self._vertices, lambda v: self[v]<=other[v])[0] - def __lt__(self,other): + def __lt__(self, other): r""" ``True`` if every component of ``self`` is at most that of ``other`` and the two divisors are not equal. INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3802,7 +4724,7 @@ def __lt__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: D < D @@ -3821,7 +4743,7 @@ def __ge__(self, other): INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3829,7 +4751,7 @@ def __ge__(self, other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [2,3,4]) sage: F = SandpileDivisor(S, [2,0,4]) @@ -3846,14 +4768,14 @@ def __ge__(self, other): """ return forall(self._vertices, lambda v: self[v]>=other[v])[0] - def __gt__(self,other): + def __gt__(self, other): r""" ``True`` if every component of ``self`` is at least that of ``other`` and the two divisors are not equal. INPUT: - ``other`` - SandpileDivisor + ``other`` -- SandpileDivisor OUTPUT: @@ -3861,7 +4783,7 @@ def __gt__(self,other): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: E = SandpileDivisor(S, [1,3,4]) sage: D > D @@ -3877,20 +4799,16 @@ def sandpile(self): r""" The divisor's underlying sandpile. - INPUT: - - None - OUTPUT: Sandpile EXAMPLES:: - sage: S = sandlib('genus2') + sage: S = sandpiles.Diamond() sage: D = SandpileDivisor(S,[1,-2,0,3]) sage: D.sandpile() - Digraph on 4 vertices + Diamond sandpile graph: 4 vertices, sink = 0 sage: D.sandpile() == S True """ @@ -3898,12 +4816,8 @@ def sandpile(self): def values(self): r""" - The values of the divisor as a list, sorted in the order of the - vertices. - - INPUT: - - None + The values of the divisor as a list. The list is sorted in the order of + the vertices. OUTPUT: @@ -3926,19 +4840,14 @@ def values(self): def dualize(self): r""" - The difference between the maximal stable divisor and the - divisor. - - INPUT: - - None + The difference with the maximal stable divisor. OUTPUT: SandpileDivisor EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.dualize() {0: 0, 1: -1, 2: -2} @@ -3947,13 +4856,13 @@ def dualize(self): """ return self._sandpile.max_stable_div() - self - def fire_vertex(self,v): + def fire_vertex(self, v): r""" - Fire the vertex ``v``. + Fire the given vertex. INPUT: - ``v`` - vertex + ``v`` -- vertex OUTPUT: @@ -3961,7 +4870,7 @@ def fire_vertex(self,v): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.fire_vertex(1) {0: 2, 1: 0, 2: 4} @@ -3974,12 +4883,12 @@ def fire_vertex(self,v): def fire_script(self, sigma): r""" - Fire the script ``sigma``, i.e., fire each vertex the indicated number - of times. + Fire the given script. In other words, fire each vertex the number of + times indicated by ``sigma``. INPUT: - ``sigma`` - SandpileDivisor or (list or dict representing a SandpileDivisor) + ``sigma`` -- SandpileDivisor or (list or dict representing a SandpileDivisor) OUTPUT: @@ -3987,7 +4896,7 @@ def fire_script(self, sigma): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.unstable() [1, 2] @@ -4009,11 +4918,7 @@ def fire_script(self, sigma): def unstable(self): r""" - List of the unstable vertices. - - INPUT: - - None + The unstable vertices. OUTPUT: @@ -4021,7 +4926,7 @@ def unstable(self): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.unstable() [1, 2] @@ -4029,13 +4934,196 @@ def unstable(self): return [v for v in self._vertices if self[v]>=self._sandpile.out_degree(v)] - def fire_unstable(self): + def fire_unstable(self): + r""" + Fire all unstable vertices. + + OUTPUT: + + SandpileDivisor + + EXAMPLES:: + + sage: S = sandpiles.Cycle(3) + sage: D = SandpileDivisor(S, [1,2,3]) + sage: D.fire_unstable() + {0: 3, 1: 1, 2: 2} + """ + D = dict(self) + for v in self.unstable(): + D[v] -= self._sandpile.out_degree(v) + for e in self._sandpile.outgoing_edges(v): + D[e[1]]+=e[2] + return SandpileDivisor(self._sandpile,D) + + def _set_q_reduced(self): + r""" + The linearly equivalent `q`-reduced divisor. + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[2,-3,2,0]) + sage: D._set_q_reduced() + sage: '_q_reduced' in D.__dict__ + True + """ + S = self.sandpile() + c = SandpileConfig(S,[self[i] for i in S.nonsink_vertices()]) + c = c.equivalent_superstable() + D = {v:c[v] for v in S.nonsink_vertices()} + D[S.sink()] = self.deg() - c.deg() + self._q_reduced = SandpileDivisor(S,D) + + def q_reduced(self, verbose=True): + r""" + The linearly equivalent `q`-reduced divisor. + + INPUT: + + ``verbose`` -- (default: ``True``) boolean + + OUTPUT: + + SandpileDivisor or list representing SandpileDivisor + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[2,-3,2,0]) + sage: D.q_reduced() + {0: -2, 1: 1, 2: 2, 3: 0} + sage: D.q_reduced(False) + [-2, 1, 2, 0] + + .. NOTE:: + + The divisor `D` is `qreduced if `D = c + kq` where `c` + is superstable, `k` is an integer, and `q` is the sink. + """ + if verbose: + return deepcopy(self._q_reduced) + else: + return self._q_reduced.values() + + def is_q_reduced(self): + r""" + Is the divisor `q`-reduced? This would mean that `self = c + kq` where + `c` is superstable, `k` is an integer, and `q` is the sink vertex. + + OUTPUT: + + boolean + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[2,-3,2,0]) + sage: D.is_q_reduced() + False + sage: SandpileDivisor(s,[10,0,1,2]).is_q_reduced() + True + + For undirected or, more generally, Eulerian graphs, `q`-reduced divisors are + linearly equivalent if and only if they are equal. The same does not hold for + general directed graphs: + + :: + + sage: s = Sandpile({0:[1],1:[1,1]}) + sage: D = SandpileDivisor(s,[-1,1]) + sage: Z = s.zero_div() + sage: D.is_q_reduced() + True + sage: Z.is_q_reduced() + True + sage: D == Z + False + sage: D.is_linearly_equivalent(Z) + True + """ + S = self.sandpile() + c = SandpileConfig(S,[self[v] for v in S.nonsink_vertices()]) + return c.is_superstable() + + def is_linearly_equivalent(self, D, with_firing_vector=False): + r""" + Is the given divisor linearly equivalent? Optionally, returns the + firing vector. (See NOTE.) + + INPUT: + + - ``D`` -- SandpileDivisor or list, tuple, etc. representing a divisor + + - ``with_firing_vector`` -- (default: ``False``) boolean + + OUTPUT: + + boolean or integer vector + + EXAMPLES:: + + sage: s = sandpiles.Complete(3) + sage: D = SandpileDivisor(s,[2,0,0]) + sage: D.is_linearly_equivalent([0,1,1]) + True + sage: D.is_linearly_equivalent([0,1,1],True) + (1, 0, 0) + sage: v = vector(D.is_linearly_equivalent([0,1,1],True)) + sage: vector(D.values()) - s.laplacian()*v + (0, 1, 1) + sage: D.is_linearly_equivalent([0,0,0]) + False + sage: D.is_linearly_equivalent([0,0,0],True) + () + + .. NOTE:: + + - If ``with_firing_vector`` is ``False``, returns either ``True`` or ``False``. + + - If ``with_firing_vector`` is ``True`` then: (i) if ``self`` is linearly + equivalent to `D`, returns a vector `v` such that ``self - v*self.laplacian().transpose() = D``. + Otherwise, (ii) if ``self`` is not linearly equivalent to `D`, the output is the empty vector, ``()``. + """ + # First try to convert D into a vector. + v = vector(self.values()) + if isinstance(D,SandpileDivisor): + w = vector(D.values()) + else: + try: + w = vector(D) + except: + raise SyntaxError(D) + # Now test for linear equivalence and find firing vector + D,U,V = self.sandpile()._smith_form + b = v - w + ub = U*b + if ub[-1]!=0: + if with_firing_vector: + return vector([]) + else: + return False + else: + try: + x = vector(ZZ,[ub[i]/D[i][i] for i in range(D.nrows()-1)]+[0]) + if with_firing_vector: + return V*x + else: + return True + except: + if with_firing_vector: + return vector([]) + else: + return False + + def simulate_threshold(self, distrib=None): r""" - Fire all unstable vertices. + The first unstabilizable divisor in the closed Markov chain. + (See NOTE.) INPUT: - None + ``distrib`` -- (optional) list of nonnegative numbers representing a probability distribution on the vertices OUTPUT: @@ -4043,37 +5131,52 @@ def fire_unstable(self): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) - sage: D = SandpileDivisor(S, [1,2,3]) - sage: D.fire_unstable() - {0: 3, 1: 1, 2: 2} - """ - D = dict(self) - for v in self.unstable(): - D[v] -= self._sandpile.out_degree(v) - for e in self._sandpile.outgoing_edges(v): - D[e[1]]+=e[2] - return SandpileDivisor(self._sandpile,D) + sage: s = sandpiles.Complete(4) + sage: D = s.zero_div() + sage: D.simulate_threshold() # random + {0: 2, 1: 3, 2: 1, 3: 2} + sage: n(mean([D.simulate_threshold().deg() for _ in range(10)])) # random + 7.10000000000000 + sage: n(s.stationary_density()*s.num_verts()) + 6.93750000000000 + + .. NOTE:: + + Starting at ``self``, repeatedly choose a vertex and add a grain of + sand to it. Return the first unstabilizable divisor that is + reached. Also see the ``markov_chain`` method for the underlying + sandpile. + """ + E = deepcopy(self) + S = E.sandpile() + V = S.vertices() + n = S.num_verts() + if distrib==None: # default = uniform distribution + distrib = [1/n]*n + X = GeneralDiscreteDistribution(distrib) + while not E.is_alive(): + E = E.stabilize() + i = X.get_random_element() + E[V[i]] += 1 + return E def _set_linear_system(self): r""" Computes and stores the complete linear system of a divisor. - INPUT: None - OUTPUT: dict - ``{num_homog: int, homog:list, num_inhomog:int, inhomog:list}`` EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [0,1,1]) sage: D._set_linear_system() # optional - 4ti2 - WARNING: + .. WARNING: - This method requires 4ti2. + This method requires 4ti2. """ # import os @@ -4135,9 +5238,6 @@ def _set_linear_system(self): ********************************** *** This method requires 4ti2. *** ********************************** - - Read the beginning of sandpile.sage or see the Sage Sandpiles - documentation for installation instructions. """ return ## first, the cone generators (the homogeneous points) @@ -4146,22 +5246,20 @@ def _set_linear_system(self): a = a.split('\n') # a starts with two numbers. We are interested in the first one num_homog = int(a[0].split()[0]) - homog = [[int(_) for _ in i.split()] for i in a[1:-1]] + homog = [map(int,i.split()) for i in a[1:-1]] ## second, the inhomogeneous points zinhom_file = open(lin_sys_zinhom,'r') b = zinhom_file.read() zinhom_file.close() b = b.split('\n') num_inhomog = int(b[0].split()[0]) - inhomog = [[int(_) for _ in i.split()] for i in b[1:-1]] + inhomog = [map(int,i.split()) for i in b[1:-1]] self._linear_system = {'num_homog':num_homog, 'homog':homog, 'num_inhomog':num_inhomog, 'inhomog':inhomog} def linear_system(self): r""" - The complete linear system of a divisor. - - INPUT: None + The complete linear system (deprecated: use ``polytope_integer_pts``). OUTPUT: @@ -4169,7 +5267,13 @@ def linear_system(self): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = Sandpile({0: {}, + ....: 1: {0: 1, 3: 1, 4: 1}, + ....: 2: {0: 1, 3: 1, 5: 1}, + ....: 3: {2: 1, 5: 1}, + ....: 4: {1: 1, 3: 1}, + ....: 5: {2: 1, 3: 1}} + ....: ) sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) sage: D.linear_system() # optional - 4ti2 {'homog': [[1, 0, 0, 0, 0, 0], [-1, 0, 0, 0, 0, 0]], @@ -4177,54 +5281,142 @@ def linear_system(self): 'num_homog': 2, 'num_inhomog': 3} - NOTES: + .. NOTE:: - If `L` is the Laplacian, an arbitrary `v` such that `v\cdot L\geq -D` - has the form `v = w + t` where `w` is in ``inhomg`` and `t` is in the - integer span of ``homog`` in the output of ``linear_system(D)``. + If `L` is the Laplacian, an arbitrary `v` such that `v\cdot L\geq -D` + has the form `v = w + t` where `w` is in ``inhomg`` and `t` is in the + integer span of ``homog`` in the output of ``linear_system(D)``. - WARNING: + .. WARNING:: - This method requires 4ti2. + This method requires 4ti2. """ + deprecation(18618,'D.linear_system() will be removed soon. See D.rank() and D.polytope().') return self._linear_system - def _set_effective_div(self): + def _set_polytope(self): r""" - Computes all of the linearly equivalent effective divisors linearly. + Compute the polyhedron determining the linear system for D. - INPUT: + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D._set_polytope() + sage: '_polytope' in D.__dict__ + True + """ + S = self.sandpile() + myL = S.laplacian().transpose().delete_columns([S._sink_ind]) + my_ieqs = [[self[v]] + list(-myL[i]) for i,v in enumerate(S.vertices())] + self._polytope = Polyhedron(ieqs=my_ieqs) + + def polytope(self): + r""" + The polytope determinining the complete linear system. + + OUTPUT: + + polytope + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: p = D.polytope() + sage: p.inequalities() + (An inequality (-3, 1, 1) x + 2 >= 0, + An inequality (1, 1, 1) x + 4 >= 0, + An inequality (1, -3, 1) x + 0 >= 0, + An inequality (1, 1, -3) x + 0 >= 0) + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.polytope() + The empty polyhedron in QQ^3 + + .. NOTE:: + + For a divisor `D`, this is the intersection of (i) the polyhedron + determined by the system of inequalities `L^t x \leq D` where `L^t` + is the transpose of the Laplacian with (ii) the hyperplane + `x_{\mathrm{sink\_vertex}} = 0`. The polytope is thought of as sitting in + `(n-1)`-dimensional Euclidean space where `n` is the number of + vertices. + """ + return deepcopy(self._polytope) - None + def _set_polytope_integer_pts(self): + r""" + Record the integer lattice points inside the polytope determining the + complete linear system (see the documentation for ``polytope``). + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D._set_polytope_integer_pts() + sage: '_polytope_integer_pts' in D.__dict__ + True + """ + self._polytope_integer_pts = self._polytope.integral_points() + + def polytope_integer_pts(self): + r""" + The integer points inside divisor's polytope. The polytope referred to + here is the one determining the divisor's complete linear system (see the + documentation for ``polytope``). OUTPUT: - None + tuple of integer vectors EXAMPLES:: - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) # optional - 4ti2 - sage: D._set_effective_div() # optional - 4ti2 + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D.polytope_integer_pts() + ((-2, -1, -1), + (-1, -2, -1), + (-1, -1, -2), + (-1, -1, -1), + (0, -1, -1), + (0, 0, 0)) + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.polytope_integer_pts() + () """ - result = [] - r = self.linear_system() - d = vector(self.values()) - for x in r['inhomog']: - c = vector(x)*self._sandpile._laplacian + d - c = SandpileDivisor(self._sandpile,list(c)) - if c not in result: - result.append(c) - self._effective_div = result + return deepcopy(self._polytope_integer_pts) + + def _set_effective_div(self): + r""" + Compute all of the linearly equivalent effective divisors linearly. + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D._set_effective_div() + sage: '_effective_div' in D.__dict__ + True + """ + S = self.sandpile() + myL = S.laplacian().transpose().delete_columns([S._sink_ind]) + P = self.polytope() + dv = vector(ZZ,self.values()) + self._effective_div = [SandpileDivisor(S,list(dv - myL*i)) for i in self._polytope_integer_pts] - def effective_div(self, verbose=True): + def effective_div(self, verbose=True, with_firing_vectors=False): r""" All linearly equivalent effective divisors. If ``verbose`` is ``False``, the divisors are converted to lists of integers. + If ``with_firing_vectors`` is ``True`` then a list of firing vectors + is also given, each of which prescribes the vertices to be fired + in order to obtain an effective divisor. INPUT: - ``verbose`` (optional) - boolean + - ``verbose`` -- (default: ``True``) boolean + + - ``with_firing_vectors`` -- (default: ``False``) boolean OUTPUT: @@ -4232,34 +5424,206 @@ def effective_div(self, verbose=True): EXAMPLES:: - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) # optional - 4ti2 - sage: D.effective_div() # optional - 4ti2 - [{0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}, {0: 1, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0}, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 2}] - sage: D.effective_div(False) # optional - 4ti2 - [[0, 0, 1, 1, 0, 0], [1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 2]] + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D.effective_div() + [{0: 0, 1: 6, 2: 0, 3: 0}, + {0: 0, 1: 2, 2: 4, 3: 0}, + {0: 0, 1: 2, 2: 0, 3: 4}, + {0: 1, 1: 3, 2: 1, 3: 1}, + {0: 2, 1: 0, 2: 2, 3: 2}, + {0: 4, 1: 2, 2: 0, 3: 0}] + sage: D.effective_div(False) + [[0, 6, 0, 0], + [0, 2, 4, 0], + [0, 2, 0, 4], + [1, 3, 1, 1], + [2, 0, 2, 2], + [4, 2, 0, 0]] + sage: D.effective_div(with_firing_vectors=True) + [({0: 0, 1: 6, 2: 0, 3: 0}, (0, -2, -1, -1)), + ({0: 0, 1: 2, 2: 4, 3: 0}, (0, -1, -2, -1)), + ({0: 0, 1: 2, 2: 0, 3: 4}, (0, -1, -1, -2)), + ({0: 1, 1: 3, 2: 1, 3: 1}, (0, -1, -1, -1)), + ({0: 2, 1: 0, 2: 2, 3: 2}, (0, 0, -1, -1)), + ({0: 4, 1: 2, 2: 0, 3: 0}, (0, 0, 0, 0))] + sage: a = _[0] + sage: a[0].values() + [0, 6, 0, 0] + sage: vector(D.values()) - s.laplacian()*a[1] + (0, 6, 0, 0) + sage: D.effective_div(False, True) + [([0, 6, 0, 0], (0, -2, -1, -1)), + ([0, 2, 4, 0], (0, -1, -2, -1)), + ([0, 2, 0, 4], (0, -1, -1, -2)), + ([1, 3, 1, 1], (0, -1, -1, -1)), + ([2, 0, 2, 2], (0, 0, -1, -1)), + ([4, 2, 0, 0], (0, 0, 0, 0))] + sage: D = SandpileDivisor(s,[-1,0,0,0]) + sage: D.effective_div(False,True) + [] """ - if verbose: - return deepcopy(self._effective_div) + S = self.sandpile() + eff = deepcopy(self._effective_div) + if with_firing_vectors: + fv = [vector(list(i)[:S._sink_ind] + [0] + list(i)[S._sink_ind:]) for i in self._polytope_integer_pts] + if verbose and with_firing_vectors: + return zip(eff,fv) + elif verbose: # verbose without firing vectors + return eff + elif with_firing_vectors: # not verbose but with firing vectors + return zip([i.values() for i in eff],fv) + else: # not verbose, no firing vectors + return [i.values() for i in eff] + + def _set_rank(self, set_witness=False): + r""" + Find the rank of the divisor `D` and an effective divisor `E` such that + `D - E` is unwinnable, i.e., has an empty complete linear system. If + Riemann-Roch applies, ``verbose`` is ``False``, and the degree of `D` is greater + than `2g-2` (`g = ` genus), then the rank is `\deg(D) - g`. In that case, + the divisor `E` is not calculated. + + INPUT: + + ``verbose`` -- (default: ``False``) boolean + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[4,2,0,0]) + sage: D._set_rank() + sage: '_rank' in D.__dict__ + True + sage: '_rank_witness' in D.__dict__ + False + sage: D._set_rank(True) + sage: '_rank_witness' in D.__dict__ + True + sage: D = SandpileDivisor(s,[1,0,0,0]) + sage: D._set_rank() + sage: '_rank' in D.__dict__ + True + sage: '_rank_witness' in D.__dict__ + False + """ + S = self.sandpile() + # If undirected and D has high degree, use Riemann-Roch. + if S.is_undirected() and not set_witness: # We've been careful about loops + g = sum(S.laplacian().diagonal())/2 - S.num_verts() + 1 + if self.deg() > 2*g - 2: + self._rank = self.deg() - g + return # return early + # If S is a complete sandpile graph and a witness is not needed, use + # the Cori-Le Borgne algorithm + if S.name()=='Complete sandpile graph' and not set_witness: + # Cori-LeBorgne algorithm + n = S.num_verts() + rk = -1 + E = self.q_reduced() + k = E[S.sink()] + c = [E[v] for v in S.nonsink_vertices()] + c.sort() + while k >= 0: + rk += 1 + try: + d = next(i for i,j in enumerate(c) if i==j and i!=0) + except: + d = n - 1 + k = k - d + if k >=0: + c[0] = n - 1 - d + b1 = [c[i] + n - d for i in range(1,d)] + b2 = [c[i] - d for i in range(d,n-1)] + c = b2 + [c[0]] + b1 + self._rank = rk + # All other cases. else: - return [E.values() for E in self._effective_div] + rk = -1 + while True: + IV = IntegerVectors(rk+1,S.num_verts()) + for e in IV: + E = SandpileDivisor(S,e) + if (self - E).effective_div()==[]: + self._rank = rk + self._rank_witness = E + return + rk += 1 - def _set_r_of_D(self, verbose=False): + def rank(self, with_witness=False): r""" - Computes ``r(D)`` and an effective divisor ``F`` such - that ``|D - F|`` is empty. + The rank of the divisor. Optionally returns an effective divisor `E` such + that `D - E` is not winnable (has an empty complete linear system). INPUT: - verbose (optional) - boolean + ``with_witness`` -- (default: ``False``) boolean OUTPUT: - None + integer or (integer, SandpileDivisor) + + EXAMPLES:: + + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S,[4,2,0,0]) + sage: D.rank() + 3 + sage: D.rank(True) + (3, {0: 3, 1: 0, 2: 1, 3: 0}) + sage: E = _[1] + sage: (D - E).rank() + -1 + + Riemann-Roch theorem:: + + sage: D.rank() - (S.canonical_divisor()-D).rank() == D.deg() + 1 - S.genus() + True + + Riemann-Roch theorem:: + + sage: D.rank() - (S.canonical_divisor()-D).rank() == D.deg() + 1 - S.genus() + True + sage: S = Sandpile({0:[1,1,1,2],1:[0,0,0,1,1,1,2,2],2:[2,2,1,1,0]},0) # multigraph with loops + sage: D = SandpileDivisor(S,[4,2,0]) + sage: D.rank(True) + (2, {0: 1, 1: 1, 2: 1}) + sage: S = Sandpile({0:[1,2], 1:[0,2,2], 2: [0,1]},0) # directed graph + sage: S.is_undirected() + False + sage: D = SandpileDivisor(S,[0,2,0]) + sage: D.effective_div() + [{0: 0, 1: 2, 2: 0}, {0: 2, 1: 0, 2: 0}] + sage: D.rank(True) + (0, {0: 0, 1: 0, 2: 1}) + sage: E = D.rank(True)[1] + sage: (D - E).effective_div() + [] + + .. NOTE:: + + The rank of a divisor `D` is -1 if `D` is not linearly equivalent to an effective divisor + (i.e., the dollar game represented by `D` is unwinnable). Otherwise, the rank of `D` is + the largest integer `r` such that `D - E` is linearly equivalent to an effective divisor + for all effective divisors `E` with `\deg(E) = r`. + """ + if with_witness: + return (self._rank, deepcopy(self._rank_witness)) + else: + return self._rank + + def _set_r_of_D(self, verbose=False): + r""" + Computes `r(D)` and an effective divisor `F` such that `|D - F|` is + empty. + + INPUT: + + ``verbose`` -- (default: ``False``) boolean EXAMPLES:: - sage: S = sandlib('generic') + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2 sage: D._set_r_of_D() # optional - 4ti2 """ @@ -4298,12 +5662,13 @@ def _set_r_of_D(self, verbose=False): def r_of_D(self, verbose=False): r""" - Returns ``r(D)`` and, if ``verbose`` is ``True``, an effective divisor - ``F`` such that ``|D - F|`` is empty. + The rank of the divisor (deprecated: use ``rank``, instead). Returns + `r(D)` and, if ``verbose`` is ``True``, an effective divisor `F` such + that `|D - F|` is empty. INPUT: - ``verbose`` (optional) - boolean + ``verbose`` -- (default: ``False``) boolean OUTPUT: @@ -4311,7 +5676,13 @@ def r_of_D(self, verbose=False): EXAMPLES:: - sage: S = sandlib('generic') + sage: S = Sandpile({0: {}, + ....: 1: {0: 1, 3: 1, 4: 1}, + ....: 2: {0: 1, 3: 1, 5: 1}, + ....: 3: {2: 1, 5: 1}, + ....: 4: {1: 1, 3: 1}, + ....: 5: {2: 1, 3: 1}} + ....: ) sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2 sage: E = D.r_of_D(True) # optional - 4ti2 sage: E # optional - 4ti2 @@ -4324,55 +5695,244 @@ def r_of_D(self, verbose=False): sage: SandpileDivisor(S, [0,0,0,0,0,-4]).r_of_D(True) # optional - 4ti2 (-1, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: -4}) """ + deprecation(18618,'D.r_of_D() will be removed soon. Please use ``D.rank()`` instead.') if verbose: return self._r_of_D else: return self._r_of_D[0] - def support(self): + def weierstrass_rank_seq(self, v='sink'): r""" - List of keys of the nonzero values of the divisor. + The Weierstrass rank sequence at the given vertex. Computes the rank of + the divisor `D - nv` starting with `n=0` and ending when the rank is + `-1`. INPUT: - None + ``v`` -- (default: ``sink``) vertex OUTPUT: - list - support of the divisor + tuple of int + + EXAMPLES:: + + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: [K.weierstrass_rank_seq(v) for v in s.vertices()] + [(1, 0, -1), (1, 0, -1), (1, 0, -1), (1, 0, -1), (1, 0, 0, -1)] + """ + s = self.sandpile() + if v=='sink': + v = s.sink() + try: + seq = self._weierstrass_rank_seq[v] + except: + D = deepcopy(self) + verts = s.vertices() + Ei = s.zero_div() + Ei[verts.index(v)]=1 + Ei = SandpileDivisor(s,Ei) + r = D.rank() + seq = [r] + while r !=-1: + D = D - Ei + r = D.rank() + seq.append(r) + self._weierstrass_rank_seq[v] = seq + return tuple(seq) + + def weierstrass_gap_seq(self, v='sink', weight=True): + r""" + The Weierstrass gap sequence at the given vertex. If ``weight`` is + ``True``, then also compute the weight of each gap value. + + INPUT: + + - ``v`` -- (default: ``sink``) vertex + + - ``weight`` -- (default: ``True``) boolean + + OUTPUT: + list or (list of list) of integers EXAMPLES:: - sage: S = sandlib('generic') - sage: c = S.identity() - sage: c.values() - [2, 2, 1, 1, 0] - sage: c.support() - [1, 2, 3, 4] - sage: S.vertices() - [0, 1, 2, 3, 4, 5] + sage: s = sandpiles.Cycle(4) + sage: D = SandpileDivisor(s,[2,0,0,0]) + sage: [D.weierstrass_gap_seq(v,False) for v in s.vertices()] + [(1, 3), (1, 2), (1, 3), (1, 2)] + sage: [D.weierstrass_gap_seq(v) for v in s.vertices()] + [((1, 3), 1), ((1, 2), 0), ((1, 3), 1), ((1, 2), 0)] + sage: D.weierstrass_gap_seq() # gap sequence at sink vertex, 0 + ((1, 3), 1) + sage: D.weierstrass_rank_seq() # rank sequence at the sink vertex + (1, 0, 0, -1) + + .. NOTE:: + + The integer `k` is a Weierstrass gap for the divisor `D` at vertex `v` if the rank + of `D - (k-1)v` does not equal the rank of `D - kv`. Let `r` be the rank of `D` and + let `k_i` be the `i`-th gap at `v`. The Weierstrass weight of `v` for `D` is the + sum of `(k_i - i)` as `i` ranges from `1` to `r + 1`. It measure the difference + between the sequence `r, r - 1, ..., 0, -1, -1, ...` and the rank sequence + `\mathrm{rank}(D), \mathrm{rank}(D - v), \mathrm{rank}(D - 2v), \dots` """ - return [i for i in self.keys() if self[i] !=0] + L = self.weierstrass_rank_seq(v) + gaps = [i for i in range(1,len(L)) if L[i]!=L[i-1]] + gaps = tuple(gaps) + if weight: + return gaps, sum(gaps)-binomial(len(gaps)+1,2) + else: + return gaps - def _set_Dcomplex(self): + def is_weierstrass_pt(self, v='sink'): r""" - Computes the simplicial complex determined by the supports of the - linearly equivalent effective divisors. + Is the given vertex a Weierstrass point? + + INPUT: + + ``v`` -- (default: ``sink``) vertex + + OUTPUT: + + boolean + + EXAMPLES:: + + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: K.weierstrass_rank_seq() # sequence at the sink vertex, 0 + (1, 0, -1) + sage: K.is_weierstrass_pt() + False + sage: K.weierstrass_rank_seq(4) + (1, 0, 0, -1) + sage: K.is_weierstrass_pt(4) + True + + .. NOTE:: + + The vertex `v` is a (generalized) Weierstrass point for divisor `D` if the sequence of ranks `r(D - nv)` + for `n = 0, 1, 2, \dots` is not `r(D), r(D)-1, \dots, 0, -1, -1, \dots` + """ + return self.weierstrass_gap_seq(v)[1] > 0 + + def _set_weierstrass_pts(self): + r""" + Tuple of Weierstrass vertices. + + EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: D = SandpileDivisor(s, [2,1,0,0]) + sage: D._set_weierstrass_pts() + sage: '_weierstrass_pts' in D.__dict__ + True + """ + self._weierstrass_pts = tuple([v for v in self.sandpile().vertices() if self.is_weierstrass_pt(v)]) + + def weierstrass_pts(self, with_rank_seq=False): + r""" + The Weierstrass points (vertices). Optionally, return the corresponding rank sequences. + + INPUT: + + ``with_rank_seq`` -- (default: ``False``) boolean + + OUTPUT: + + tuple of vertices or list of (vertex, rank sequence) + + EXAMPLES:: + + sage: s = sandpiles.House() + sage: K = s.canonical_divisor() + sage: K.weierstrass_pts() + (4,) + sage: K.weierstrass_pts(True) + [(4, (1, 0, 0, -1))] + + .. NOTE:: + + The vertex `v` is a (generalized) Weierstrass point for divisor `D` if the sequence of ranks `r(D - nv)` + for `n = 0, 1, 2, \dots`` is not `r(D), r(D)-1, \dots, 0, -1, -1, \dots` + """ + if with_rank_seq: + rks = [self.weierstrass_rank_seq(v) for v in self._weierstrass_pts] + return zip(self._weierstrass_pts, rks) + return self._weierstrass_pts + + def weierstrass_div(self, verbose=True): + r""" + The Weierstrass divisor. Its value at a vertex is the weight of that + vertex as a Weierstrass point. (See + ``SandpileDivisor.weierstrass_gap_seq``.) INPUT: - None + ``verbose`` -- (default: ``True``) boolean + + OUTPUT: + + SandpileDivisor + + EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: D = SandpileDivisor(s,[4,2,1,0]) + sage: [D.weierstrass_rank_seq(v) for v in s] + [(5, 4, 3, 2, 1, 0, 0, -1), + (5, 4, 3, 2, 1, 0, -1), + (5, 4, 3, 2, 1, 0, 0, 0, -1), + (5, 4, 3, 2, 1, 0, 0, -1)] + sage: D.weierstrass_div() + {0: 1, 1: 0, 2: 2, 3: 1} + sage: k5 = sandpiles.Complete(5) + sage: K = k5.canonical_divisor() + sage: K.weierstrass_div() + {0: 9, 1: 9, 2: 9, 3: 9, 4: 9} + """ + s = self.sandpile() + D = [self.weierstrass_gap_seq(v, True)[1] for v in s.vertices()] + D = SandpileDivisor(s, D) + if verbose: + return D + else: + return D.values() + + def support(self): + r""" + List of vertices at which the divisor is nonzero. OUTPUT: - None + list representing the support of the divisor + + EXAMPLES:: + + sage: S = sandpiles.Cycle(4) + sage: D = SandpileDivisor(S, [0,0,1,1]) + sage: D.support() + [2, 3] + sage: S.vertices() + [0, 1, 2, 3] + """ + return [i for i in self.keys() if self[i] !=0] + + def _set_Dcomplex(self): + r""" + Computes the simplicial complex determined by the supports of the + linearly equivalent effective divisors. EXAMPLES:: - sage: S = sandlib('generic') - sage: D = SandpileDivisor(S, [0,1,2,0,0,1]) # optional - 4ti2 - sage: D._set_Dcomplex() # optional - 4ti2 + sage: S = sandpiles.Complete(4) + sage: D = SandpileDivisor(S, [0,0,1,1]) + sage: D._set_Dcomplex() + sage: '_Dcomplex' in D.__dict__ + True """ simp = [] for E in self.effective_div(): @@ -4399,12 +5959,7 @@ def _set_Dcomplex(self): def Dcomplex(self): r""" - The simplicial complex determined by the supports of the - linearly equivalent effective divisors. - - INPUT: - - None + The support-complex. (See NOTE.) OUTPUT: @@ -4412,25 +5967,25 @@ def Dcomplex(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: p = SandpileDivisor(S, [0,1,2,0,0,1]).Dcomplex() # optional - 4ti2 - sage: p.homology() # optional - 4ti2 - {0: 0, 1: Z x Z, 2: 0, 3: 0} - sage: p.f_vector() # optional - 4ti2 - [1, 6, 15, 9, 1] - sage: p.betti() # optional - 4ti2 - {0: 1, 1: 2, 2: 0, 3: 0} + sage: S = sandpiles.House() + sage: p = SandpileDivisor(S, [1,2,1,0,0]).Dcomplex() + sage: p.homology() + {0: 0, 1: Z x Z, 2: 0} + sage: p.f_vector() + [1, 5, 10, 4] + sage: p.betti() + {0: 1, 1: 2, 2: 0} + + .. NOTE:: + + The "support-complex" is the simplicial complex determined by the + supports of the linearly equivalent effective divisors. """ return self._Dcomplex def betti(self): r""" - The Betti numbers for the simplicial complex associated with - the divisor. - - INPUT: - - None + The Betti numbers for the support-complex. (See NOTE.) OUTPUT: @@ -4438,20 +5993,25 @@ def betti(self): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(3), 0) + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [2,0,1]) - sage: D.betti() # optional - 4ti2 + sage: D.betti() {0: 1, 1: 1} + + .. NOTE:: + + The "support-complex" is the simplicial complex determined by the + supports of the linearly equivalent effective divisors. """ return self.Dcomplex().betti() - def add_random(self): + def add_random(self, distrib=None): r""" Add one grain of sand to a random vertex. INPUT: - None + ``distrib`` -- (optional) list of nonnegative numbers representing a probability distribution on the vertices OUTPUT: @@ -4459,24 +6019,37 @@ def add_random(self): EXAMPLES:: - sage: S = sandlib('generic') - sage: S.zero_div().add_random() #random - {0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0} + sage: s = sandpiles.Complete(4) + sage: D = s.zero_div() + sage: D.add_random() # random + {0: 0, 1: 0, 2: 1, 3: 0} + sage: D.add_random([0.1,0.1,0.1,0.7]) # random + {0: 0, 1: 0, 2: 0, 3: 1} + + .. WARNING:: + + If ``distrib`` is not ``None``, the user is responsible for assuring the sum of its entries is 1. """ - D = dict(self) - C = CombinatorialClass() - C.list = lambda: self.keys() - D[C.random_element()] += 1 - return SandpileDivisor(self._sandpile,D) + D = deepcopy(self) + S = self.sandpile() + V = S.vertices() + if distrib==None: # default = uniform distribution + n = S.num_verts() + distrib = [1/n]*n + X = GeneralDiscreteDistribution(distrib) + i = X.get_random_element() + D[V[i]] += 1 + return D def is_symmetric(self, orbits): r""" - This function checks if the values of the divisor are constant - over the vertices in each sublist of ``orbits``. + Is the divisor symmetric? Return ``True`` if the values of the + configuration are constant over the vertices in each sublist of + ``orbits``. INPUT: - - ``orbits`` - list of lists of vertices + ``orbits`` -- list of lists of vertices OUTPUT: @@ -4484,15 +6057,15 @@ def is_symmetric(self, orbits): EXAMPLES:: - sage: S = sandlib('kite') + sage: S = sandpiles.House() sage: S.dict() - {0: {}, - 1: {0: 1, 2: 1, 3: 1}, - 2: {1: 1, 3: 1, 4: 1}, + {0: {1: 1, 2: 1}, + 1: {0: 1, 3: 1}, + 2: {0: 1, 3: 1, 4: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {2: 1, 3: 1}} - sage: D = SandpileDivisor(S, [2,1, 2, 2, 3]) - sage: D.is_symmetric([[0,2,3]]) + sage: D = SandpileDivisor(S, [0,0,1,1,3]) + sage: D.is_symmetric([[2,3], [4]]) True """ for x in orbits: @@ -4502,23 +6075,17 @@ def is_symmetric(self, orbits): def _set_life(self): r""" - Will the sequence of divisors ``D_i`` where ``D_{i+1}`` is obtained from - ``D_i`` by firing all unstable vertices of ``D_i`` stabilize? If so, + Will the sequence of divisors `D_i` where `D_{i+1}` is obtained from + `D_i` by firing all unstable vertices of `D_i` stabilize? If so, save the resulting cycle, otherwise save ``[]``. - INPUT: - - None - - OUTPUT: - - None - EXAMPLES:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2}) sage: D._set_life() + sage: '_life' in D.__dict__ + True """ oldD = deepcopy(self) result = [oldD] @@ -4536,12 +6103,13 @@ def _set_life(self): def is_alive(self, cycle=False): r""" - Will the divisor stabilize under repeated firings of all unstable - vertices? Optionally returns the resulting cycle. + Is the divisor stabilizable? In other words, will the divisor stabilize + under repeated firings of all unstable vertices? Optionally returns the + resulting cycle. INPUT: - ``cycle`` (optional) - boolean + ``cycle`` -- (default: ``False``) boolean OUTPUT: @@ -4549,7 +6117,7 @@ def is_alive(self, cycle=False): EXAMPLES:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2}) sage: D.is_alive() True @@ -4561,23 +6129,68 @@ def is_alive(self, cycle=False): else: return self._life != [] - def show(self,heights=True,directed=None,**kwds): + def _set_stabilize(self): + r""" + The stabilization of the divisor. If not stabilizable, return an error. + + EXAMPLES:: + + sage: s = sandpiles.Diamond() + sage: D = SandpileDivisor(s, [2,1,0,0]) + sage: D._set_stabilize() + sage: '_stabilize' in D.__dict__ + True + """ + if self.is_alive(): + raise RuntimeError('Divisor is not stabilizable.') + else: + firing_vector = self._sandpile.zero_div() + E = deepcopy(self) + unstable = E.unstable() + while unstable!=[]: + E = E.fire_unstable() + for v in unstable: + firing_vector[v] += 1 + unstable = E.unstable() + self._stabilize = [E, firing_vector] + + def stabilize(self, with_firing_vector=False): + r""" + The stabilization of the divisor. If not stabilizable, return an error. + + INPUT: + + ``with_firing_vector`` -- (default: ``False``) boolean + + EXAMPLES:: + + sage: s = sandpiles.Complete(4) + sage: D = SandpileDivisor(s,[0,3,0,0]) + sage: D.stabilize() + {0: 1, 1: 0, 2: 1, 3: 1} + sage: D.stabilize(with_firing_vector=True) + [{0: 1, 1: 0, 2: 1, 3: 1}, {0: 0, 1: 1, 2: 0, 3: 0}] + """ + if with_firing_vector: + return self._stabilize + else: + return self._stabilize[0] + + def show(self, heights=True, directed=None, **kwds): r""" Show the divisor. INPUT: - - ``heights`` - whether to label each vertex with the amount of sand - - ``kwds`` - arguments passed to the show method for Graph - - ``directed`` - whether to draw directed edges + - ``heights`` -- (default: ``True``) whether to label each vertex with the amount of sand - OUTPUT: + - ``directed`` -- (optional) whether to draw directed edges - None + - ``kwds`` -- (optional) arguments passed to the show method for Graph EXAMPLES:: - sage: S = sandlib('genus2') + sage: S = sandpiles.Diamond() sage: D = SandpileDivisor(S,[1,-2,0,2]) sage: D.show(graph_border=True,vertex_size=700,directed=False) """ @@ -4609,7 +6222,7 @@ def sandlib(selector=None): INPUT: - ``selector`` - identifier or None + ``selector`` -- (optional) identifier or None OUTPUT: @@ -4618,6 +6231,8 @@ def sandlib(selector=None): EXAMPLES:: sage: sandlib() + doctest:...: DeprecationWarning: sandlib() will soon be removed. Use sandpile() instead. + See http://trac.sagemath.org/18618 for details. Sandpiles in the sandlib: kite : generic undirected graphs with 5 vertices @@ -4627,8 +6242,6 @@ def sandlib(selector=None): riemann-roch1 : directed graph with postulation 9 and 3 maximal weight superstables riemann-roch2 : directed graph with a superstable not majorized by a maximal superstable gor : Gorenstein but not a complete intersection - - sage: S = sandlib('gor') sage: S.resolution() 'R^1 <-- R^5 <-- R^5 <-- R^1' @@ -4705,7 +6318,7 @@ def complete_sandpile(n): INPUT: - ``n`` - positive integer + ``n`` -- positive integer OUTPUT: @@ -4713,19 +6326,20 @@ def complete_sandpile(n): EXAMPLES:: - sage: K = complete_sandpile(5) + sage: K = sandpiles.Complete(5) sage: K.betti(verbose=False) [1, 15, 50, 60, 24] """ + deprecation(18618,'May 25, 2015: Replaced by sandpiles.Complete.') return Sandpile(graphs.CompleteGraph(n), 0) -def grid_sandpile(m,n): - """ - The mxn grid sandpile. Each nonsink vertex has degree 4. +def grid_sandpile(m, n): + r""" + The `m\times n` grid sandpile. Each nonsink vertex has degree 4. INPUT: - ``m``, ``n`` - positive integers + ``m``, ``n`` -- positive integers OUTPUT: @@ -4734,6 +6348,10 @@ def grid_sandpile(m,n): EXAMPLES:: sage: G = grid_sandpile(3,4) + doctest:...: DeprecationWarning: grid_sandpile() will soon be removed. Use sandpile.Grid() instead. + See http://trac.sagemath.org/18618 for details. + doctest:...: DeprecationWarning: May 25, 2015: Replaced by sandpiles.Grid. + See http://trac.sagemath.org/18618 for details. sage: G.dict() {'sink': {}, (1, 1): {'sink': 2, (1, 2): 1, (2, 1): 1}, @@ -4753,6 +6371,7 @@ def grid_sandpile(m,n): sage: G.invariant_factors() [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1380027] """ + deprecation(18618,'May 25, 2015: Replaced by sandpiles.Grid.') g = {} # corners first g[(1,1)] = {(1,2):1, (2,1):1, 'sink':2} @@ -4786,7 +6405,7 @@ def triangle_sandpile(n): INPUT: - ``n`` - int + ``n`` -- integer OUTPUT: @@ -4795,6 +6414,9 @@ def triangle_sandpile(n): EXAMPLES:: sage: T = triangle_sandpile(5) + doctest:...: DeprecationWarning: + Importing triangle_sandpile from here is deprecated. If you need to use it, please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. sage: T.group_order() 135418115000 """ @@ -4830,7 +6452,7 @@ def aztec_sandpile(n): INPUT: - ``n`` - integer + ``n`` -- integer OUTPUT: @@ -4839,6 +6461,9 @@ def aztec_sandpile(n): EXAMPLES:: sage: aztec_sandpile(2) + doctest:...: DeprecationWarning: + Importing aztec_sandpile from here is deprecated. If you need to use it, please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. {'sink': {(-3/2, -1/2): 2, (-3/2, 1/2): 2, (-1/2, -3/2): 2, @@ -4865,10 +6490,10 @@ def aztec_sandpile(n): sage: Sandpile(aztec_sandpile(2),'sink').group_order() 4542720 - NOTES: + .. NOTE:: - This is the aztec diamond graph with a sink vertex added. Boundary - vertices have edges to the sink so that each vertex has degree 4. + This is the aztec diamond graph with a sink vertex added. Boundary + vertices have edges to the sink so that each vertex has degree 4. """ aztec_sandpile = {} half = QQ(1)/2 @@ -4910,10 +6535,13 @@ def random_digraph(num_verts, p=0.5, directed=True, weight_max=1): INPUT: - - ``num_verts`` - number of vertices - - ``p`` - probability edges occur - - ``directed`` - True if directed - - ``weight_max`` - integer maximum for random weights + - ``num_verts`` -- number of vertices + + - ``p`` -- (default: 0.5) probability edges occur + + - ``directed`` -- (default: ``True``) if directed + + - ``weight_max`` -- (default: 1) integer maximum for random weights OUTPUT: @@ -4922,6 +6550,9 @@ def random_digraph(num_verts, p=0.5, directed=True, weight_max=1): EXAMPLES:: sage: g = random_digraph(6,0.2,True,3) + doctest:...: DeprecationWarning: random_digraph will be removed soon. Use any of the Random* methods + from graphs() and from digraphs() instead. + See http://trac.sagemath.org/18618 for details. sage: S = Sandpile(g,0) sage: S.show(edge_labels = True) @@ -4933,7 +6564,7 @@ def random_digraph(num_verts, p=0.5, directed=True, weight_max=1): sage: random_digraph(5) Digraph on 5 vertices """ - + deprecation(18618,'random_digraph will be removed soon. Use any of the Random* methods from graphs() and from digraphs() instead.') a = digraphs.RandomDirectedGN(num_verts) b = graphs.RandomGNP(num_verts,p) a.add_edges(b.edges()) @@ -4960,9 +6591,11 @@ def random_DAG(num_verts, p=0.5, weight_max=1): INPUT: - - ``num_verts`` - positive integer - - ``p`` - real number such that `0 < p \leq 1` - - ``weight_max`` - positive integer + - ``num_verts`` -- positive integer + + - ``p`` -- (default: 0,5) real number such that `0 < p \leq 1` + + - ``weight_max`` -- (default: 1) positive integer OUTPUT: @@ -4970,7 +6603,7 @@ def random_DAG(num_verts, p=0.5, weight_max=1): EXAMPLES:: - sage: d = DiGraph(random_DAG(5, .5));d + sage: d = DiGraph(random_DAG(5, .5)); d Digraph on 5 vertices TESTS: @@ -5006,14 +6639,14 @@ def random_DAG(num_verts, p=0.5, weight_max=1): g[i] = out_edges return g -def random_tree(n,d): +def random_tree(n, d): r""" - A random undirected tree with ``n`` nodes, no node having - degree higher than ``d``. + A random undirected tree with `n` nodes, no node having + degree higher than `d`. INPUT: - ``n``, ``d`` - integers + ``n``, ``d`` -- integers OUTPUT: @@ -5022,11 +6655,14 @@ def random_tree(n,d): EXAMPLES:: sage: T = random_tree(15,3) + doctest:...: DeprecationWarning: random_tree will be removed soon. Use graphs.RandomTree() instead. + See http://trac.sagemath.org/18618 for details. sage: T.show() sage: S = Sandpile(T,0) sage: U = S.reorder_vertices() sage: U.show() """ + deprecation(18618,'random_tree will be removed soon. Use graphs.RandomTree() instead.') g = Graph() # active vertices active = [0] @@ -5046,14 +6682,15 @@ def random_tree(n,d): next_vertex+=1 return g -def glue_graphs(g,h,glue_g,glue_h): +def glue_graphs(g, h, glue_g, glue_h): r""" Glue two graphs together. INPUT: - - ``g``, ``h`` - dictionaries for directed multigraphs - - ``glue_h``, ``glue_g`` - dictionaries for a vertex + - ``g``, ``h`` -- dictionaries for directed multigraphs + + - ``glue_h``, ``glue_g`` -- dictionaries for a vertex OUTPUT: @@ -5067,6 +6704,10 @@ def glue_graphs(g,h,glue_g,glue_h): sage: glue_x = {1: 1, 3: 2} sage: glue_y = {0: 1, 1: 2, 3: 1} sage: z = glue_graphs(x,y,glue_x,glue_y) + doctest:...: DeprecationWarning: + Importing glue_graphs from here is deprecated. If you need to use it, + please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. sage: z {0: {}, 'x0': {0: 1, 'x1': 1, 'x3': 2, 'y1': 2, 'y3': 1}, @@ -5082,17 +6723,17 @@ def glue_graphs(g,h,glue_g,glue_h): sage: S.resolution() 'R^1 <-- R^7 <-- R^21 <-- R^35 <-- R^35 <-- R^21 <-- R^7 <-- R^1' - NOTES: + .. NOTE:: - This method makes a dictionary for a graph by combining those for - ``g`` and ``h``. The sink of ``g`` is replaced by a vertex that - is connected to the vertices of ``g`` as specified by ``glue_g`` - the vertices of ``h`` as specified in ``glue_h``. The sink of the glued - graph is `0`. + This method makes a dictionary for a graph by combining those for + `g` and `h`. The sink of `g` is replaced by a vertex that + is connected to the vertices of `g` as specified by ``glue_g`` + the vertices of `h` as specified in ``glue_h``. The sink of the glued + graph is `0`. - Both ``glue_g`` and ``glue_h`` are dictionaries with entries of the form - ``v:w`` where ``v`` is the vertex to be connected to and ``w`` is the weight - of the connecting edge. + Both ``glue_g`` and ``glue_h`` are dictionaries with entries of the form + ``v:w`` where ``v`` is the vertex to be connected to and ``w`` is the weight + of the connecting edge. """ # first find the sinks of g and h for i in g: @@ -5131,16 +6772,16 @@ def glue_graphs(g,h,glue_g,glue_h): k['x'+str(g_sink)] = new_edges return k -def firing_graph(S,eff): +def firing_graph(S, eff): r""" - Creates a digraph with divisors as vertices and edges between two - divisors ``D`` and ``E`` if firing a single vertex in ``D`` gives - ``E``. + Creates a digraph with divisors as vertices and edges between two divisors + `D` and `E` if firing a single vertex in `D` gives `E`. INPUT: - ``S`` - sandpile - ``eff`` - list of divisors + ``S`` -- Sandpile + + ``eff`` -- list of divisors OUTPUT: @@ -5148,10 +6789,10 @@ def firing_graph(S,eff): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(6),0) + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2 + sage: eff = D.effective_div() + sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) """ g = DiGraph() g.add_vertices(range(len(eff))) @@ -5168,14 +6809,14 @@ def firing_graph(S,eff): def parallel_firing_graph(S, eff): r""" - Creates a digraph with divisors as vertices and edges between two - divisors ``D`` and ``E`` if firing all unstable vertices in ``D`` gives - ``E``. + Creates a digraph with divisors as vertices and edges between two divisors + `D` and `E` if firing all unstable vertices in `D` gives `E`. INPUT: - ``S`` - Sandpile - ``eff`` - list of divisors + ``S`` -- Sandpile + + ``eff`` -- list of divisors OUTPUT: @@ -5183,10 +6824,10 @@ def parallel_firing_graph(S, eff): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(6),0) + sage: S = sandpiles.Cycle(6) sage: D = SandpileDivisor(S, [1,1,1,1,2,0]) - sage: eff = D.effective_div() # optional - 4ti2 - sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2 + sage: eff = D.effective_div() + sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) """ g = DiGraph() g.add_vertices(range(len(eff))) @@ -5205,13 +6846,14 @@ def parallel_firing_graph(S, eff): def admissible_partitions(S, k): r""" - The partitions of the vertices of ``S`` into ``k`` parts, - each of which is connected. + The partitions of the vertices of `S` into `k` parts, each of which is + connected. INPUT: - ``S`` - Sandpile - ``k`` - integer + ``S`` -- Sandpile + + ``k`` -- integer OUTPUT: @@ -5219,8 +6861,11 @@ def admissible_partitions(S, k): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: P = [admissible_partitions(S, i) for i in [2,3,4]] + doctest:...: DeprecationWarning: + Importing admissible_partitions from here is deprecated. If you need to use it, please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. sage: P [[{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, @@ -5235,6 +6880,9 @@ def admissible_partitions(S, k): [{{0}, {1}, {2}, {3}}]] sage: for p in P: ... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p]) + doctest:...: DeprecationWarning: + Importing partition_sandpile from here is deprecated. If you need to use it, please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. 6 8 3 @@ -5257,16 +6905,17 @@ def admissible_partitions(S, k): result.append(p) return result -def partition_sandpile(S,p): +def partition_sandpile(S, p): r""" - Each set of vertices in ``p`` is regarded as a single vertex, with and edge - between ``A`` and ``B`` if some element of ``A`` is connected by an edge - to some element of ``B`` in ``S``. + Each set of vertices in `p` is regarded as a single vertex, with and edge + between `A` and `B` if some element of `A` is connected by an edge to some + element of `B` in `S`. INPUT: - ``S`` - Sandpile - ``p`` - partition of the vertices of ``S`` + ``S`` -- Sandpile + + ``p`` -- partition of the vertices of ``S`` OUTPUT: @@ -5274,7 +6923,7 @@ def partition_sandpile(S,p): EXAMPLES:: - sage: S = Sandpile(graphs.CycleGraph(4), 0) + sage: S = sandpiles.Cycle(4) sage: P = [admissible_partitions(S, i) for i in [2,3,4]] sage: for p in P: ... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p]) @@ -5302,15 +6951,16 @@ def partition_sandpile(S,p): if S.sink() in i: return Sandpile(g,i) -def firing_vector(S,D,E): +def firing_vector(S, D, E): r""" - If ``D`` and ``E`` are linearly equivalent divisors, find the firing vector - taking ``D`` to ``E``. + If `D` and `E` are linearly equivalent divisors, find the firing vector + taking `D` to `E`. INPUT: - - ``S`` - Sandpile - - ``D``, ``E`` - tuples (representing linearly equivalent divisors) + - ``S`` -- Sandpile + + - ``D``, ``E`` -- tuples (representing linearly equivalent divisors) OUTPUT: @@ -5318,10 +6968,14 @@ def firing_vector(S,D,E): EXAMPLES:: - sage: S = complete_sandpile(4) + sage: S = sandpiles.Complete(4) sage: D = SandpileDivisor(S, {0: 0, 1: 0, 2: 8, 3: 0}) sage: E = SandpileDivisor(S, {0: 2, 1: 2, 2: 2, 3: 2}) sage: v = firing_vector(S, D, E) + doctest:...: DeprecationWarning: firing_vector() will soon be removed. Use SandpileDivisor.is_linearly_equivalent() instead. + See http://trac.sagemath.org/18618 for details. + doctest:...: DeprecationWarning: May 25, 2015: Replaced by SandpileDivisor.is_linearly_equivalent. + See http://trac.sagemath.org/18618 for details. sage: v (0, 0, 2, 0) @@ -5332,6 +6986,7 @@ def firing_vector(S,D,E): sage: firing_vector(S, D, S.zero_div()) Error. Are the divisors linearly equivalent? """ + deprecation(18618,'May 25, 2015: Replaced by SandpileDivisor.is_linearly_equivalent.') try: v = vector(D.values()) w = vector(E.values()) @@ -5340,14 +6995,15 @@ def firing_vector(S,D,E): print "Error. Are the divisors linearly equivalent?" return -def min_cycles(G,v): +def min_cycles(G, v): r""" - Minimal length cycles in the digraph ``G`` starting at vertex ``v``. + Minimal length cycles in the digraph `G` starting at vertex `v`. INPUT: - ``G`` - DiGraph - ``v`` - vertex of ``G`` + - ``G`` -- DiGraph + + - ``v`` -- vertex of ``G`` OUTPUT: @@ -5357,6 +7013,9 @@ def min_cycles(G,v): sage: T = sandlib('gor') sage: [min_cycles(T, i) for i in T.vertices()] + doctest:...: DeprecationWarning: + Importing min_cycles from here is deprecated. If you need to use it, please import it directly from sage.sandpiles.sandpile + See http://trac.sagemath.org/18618 for details. [[], [[1, 3]], [[2, 3, 1], [2, 3]], [[3, 1], [3, 2]]] """ pr = G.neighbors_in(v) @@ -5365,16 +7024,16 @@ def min_cycles(G,v): def wilmes_algorithm(M): r""" - Computes an integer matrix ``L`` with the same integer row span as ``M`` - and such that ``L`` is the reduced laplacian of a directed multigraph. + Computes an integer matrix `L` with the same integer row span as `M` and + such that `L` is the reduced Laplacian of a directed multigraph. INPUT: - ``M`` - square integer matrix of full rank + ``M`` -- square integer matrix of full rank OUTPUT: - ``L`` - integer matrix + integer matrix (``L``) EXAMPLES:: @@ -5385,9 +7044,11 @@ def wilmes_algorithm(M): [ 0 -1 1650 -1649] [ 0 0 -1658 1658] - NOTES: + REFERENCES: - The algorithm is due to John Wilmes. + .. [Primer2013] Perlman, Perkinson, and Wilmes. Primer for the algebraic + geometry of sandpiles. Tropical and Non-Archimedean Geometry, Contemp. + Math., 605, Amer. Math. Soc., Providence, RI, 2013. """ # find the gcd of the row-sums, and perform the corresponding row # operations on M @@ -5415,10 +7076,3 @@ def wilmes_algorithm(M): return L else: raise UserWarning('matrix not of full rank') - -######### Notes ################ -""" -* pickling sandpiles? -* Does 'sat' return a minimal generating set -""" - diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index d21ea11103f..f142a3b979f 100644 --- a/src/sage/schemes/affine/affine_morphism.py +++ b/src/sage/schemes/affine/affine_morphism.py @@ -175,9 +175,9 @@ def __call__(self, x, check=True): try: x = self.domain()(x) except (TypeError, NotImplementedError): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) elif self.domain()!=x.codomain(): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) # Passes the array of args to _fast_eval P = self._fast_eval(x._coords) diff --git a/src/sage/schemes/elliptic_curves/ec_database.py b/src/sage/schemes/elliptic_curves/ec_database.py index ded060f2a6d..9fc29f4047a 100644 --- a/src/sage/schemes/elliptic_curves/ec_database.py +++ b/src/sage/schemes/elliptic_curves/ec_database.py @@ -6,27 +6,60 @@ +------+------------------+--------------------+ | Rank | Number of curves | Maximal conductor | +======+==================+====================+ -| 0 | 30427 | 9999 | +| 0 | 30427 | 9999 | +------+------------------+--------------------+ -| 1 | 31871 | 9999 | +| 1 | 31871 | 9999 | +------+------------------+--------------------+ -| 2 | 2388 | 9999 | +| 2 | 2388 | 9999 | +------+------------------+--------------------+ -| 3 | 836 | 119888 | +| 3 | 836 | 119888 | +------+------------------+--------------------+ -| 4 | 1 | 234446 | +| 4 | 10 | 1175648 | +------+------------------+--------------------+ -| 5 | 1 | 19047851 | +| 5 | 5 | 37396136 | +------+------------------+--------------------+ -| 6 | 1 | 5187563742 | +| 6 | 5 | 6663562874 | +------+------------------+--------------------+ -| 7 | 1 | 382623908456 | +| 7 | 5 | 896913586322 | +------+------------------+--------------------+ -| 8 | 1 | 457532830151317 | +| 8 | 6 | 457532830151317 | +------+------------------+--------------------+ +| 9 | 7 | ~9.612839e+21 | ++------+------------------+--------------------+ +| 10 | 6 | ~1.971057e+21 | ++------+------------------+--------------------+ +| 11 | 6 | ~1.803406e+24 | ++------+------------------+--------------------+ +| 12 | 1 | ~2.696017e+29 | ++------+------------------+--------------------+ +| 14 | 1 | ~3.627533e+37 | ++------+------------------+--------------------+ +| 15 | 1 | ~1.640078e+56 | ++------+------------------+--------------------+ +| 17 | 1 | ~2.750021e+56 | ++------+------------------+--------------------+ +| 19 | 1 | ~1.373776e+65 | ++------+------------------+--------------------+ +| 20 | 1 | ~7.381324e+73 | ++------+------------------+--------------------+ +| 21 | 1 | ~2.611208e+85 | ++------+------------------+--------------------+ +| 22 | 1 | ~2.272064e+79 | ++------+------------------+--------------------+ +| 23 | 1 | ~1.139647e+89 | ++------+------------------+--------------------+ +| 24 | 1 | ~3.257638e+95 | ++------+------------------+--------------------+ +| 28 | 1 | ~3.455601e+141 | ++------+------------------+--------------------+ + +Note that lists for r>=4 are not exhaustive; there may well be curves +of the given rank with conductor less than the listed maximal conductor, +which are not included in the tables. -AUTHOR: +AUTHORS: - William Stein (2007-10-07): initial version +- Simon Spicer (2014-10-24): Added examples of more high-rank curves See also the functions cremona_curves() and cremona_optimal_curves() which enable easy looping through the Cremona elliptic curve database. @@ -80,7 +113,7 @@ def rank(self, rank, tors=0, n=10, labels=False): sage: e = elliptic_curves.rank(7)[0]; e.ainvs(), e.conductor() ((0, 0, 0, -10012, 346900), 382623908456) sage: e = elliptic_curves.rank(8)[0]; e.ainvs(), e.conductor() - ((0, 0, 1, -23737, 960366), 457532830151317) + ((1, -1, 0, -106384, 13075804), 249649566346838) """ from sage.env import SAGE_SHARE diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 8130c2b3209..11ff4483139 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -861,7 +861,7 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True): """ try: return EllipticCurveIsogeny(self, kernel, codomain, degree, model, check=check) - except AttributeError, e: + except AttributeError as e: raise RuntimeError("Unable to contruct isogeny: %s" % e) diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 7455b222d1d..d82a74e5ea4 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -31,6 +31,8 @@ - Simon Spicer (2013-03): Added code for modular degrees and congruence numbers of higher level +- Simon Spicer (2014-08): Added new analytic rank computatation functionality + """ ############################################################################## @@ -66,6 +68,8 @@ from sage.modular.modsym.modsym import ModularSymbols +from sage.lfunctions.zero_sums import LFunctionZeroSum_EllipticCurve + import sage.modular.modform.constructor import sage.modular.modform.element import sage.libs.mwrank.all as mwrank @@ -1332,38 +1336,47 @@ def q_eigenform(self, prec): def analytic_rank(self, algorithm="pari", leading_coefficient=False): r""" Return an integer that is *probably* the analytic rank of this - elliptic curve. If leading_coefficient is ``True`` (only implemented - for PARI), return a tuple `(rank, lead)` where `lead` is the value of - the first non-zero derivative of the L-function of the elliptic - curve. + elliptic curve. INPUT: - - algorithm - - - - 'pari' (default) - use the PARI library function. - - - 'sympow' -use Watkins's program sympow + - ``algorithm`` -- (default: 'pari'), String - - 'rubinstein' - use Rubinstein's L-function C++ program lcalc. - - - 'magma' - use MAGMA - - - 'all' - compute with all other free algorithms, check that + - ``'pari'`` -- use the PARI library function. + - ``'sympow'`` -- use Watkins's program sympow + - ``'rubinstein'`` -- use Rubinstein's L-function C++ program lcalc. + - ``'magma'`` -- use MAGMA + - ``'zero_sum'`` -- Use the rank bounding zero sum method implemented + in self.analytic_rank_upper_bound() + - ``'all'`` -- compute with PARI, sympow and lcalc, check that the answers agree, and return the common answer. + - ``leading_coefficient`` -- (default: False) Boolean; if set to + True, return a tuple `(rank, lead)` where `lead` is the value of + the first non-zero derivative of the L-function of the elliptic + curve. Only implemented for algorithm='pari'. + .. note:: If the curve is loaded from the large Cremona database, then the modular degree is taken from the database. - Of the three above, probably Rubinstein's is the most - efficient (in some limited testing I've done). + Of the first three algorithms above, probably Rubinstein's is the + most efficient (in some limited testing done). The zero sum method + is often *much* faster, but can return a value which is strictly + larger than the analytic rank. For curves with conductor <=10^9 + using default parameters, testing indicates that for 99.75% of + curves the returned rank bound is the true rank. + + .. note:: + + If you use set_verbose(1), extra information about the computation + will be printed when algorithm='zero_sum'. .. note:: - It is an open problem to *prove* that *any* particular - elliptic curve has analytic rank `\geq 4`. + It is an open problem to *prove* that *any* particular + elliptic curve has analytic rank `\geq 4`. EXAMPLES:: @@ -1376,12 +1389,14 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): 2 sage: E.analytic_rank(algorithm='magma') # optional - magma 2 + sage: E.analytic_rank(algorithm='zero_sum') + 2 sage: E.analytic_rank(algorithm='all') 2 With the optional parameter leading_coefficient set to ``True``, a tuple of both the analytic rank and the leading term of the - L-series at `s = 1` is returned:: + L-series at `s = 1` is returned. This only works for algorithm=='pari':: sage: EllipticCurve([0,-1,1,-10,-20]).analytic_rank(leading_coefficient=True) (0, 0.25384186085591068...) @@ -1431,6 +1446,11 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): raise NotImplementedError("Cannot compute leading coefficient using magma") from sage.interfaces.all import magma return rings.Integer(magma(self).AnalyticRank()) + elif algorithm == 'zero_sum': + if leading_coefficient: + s = "Cannot compute leading coefficient using the zero sum method" + raise NotImplementedError(s) + return self.analytic_rank_upper_bound() elif algorithm == 'all': if leading_coefficient: S = set([self.analytic_rank('pari', True)]) @@ -1443,6 +1463,218 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): else: raise ValueError("algorithm %s not defined"%algorithm) + def analytic_rank_upper_bound(self, + max_Delta=None, + adaptive=True, + N=None, + root_number="compute", + bad_primes=None, + ncpus=None): + r""" + Return an upper bound for the analytic rank of self, conditional on + the Generalized Riemann Hypothesis, via computing + the zero sum `\sum_{\gamma} f(\Delta\gamma),` where `\gamma` + ranges over the imaginary parts of the zeros of `L(E,s)` + along the critical strip, `f(x) = (\sin(\pi x)/(\pi x))^2`, + and `\Delta` is the tightness parameter whose maximum value is specified + by ``max_Delta``. This computation can be run on curves with very large + conductor (so long as the conductor is known or quickly computable) + when `\Delta` is not too large (see below). + Uses Bober's rank bounding method as described in [Bob13]. + + INPUT: + + - ``max_Delta`` -- (default: None) If not None, a positive real value + specifying the maximum Delta value used in the zero sum; larger + values of Delta yield better bounds - but runtime is exponential in + Delta. If left as None, Delta is set + to `\min\{\frac{1}{\pi}(\log(N+1000)/2-\log(2\pi)-\eta), 2.5\}`, + where `N` is the conductor of the curve attached to self, and `\eta` + is the Euler-Mascheroni constant `= 0.5772...`; the crossover + point is at conductor around `8.3 \cdot 10^8`. For the former value, + empirical results show that for about 99.7% of all curves the returned + value is the actual analytic rank. + + - ``adaptive`` -- (default: True) Boolean + + - ``True`` -- the computation is first run with small and then + successively larger `\Delta` values up to max_Delta. If at any + point the computed bound is 0 (or 1 when when root_number is -1 + or True), the computation halts and that value is returned; + otherwise the minimum of the computed bounds is returned. + - ``False`` -- the computation is run a single time with `\Delta` + equal to ``max_Delta``, and the resulting bound returned. + + - ``N`` -- (default: None) If not None, a positive integer equal to + the conductor of self. This is passable so that rank estimation + can be done for curves whose (large) conductor has been precomputed. + + - ``root_number`` -- (default: "compute") String or integer + + - ``"compute"`` -- the root number of self is computed and used to + (possibly) lower ther analytic rank estimate by 1. + - ``"ignore"`` -- the above step is omitted + - ``1`` -- this value is assumed to be the root number of + self. This is passable so that rank estimation can be done for + curves whose root number has been precomputed. + - ``-1`` -- this value is assumed to be the root number of + self. This is passable so that rank estimation can be done for + curves whose root number has been precomputed. + + - ``bad_primes`` -- (default: None) If not None, a list of the primes + of bad reduction for the curve attached to self. This is passable + so that rank estimation can be done for curves of large conductor + whose bad primes have been precomputed. + + - ``ncpus`` - (default: None) If not None, a positive integer + defining the maximum number of CPUs to be used for the computation. + If left as None, the maximum available number of CPUs will be used. + Note: Due to parallelization overhead, multiple processors will + only be used for Delta values `\ge 1.75`. + + .. NOTE:: + + Output will be incorrect if the incorrect conductor or root number + is specified. + + .. WARNING:: + + Zero sum computation time is exponential in the tightness + parameter `\Delta`, roughly doubling for every increase of 0.1 + thereof. Using `\Delta=1` (and adaptive=False) will yield a runtime + of a few milliseconds; `\Delta=2` takes a few seconds, and `\Delta=3` + may take upwards of an hour. Increase beyond this at your own risk! + + OUTPUT: + + A non-negative integer greater than or equal to the analytic rank of + self. + + .. NOTE:: + + If you use set_verbose(1), extra information about the computation + will be printed. + + .. SEEALSO:: + + :func:`LFunctionZeroSum` + :meth:`.root_number` + :func:`set_verbose` + + EXAMPLES: + + For most elliptic curves with small conductor the central zero(s) + of `L_E(s)` are fairly isolated, so small values of `\Delta` + will yield tight rank estimates. + + :: + + sage: E = EllipticCurve("11a") + sage: E.rank() + 0 + sage: E.analytic_rank_upper_bound(max_Delta=1,adaptive=False) + 0 + sage: E = EllipticCurve([-39,123]) + sage: E.rank() + 1 + sage: E.analytic_rank_upper_bound(max_Delta=1,adaptive=True) + 1 + + This is especially true for elliptic curves with large rank. + + :: + + sage: for r in range(9): + ....: E = elliptic_curves.rank(r)[0] + ....: print(r,E.analytic_rank_upper_bound(max_Delta=1, + ....: adaptive=False,root_number="ignore")) + ....: + (0, 0) + (1, 1) + (2, 2) + (3, 3) + (4, 4) + (5, 5) + (6, 6) + (7, 7) + (8, 8) + + However, some curves have `L`-functions with low-lying zeroes, and for these + larger values of `\Delta` must be used to get tight estimates. + + :: + + sage: E = EllipticCurve("974b1") + sage: r = E.rank(); r + 0 + sage: E.analytic_rank_upper_bound(max_Delta=1,root_number="ignore") + 1 + sage: E.analytic_rank_upper_bound(max_Delta=1.3,root_number="ignore") + 0 + + Knowing the root number of `E` allows us to use smaller Delta values + to get tight bounds, thus speeding up runtime considerably. + + :: + + sage: E.analytic_rank_upper_bound(max_Delta=0.6,root_number="compute") + 0 + + The are a small number of curves which have pathalogically low-lying + zeroes. For these curves, this method will produce a bound that is + strictly larger than the analytic rank, unless very large values of + Delta are used. The following curve ("256944c1" in the Cremona tables) + is a rank 0 curve with a zero at 0.0256...; the smallest Delta value + for which the zero sum is strictly less than 2 is ~2.815. + + :: + + sage: E = EllipticCurve([0, -1, 0, -7460362000712, -7842981500851012704]) + sage: N,r = E.conductor(),E.analytic_rank(); N, r + (256944, 0) + sage: E.analytic_rank_upper_bound(max_Delta=1,adaptive=False) + 2 + sage: E.analytic_rank_upper_bound(max_Delta=2,adaptive=False) + 2 + + This method is can be called on curves with large conductor. + + :: + + sage: E = EllipticCurve([-2934,19238]) + sage: E.analytic_rank_upper_bound() + 1 + + And it can bound rank on curves with *very* large conductor, so long as + you know beforehand/can easily compute the conductor and primes of bad + reduction less than `e^{2\pi\Delta}`. The example below is of the rank + 28 curve discovered by Elkies that is the elliptic curve of (currently) + largest known rank. + + :: + + sage: a4 = -20067762415575526585033208209338542750930230312178956502 + sage: a6 = 34481611795030556467032985690390720374855944359319180361266008296291939448732243429 + sage: E = EllipticCurve([1,-1,1,a4,a6]) + sage: bad_primes = [2,3,5,7,11,13,17,19,48463] + sage: N = 3455601108357547341532253864901605231198511505793733138900595189472144724781456635380154149870961231592352897621963802238155192936274322687070 + sage: E.analytic_rank_upper_bound(max_Delta=2.37,adaptive=False, # long time + ....: N=N,root_number=1,bad_primes=bad_primes,ncpus=2) # long time + 32 + + REFERENCES: + + .. [Bob13] J.W. Bober. Conditionally bounding analytic ranks of elliptic curves. + ANTS 10. http://msp.org/obs/2013/1-1/obs-v1-n1-p07-s.pdf + + """ + Z = LFunctionZeroSum_EllipticCurve(self, N) + bound = Z.analytic_rank_upper_bound(max_Delta=max_Delta, + adaptive=adaptive, + root_number=root_number, + bad_primes=bad_primes, + ncpus=ncpus) + return bound def simon_two_descent(self, verbose=0, lim1=5, lim3=50, limtriv=3, maxprob=20, limbigprime=30, known_points=None): @@ -1668,12 +1900,14 @@ def rank(self, use_database=False, verbose=False, - ``use_database (bool)`` - (default: False), if True, try to look up the regulator in the Cremona database. - - ``verbose`` - (default: None), if specified changes - the verbosity of mwrank computations. algorithm - + - ``verbose`` - (default: False), if specified changes + the verbosity of mwrank computations. - - ``- 'mwrank_shell'`` - call mwrank shell command + - ``algorithm`` - (default: 'mwrank_lib'), one of: - - ``- 'mwrank_lib'`` - call mwrank c library + - ``'mwrank_shell'`` - call mwrank shell command + + - ``'mwrank_lib'`` - call mwrank c library - ``only_use_mwrank`` - (default: True) if False try using analytic rank methods first. @@ -1746,6 +1980,15 @@ def rank(self, use_database=False, verbose=False, # curve not in database, or rank not known pass if not only_use_mwrank: + # Try zero sum rank bound first; if this is 0 or 1 it's the + # true rank + rank_bound = self.analytic_rank_upper_bound() + if rank_bound <= 1: + misc.verbose("rank %s due to zero sum bound and parity"%rank_bound) + self.__rank[proof] = rank_bound + return self.__rank[proof] + # Next try evaluate the L-function or its derivative at the + # central point N = self.conductor() prec = int(4*float(sqrt(N))) + 10 if self.root_number() == 1: @@ -2037,8 +2280,7 @@ def _compute_gens(self, proof, G.append(eval(X[k:j].replace(':',','))) X = X[j:] i = X.find('Generator ') - G = [self.point(x, check=True) for x in G] - G.sort() + G = sorted([self.point(x, check=True) for x in G]) return G, proved def gens_certain(self): @@ -2688,7 +2930,9 @@ def is_p_minimal(self, p): """ Tests if curve is p-minimal at a given prime p. - INPUT: p - a primeOUTPUT: True - if curve is p-minimal + INPUT: p - a prime + + OUTPUT: True - if curve is p-minimal - ``False`` - if curve isn't p-minimal 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 63730c7854f..59bc0720cf4 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py +++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py @@ -467,8 +467,7 @@ def _maybe_borels(E, L, patience=100): E = _over_numberfield(E) K = E.base_field() - L = list(set(L)) # Remove duplicates from L and makes a copy for output - L.sort() + L = sorted(set(L)) # Remove duplicates from L and makes a copy for output include_2 = False if 2 in L: # c.f. Section 5.3(a) of [Serre72]. diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py index ccb2dec2947..90fbff5629d 100644 --- a/src/sage/schemes/elliptic_curves/height.py +++ b/src/sage/schemes/elliptic_curves/height.py @@ -518,8 +518,7 @@ def nonneg_region(f): sage: nonneg_region(-x^4-1) () """ - roots = f.roots() - roots.sort() + roots = sorted(f.roots()) sign_changes = [r for r,e in roots if e%2 == 1] if (f.leading_coefficient() * (-1)**f.degree()) > 0: sign_changes = [-infinity] + sign_changes diff --git a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py index 20659a6423a..b91ff713ddc 100644 --- a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py +++ b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py @@ -1145,8 +1145,7 @@ def isogenies_13_0(E): [endo.set_post_isomorphism(endo.codomain().isomorphism_to(E)) for endo in isogs] # we may have up to 12 other isogenies: - ts = (x**4 + 7*x**3 + 20*x**2 + 19*x + 1).roots(multiplicities=False) - ts.sort() + ts = sorted((x**4 + 7*x**3 + 20*x**2 + 19*x + 1).roots(multiplicities=False)) for t0 in ts: s3 = a / (6*t0**3 + 32*t0**2 + 68*t0 + 4) ss = sorted((x**3-s3).roots(multiplicities=False)) @@ -1256,8 +1255,7 @@ def isogenies_13_1728(E): # we may have up to 12 other isogenies: - ts = (x**6 + 10*x**5 + 46*x**4 + 108*x**3 + 122*x**2 + 38*x - 1).roots(multiplicities=False) - ts.sort() + ts = sorted((x**6 + 10*x**5 + 46*x**4 + 108*x**3 + 122*x**2 + 38*x - 1).roots(multiplicities=False)) for t0 in ts: s2 = a/(66*t0**5 + 630*t0**4 + 2750*t0**3 + 5882*t0**2 + 5414*t0 + 162) ss = sorted((x**2-s2).roots(multiplicities=False)) diff --git a/src/sage/schemes/elliptic_curves/lseries_ell.py b/src/sage/schemes/elliptic_curves/lseries_ell.py index d7b131ebd1f..551984a5506 100644 --- a/src/sage/schemes/elliptic_curves/lseries_ell.py +++ b/src/sage/schemes/elliptic_curves/lseries_ell.py @@ -4,7 +4,9 @@ AUTHORS: -- Jeroen Demeyer (2013-10-17): compute L series with arbitrary precision +- Simon Spicer (2014-08-15) - Added LFunctionZeroSum class interface method + +- Jeroen Demeyer (2013-10-17) - Compute L series with arbitrary precision instead of floats. - William Stein et al. (2005 and later) @@ -877,3 +879,30 @@ def L_ratio(self): return self.__lratio k += sqrtN misc.verbose("Increasing precision to %s terms."%k) + + def zero_sums(self, N=None): + r""" + Return an LFunctionZeroSum class object for efficient computation + of sums over the zeros of self. This can be used to bound analytic + rank from above without having to compute with the $L$-series + directly. + + INPUT: + + - ``N`` -- (default: None) If not None, the conductor of the + elliptic curve attached to self. This is passable so that zero + sum computations can be done on curves for which the conductor + has been precomputed. + + OUTPUT: + + A LFunctionZeroSum_EllipticCurve instance. + + EXAMPLES:: + + sage: E = EllipticCurve("5077a") + sage: E.lseries().zero_sums() + Zero sum estimator for L-function attached to Elliptic Curve defined by y^2 + y = x^3 - 7*x + 6 over Rational Field + """ + from sage.lfunctions.zero_sums import LFunctionZeroSum + return LFunctionZeroSum(self.__E, N=N) diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 2a41d87821c..30f1237b30e 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -1039,19 +1039,27 @@ def ei(self): sage: L.ei() [-1.107159871688768?, 0.2695944364054446?, 0.8375654352833230?] + In the following example, we should have one purely real 2-division point coordinate, + and two conjugate purely imaginary coordinates. + :: sage: K. = NumberField(x^3-2) sage: E = EllipticCurve([0,1,0,a,a]) sage: L = E.period_lattice(K.embeddings(RealField())[0]) - sage: L.ei() - [0.?e-17 - 1.122462048309373?*I, 0.?e-17 + 1.122462048309373?*I, -1] + sage: x1,x2,x3 = L.ei() + sage: abs(x1.real())+abs(x2.real())<1e-14 + True + sage: x1.imag(),x2.imag(),x3 + (-1.122462048309373?, 1.122462048309373?, -1) - sage: L = E.period_lattice(K.embeddings(ComplexField())[0]) - sage: L.ei() - [-1.000000000000000? + 0.?e-1...*I, - -0.9720806486198328? - 0.561231024154687?*I, - 0.9720806486198328? + 0.561231024154687?*I] + :: + + sage: L = E.period_lattice(K.embeddings(ComplexField())[0]) + sage: L.ei() + [-1.000000000000000? + 0.?e-1...*I, + -0.9720806486198328? - 0.561231024154687?*I, + 0.9720806486198328? + 0.561231024154687?*I] """ return self._ei diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index 3009c9f3df3..1de864415ed 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -249,9 +249,9 @@ def __call__(self, x, *args, **kwds): try: x = D(x) except (TypeError, NotImplementedError): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) elif self.domain()!=x.codomain(): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) else: x = converter(x) if not args and not kwds: @@ -382,9 +382,9 @@ def __mul__(self, right): Defn: Structure map) codomain """ if not isinstance(right, SchemeMorphism): - raise TypeError, "right (=%s) must be a SchemeMorphism to multiply it by %s"%(right, self) + raise TypeError("right (=%s) must be a SchemeMorphism to multiply it by %s"%(right, self)) if right.codomain() != self.domain(): - raise TypeError, "self (=%s) domain must equal right (=%s) codomain"%(self, right) + raise TypeError("self (=%s) domain must equal right (=%s) codomain"%(self, right)) if isinstance(self, SchemeMorphism_id): return right if isinstance(right, SchemeMorphism_id): @@ -416,7 +416,7 @@ def __pow__(self, n, dummy=None): Defn: Identity map """ if not self.is_endomorphism(): - raise TypeError, "self must be an endomorphism." + raise TypeError("self must be an endomorphism.") if n==0: return self.domain().identity_morphism() return generic_power(self, n) diff --git a/src/sage/schemes/product_projective/wehlerK3.py b/src/sage/schemes/product_projective/wehlerK3.py index e34980479cf..005b26b9676 100644 --- a/src/sage/schemes/product_projective/wehlerK3.py +++ b/src/sage/schemes/product_projective/wehlerK3.py @@ -927,8 +927,7 @@ def degenerate_primes(self,check = True): power += 1 if power == 1: bad_primes = bad_primes+GB[i].lt().coefficients()[0].support() - bad_primes = list(set(bad_primes)) - bad_primes.sort() + bad_primes = sorted(set(bad_primes)) #check to return only the truly bad primes if check == True: for p in bad_primes: @@ -1066,7 +1065,7 @@ def sigmaX(self,P, **kwds): try: P = self(list(P)) except (TypeError, NotImplementedError, AttributeError): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self)) pt = list(P[0]) + [0,0,0] if(P[1][0] != 0): [a,b,c] = [P[1][0]*self.Gpoly(1,0)(*pt),\ @@ -1310,7 +1309,7 @@ def sigmaY(self,P, **kwds): try: P = self(list(P)) except (TypeError, NotImplementedError, AttributeError): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self)) pt = [0,0,0] + list(P[1]) if(P[0][0] != 0): [a,b,c] = [P[0][0]*self.Gpoly(0,0)(*pt),\ diff --git a/src/sage/schemes/projective/endPN_automorphism_group.py b/src/sage/schemes/projective/endPN_automorphism_group.py index f17cd530dac..1642f0c698a 100644 --- a/src/sage/schemes/projective/endPN_automorphism_group.py +++ b/src/sage/schemes/projective/endPN_automorphism_group.py @@ -1732,7 +1732,7 @@ def which_group(list_of_elements): # invalid input if n == 0: - raise(ValueError, "Group must have at least one element") + raise ValueError("Group must have at least one element") # define ground field and ambient function field rational_function = G[-1] diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index 5b9b803658f..fe3870736d1 100644 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -134,8 +134,7 @@ def points(self, B=0, prec=53): aff_points = Y.rational_points() for PP in aff_points: points.add(X.ambient_space()(list(phi(PP)))) - points = list(points) - points.sort() + points = sorted(points) return points R = self.value_ring() if is_RationalField(R): diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index 3f9201bca48..c491c07db2b 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -195,9 +195,9 @@ def __call__(self, x, check=True): try: x = self.domain()(x) except (TypeError, NotImplementedError): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) elif self.domain()!=x.codomain(): - raise TypeError, "%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain()) + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(x, self.domain())) # Passes the array of args to _fast_eval P = self._fast_eval(x._coords) @@ -4233,7 +4233,7 @@ def automorphism_group(self, **kwds): return_functions=kwds.get('return_functions',False) if self.domain().dimension_relative()!=1: - raise NotImplementedError, "Must be dimension 1" + raise NotImplementedError("Must be dimension 1") else: f=self.dehomogenize(1) z=f[0].parent().gen() diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 14ddc510ced..ede01579cb8 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -1180,10 +1180,6 @@ cdef class ElementWithCachedMethod(Element): ....: " return '<%s>'%self.x", ....: " def __hash__(self):", ....: " return hash(self.x)", - ....: " def __cmp__(left, right):", - ....: " return (left)._cmp(right)", - ....: " def __richcmp__(left, right, op):", - ....: " return (left)._richcmp(right,op)", ....: " cpdef int _cmp_(left, Element right) except -2:", ....: " return cmp(left.x,right.x)", ....: " def raw_test(self):", @@ -1199,10 +1195,6 @@ cdef class ElementWithCachedMethod(Element): ....: " return '<%s>'%self.x", ....: " def __hash__(self):", ....: " return hash(self.x)", - ....: " def __cmp__(left, right):", - ....: " return (left)._cmp(right)", - ....: " def __richcmp__(left, right, op):", - ....: " return (left)._richcmp(right,op)", ....: " cpdef int _cmp_(left, Element right) except -2:", ....: " return cmp(left.x,right.x)", ....: " def raw_test(self):", diff --git a/src/sage/structure/formal_sum.py b/src/sage/structure/formal_sum.py index f8c1bd634e2..5933c47bd5c 100644 --- a/src/sage/structure/formal_sum.py +++ b/src/sage/structure/formal_sum.py @@ -76,168 +76,11 @@ from sage.categories.action import PrecomposedAction from sage.structure.unique_representation import UniqueRepresentation - -class FormalSums(UniqueRepresentation, Module): - """ - The R-module of finite formal sums with coefficients in some ring R. - - EXAMPLES:: - - sage: FormalSums() - Abelian Group of all Formal Finite Sums over Integer Ring - sage: FormalSums(ZZ) - Abelian Group of all Formal Finite Sums over Integer Ring - sage: FormalSums(GF(7)) - Abelian Group of all Formal Finite Sums over Finite Field of size 7 - sage: FormalSums(ZZ[sqrt(2)]) - Abelian Group of all Formal Finite Sums over Order in Number Field in sqrt2 with defining polynomial x^2 - 2 - sage: FormalSums(GF(9,'a')) - Abelian Group of all Formal Finite Sums over Finite Field in a of size 3^2 - """ - - @staticmethod - def __classcall__(cls, base_ring = ZZ): - """ - Set the default value for the base ring. - - EXAMPLES:: - - sage: FormalSums(ZZ) == FormalSums() # indirect test - True - """ - return UniqueRepresentation.__classcall__(cls, base_ring) - - 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_() - 'Abelian Group of all Formal Finite Sums over Finite Field of size 7' - """ - return "Abelian Group of all Formal Finite Sums over %s"%self.base_ring() - - def _element_constructor_(self, x, check=True, reduce=True): - """ - Make a formal sum in self from x. - - INPUT: - - - ``x`` -- formal sum, list or number - - - ``check`` -- bool (default: True) - - - ``reduce`` -- bool (default: True); whether to combine terms - - EXAMPLES:: - - sage: P = FormalSum([(1,2/3)]).parent() - sage: P([(1,2/3), (5,-2/9)]) # indirect test - 5*-2/9 + 2/3 - """ - if isinstance(x, FormalSum): - P = x.parent() - if P is self: - return x - elif P == self: - return FormalSum(x._data, check=False, reduce=False, parent=self) - else: - x = x._data - if isinstance(x, list): - return FormalSum(x, check=check,reduce=reduce,parent=self) - if x == 0: - return FormalSum([], check=False, reduce=False, parent=self) - else: - return FormalSum([(self.base_ring()(1), x)], check=False, reduce=False, parent=self) - - def _coerce_map_from_(self, X): - r""" - Return whether there is a coercion from ``X`` - - EXAMPLE:: - - sage: FormalSums(QQ).has_coerce_map_from( FormalSums(ZZ) ) # indirect test - True - - sage: FormalSums(ZZ).get_action(QQ) # indirect test - Right scalar multiplication by Rational Field on Abelian Group of all Formal Finite Sums over Rational Field - with precomposition on left by Conversion map: - From: Abelian Group of all Formal Finite Sums over Integer Ring - To: Abelian Group of all Formal Finite Sums over Rational Field - """ - if isinstance(X,FormalSums): - if self.base_ring().has_coerce_map_from(X.base_ring()): - return True - return False - - def base_extend(self, R): - """ - EXAMPLES:: - - sage: FormalSums(ZZ).base_extend(GF(7)) - Abelian Group of all Formal Finite Sums over Finite Field of size 7 - """ - if self.base_ring().has_coerce_map_from(R): - return self - elif R.has_coerce_map_from(self.base_ring()): - return self.__class__(R) - - def _get_action_(self, other, op, self_is_left): - """ - EXAMPLES:: - - sage: A = FormalSums(RR); A.get_action(RR) # indirect doctest - Right scalar multiplication by Real Field with 53 bits of precision on Abelian Group of all Formal Finite Sums over Real Field with 53 bits of precision - - sage: A = FormalSums(ZZ); A.get_action(QQ) - Right scalar multiplication by Rational Field on Abelian Group of all Formal Finite Sums over Rational Field - with precomposition on left by Conversion map: - From: Abelian Group of all Formal Finite Sums over Integer Ring - To: Abelian Group of all Formal Finite Sums over Rational Field - sage: A = FormalSums(QQ); A.get_action(ZZ) - Right scalar multiplication by Integer Ring on Abelian Group of all Formal Finite Sums over Rational Field - """ - if op is operator.mul and isinstance(other, Parent): - extended = self.base_extend(other) - if self_is_left: - action = RightModuleAction(other, extended) - if extended is not self: - action = PrecomposedAction(action, extended._internal_coerce_map_from(self), None) - else: - action = LeftModuleAction(other, extended) - if extended is not self: - action = PrecomposedAction(action, None, extended._internal_coerce_map_from(self)) - return action - - def _an_element_(self, check=False, reduce=False): - """ - EXAMPLES:: - - sage: FormalSums(ZZ).an_element() # indirect test - 1 - sage: FormalSums(QQ).an_element() - 1/2*1 - sage: QQ.an_element() - 1/2 - """ - return FormalSum([(self.base_ring().an_element(), 1)], - check=check, reduce=reduce, parent=self) - - -formal_sums = FormalSums() - -# Formal sums now derives from UniqueRepresentation, which makes the -# factory function unnecessary. This is why the name was changed from -# class FormalSums_generic to class FormalSums. -from sage.structure.sage_object import register_unpickle_override -register_unpickle_override('sage.structure.formal_sum', 'FormalSums_generic', FormalSums) - - class FormalSum(ModuleElement): """ A formal sum over a ring. """ - def __init__(self, x, parent=formal_sums, check=True, reduce=True): + def __init__(self, x, parent=None, check=True, reduce=True): """ INPUT: - ``x`` -- object @@ -283,6 +126,7 @@ def __init__(self, x, parent=formal_sums, check=True, reduce=True): if parent is None: parent = formal_sums ModuleElement.__init__(self, parent) + assert isinstance(parent, parent.category().parent_class) if reduce: # first reduce self.reduce() if check: # then check @@ -468,3 +312,168 @@ def reduce(self): if coeff != 0: w.append((coeff,last)) self._data = w + +class FormalSums(UniqueRepresentation, Module): + """ + The R-module of finite formal sums with coefficients in some ring R. + + EXAMPLES:: + + sage: FormalSums() + Abelian Group of all Formal Finite Sums over Integer Ring + sage: FormalSums(ZZ) + Abelian Group of all Formal Finite Sums over Integer Ring + sage: FormalSums(GF(7)) + Abelian Group of all Formal Finite Sums over Finite Field of size 7 + sage: FormalSums(ZZ[sqrt(2)]) + Abelian Group of all Formal Finite Sums over Order in Number Field in sqrt2 with defining polynomial x^2 - 2 + sage: FormalSums(GF(9,'a')) + Abelian Group of all Formal Finite Sums over Finite Field in a of size 3^2 + + TESTS:: + + sage: TestSuite(FormalSums(QQ)).run() + + """ + Element = FormalSum + @staticmethod + def __classcall__(cls, base_ring = ZZ): + """ + Set the default value for the base ring. + + EXAMPLES:: + + sage: FormalSums(ZZ) == FormalSums() # indirect test + True + """ + return UniqueRepresentation.__classcall__(cls, base_ring) + + 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_() + 'Abelian Group of all Formal Finite Sums over Finite Field of size 7' + """ + return "Abelian Group of all Formal Finite Sums over %s"%self.base_ring() + + def _element_constructor_(self, x, check=True, reduce=True): + """ + Make a formal sum in self from x. + + INPUT: + + - ``x`` -- formal sum, list or number + + - ``check`` -- bool (default: True) + + - ``reduce`` -- bool (default: True); whether to combine terms + + EXAMPLES:: + + sage: P = FormalSum([(1,2/3)]).parent() + sage: P([(1,2/3), (5,-2/9)]) # indirect test + 5*-2/9 + 2/3 + """ + if isinstance(x, FormalSum): + P = x.parent() + if P is self: + return x + elif P == self: + return self.element_class(x._data, check=False, reduce=False, parent=self) + else: + x = x._data + if isinstance(x, list): + return self.element_class(x, check=check,reduce=reduce,parent=self) + if x == 0: + return self.element_class([], check=False, reduce=False, parent=self) + else: + return self.element_class([(self.base_ring()(1), x)], check=False, reduce=False, parent=self) + + def _coerce_map_from_(self, X): + r""" + Return whether there is a coercion from ``X`` + + EXAMPLE:: + + sage: FormalSums(QQ).has_coerce_map_from( FormalSums(ZZ) ) # indirect test + True + + sage: FormalSums(ZZ).get_action(QQ) # indirect test + Right scalar multiplication by Rational Field on Abelian Group of all Formal Finite Sums over Rational Field + with precomposition on left by Conversion map: + From: Abelian Group of all Formal Finite Sums over Integer Ring + To: Abelian Group of all Formal Finite Sums over Rational Field + """ + if isinstance(X,FormalSums): + if self.base_ring().has_coerce_map_from(X.base_ring()): + return True + return False + + def base_extend(self, R): + """ + EXAMPLES:: + + sage: F7 = FormalSums(ZZ).base_extend(GF(7)); F7 + Abelian Group of all Formal Finite Sums over Finite Field of size 7 + + The following tests against a bug that was fixed at :trac:`18795`:: + + sage: isinstance(F7, F7.category().parent_class) + True + """ + if self.base_ring().has_coerce_map_from(R): + return self + elif R.has_coerce_map_from(self.base_ring()): + return FormalSums(R) + + def _get_action_(self, other, op, self_is_left): + """ + EXAMPLES:: + + sage: A = FormalSums(RR); A.get_action(RR) # indirect doctest + Right scalar multiplication by Real Field with 53 bits of precision on Abelian Group of all Formal Finite Sums over Real Field with 53 bits of precision + + sage: A = FormalSums(ZZ); A.get_action(QQ) + Right scalar multiplication by Rational Field on Abelian Group of all Formal Finite Sums over Rational Field + with precomposition on left by Conversion map: + From: Abelian Group of all Formal Finite Sums over Integer Ring + To: Abelian Group of all Formal Finite Sums over Rational Field + sage: A = FormalSums(QQ); A.get_action(ZZ) + Right scalar multiplication by Integer Ring on Abelian Group of all Formal Finite Sums over Rational Field + """ + if op is operator.mul and isinstance(other, Parent): + extended = self.base_extend(other) + if self_is_left: + action = RightModuleAction(other, extended) + if extended is not self: + action = PrecomposedAction(action, extended._internal_coerce_map_from(self), None) + else: + action = LeftModuleAction(other, extended) + if extended is not self: + action = PrecomposedAction(action, None, extended._internal_coerce_map_from(self)) + return action + + def _an_element_(self, check=False, reduce=False): + """ + EXAMPLES:: + + sage: FormalSums(ZZ).an_element() # indirect test + 1 + sage: FormalSums(QQ).an_element() + 1/2*1 + sage: QQ.an_element() + 1/2 + """ + return self.element_class([(self.base_ring().an_element(), 1)], + check=check, reduce=reduce, parent=self) + + +formal_sums = FormalSums() + +# Formal sums now derives from UniqueRepresentation, which makes the +# factory function unnecessary. This is why the name was changed from +# class FormalSums_generic to class FormalSums. +from sage.structure.sage_object import register_unpickle_override +register_unpickle_override('sage.structure.formal_sum', 'FormalSums_generic', FormalSums) diff --git a/src/sage/structure/list_clone.pyx b/src/sage/structure/list_clone.pyx index cd78a12b96d..861bb1e9de0 100644 --- a/src/sage/structure/list_clone.pyx +++ b/src/sage/structure/list_clone.pyx @@ -800,8 +800,6 @@ cdef class ClonableArray(ClonableElement): """ return self._list.count(key) - # __hash__ is not properly inherited if comparison is changed - # see def __hash__(self): """ Returns the hash value of ``self``. @@ -824,7 +822,8 @@ cdef class ClonableArray(ClonableElement): self._hash = self._hash_() return self._hash - def __richcmp__(left, right, int op): + # See protocol in comment in sage/structure/element.pyx + cpdef int _cmp_(left, Element right) except -2: """ TESTS:: @@ -833,13 +832,8 @@ cdef class ClonableArray(ClonableElement): sage: elc = copy(el) sage: elc == el # indirect doctest True - """ - return (left)._richcmp(right, op) - # See protocol in comment in sage/structure/element.pyx - cpdef int _cmp_(left, Element right) except -2: - """ - TEST:: + :: sage: from sage.structure.list_clone_demo import IncreasingArrays sage: el1 = IncreasingArrays()([1,2,4]) @@ -1580,7 +1574,8 @@ cdef class ClonableIntArray(ClonableElement): self._hash = self._hash_() return self._hash - def __richcmp__(left, right, int op): + # See protocol in comment in sage/structure/element.pyx + cpdef int _cmp_(left, Element right) except -2: """ TESTS:: @@ -1589,13 +1584,8 @@ cdef class ClonableIntArray(ClonableElement): sage: elc = copy(el) sage: elc == el # indirect doctest True - """ - return (left)._richcmp(right, op) - # See protocol in comment in sage/structure/element.pyx - cpdef int _cmp_(left, Element right) except -2: - """ - TEST:: + :: sage: from sage.structure.list_clone_demo import IncreasingIntArrays sage: el1 = IncreasingIntArrays()([1,2,4]) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 71f1cc0dbe3..7e7b1356168 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -1514,8 +1514,7 @@ cdef class Expression(CommutativeRingElement): """ return self._gobj.gethash() - # Boilerplate code from sage/structure/element.pyx - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ Create a formal symbolic inequality or equality. @@ -1570,9 +1569,6 @@ cdef class Expression(CommutativeRingElement): sage: x == (x == x) False """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): cdef Expression l, r l = left @@ -1809,6 +1805,33 @@ cdef class Expression(CommutativeRingElement): """ return haswild(self._gobj) + def is_algebraic(self): + """ + Return True if this expression is known to be algebraic. + + EXAMPLES:: + + sage: sqrt(2).is_algebraic() + True + sage: (5*sqrt(2)).is_algebraic() + True + sage: (sqrt(2) + 2^(1/3) - 1).is_algebraic() + True + sage: (I*golden_ratio + sqrt(2)).is_algebraic() + True + sage: (sqrt(2) + pi).is_algebraic() + False + sage: SR(QQ(2/3)).is_algebraic() + True + sage: SR(1.2).is_algebraic() + False + """ + try: + ex = sage.rings.all.QQbar(self) + except (TypeError, ValueError, NotImplementedError): + return False + return True + def is_real(self): """ Return True if this expression is known to be a real number. @@ -2250,10 +2273,8 @@ cdef class Expression(CommutativeRingElement): False sage: SR(1).__nonzero__() True - sage: bool(abs(x)) - True - sage: bool(x/x - 1) - False + sage: assert(abs(x)) + sage: assert(not x/x - 1) This is called by :meth:`is_zero`:: @@ -2272,67 +2293,41 @@ cdef class Expression(CommutativeRingElement): for symbolic relations:: sage: x = var('x') - sage: bool((x-1)^2 == x^2 - 2*x + 1) - True - sage: bool(((x-1)^2 == x^2 - 2*x + 1).expand()) - True - sage: bool(((x-1)^2 == x^2 - 2*x + 3).expand()) - False - sage: bool(2 + x < 3 + x) - True - sage: bool(2 + x < 1 + x) - False - sage: bool(2 + x > 1 + x) - True - sage: bool(1 + x > 1 + x) - False - sage: bool(1 + x >= 1 + x) - True - sage: bool(1 + x < 1 + x) - False - sage: bool(1 + x <= 1 + x) - True - sage: bool(1 + x^2 != 1 + x*x) - False - sage: bool(1 + x^2 != 2 + x*x) - True - sage: bool(SR(oo) == SR(oo)) - True - sage: bool(-SR(oo) == SR(oo)) - False - sage: bool(-SR(oo) != SR(oo)) - True + sage: assert((x-1)^2 == x^2 - 2*x + 1) + sage: assert(((x-1)^2 == x^2 - 2*x + 1).expand()) + sage: assert(not ((x-1)^2 == x^2 - 2*x + 3).expand()) + sage: assert(2 + x < 3 + x) + sage: assert(not 2 + x < 1 + x) + sage: assert(2 + x > 1 + x) + sage: assert(not 1 + x > 1 + x) + sage: assert(1 + x >= 1 + x) + sage: assert(not 1 + x < 1 + x) + sage: assert(1 + x <= 1 + x) + sage: assert(not 1 + x^2 != 1 + x*x) + sage: assert(1 + x^2 != 2 + x*x) + sage: assert(SR(oo) == SR(oo)) + sage: assert(not -SR(oo) == SR(oo)) + sage: assert(-SR(oo) != SR(oo)) Next, tests to ensure assumptions are correctly used:: sage: x, y, z = var('x, y, z') - sage: assume(x>=y,y>=z,z>=x) - sage: bool(x==z) - True - sage: bool(zy) - False - sage: bool(y==z) - True - sage: bool(y<=z) - True + sage: assume(x >= y, y >= z, z >= x) + sage: assert(x == z) + sage: assert(not z < x) + sage: assert(not z > y) + sage: assert(y == z) + sage: assert(y <= z) sage: forget() - sage: assume(x>=1,x<=1) - sage: bool(x==1) - True - sage: bool(x != 1) - False - sage: bool(x>1) - False + sage: assume(x >= 1, x <= 1) + sage: assert(x == 1) + sage: assert(not x != 1) + sage: assert(not x > 1) sage: forget() - sage: assume(x>0) - sage: bool(x==0) - False - sage: bool(x != 0) - True - sage: bool(x == 1) - False + sage: assume(x > 0) + sage: assert(not x == 0) + sage: assert(x != 0) + sage: assert(not x == 1) The following must be true, even though we do not know for sure that x is not 1, as symbolic comparisons @@ -2342,16 +2337,12 @@ cdef class Expression(CommutativeRingElement): :: - sage: bool(x != 1) - True + sage: assert(x != 1) sage: forget() sage: assume(x>y) - sage: bool(x==y) - False - sage: bool(x != y) - True - sage: bool(x != y) # The same comment as above applies here as well - True + sage: assert(not x==y) + sage: assert(x != y) + sage: assert(x != y) # The same comment as above applies here as well sage: forget() Comparisons of infinities:: @@ -2382,8 +2373,29 @@ cdef class Expression(CommutativeRingElement): Check that :trac:`13326` is fixed:: - sage: bool(log(2)*Infinity == Infinity) - True + sage: assert(log(2)*Infinity == Infinity) + + More checks for comparisons with infinity (see :trac:`12967`):: + + sage: assert(SR(oo) > 5) + sage: assert(5 < SR(oo)) + sage: assert(SR(2) < Infinity) + sage: assert(pi < Infinity) + sage: assert(not pi>Infinity) + sage: assert(2*pi < Infinity) + sage: assert(SR(pi) < SR(Infinity)) + sage: assert(sqrt(2) < oo) + sage: assert(log(2) < oo) + sage: assert(e < oo) + sage: assert(e+pi < oo) + sage: assert(e^pi < oo) + sage: assert(not SR(2) < -oo) + sage: assert(SR(2) > -oo) + sage: assert(exp(2) > -oo) + sage: assert(SR(oo) > sqrt(2)) + sage: assert(sqrt(2) < SR(oo)) + sage: assert(SR(-oo) < sqrt(2)) + sage: assert(sqrt(2) > SR(-oo)) """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -2514,6 +2526,15 @@ cdef class Expression(CommutativeRingElement): False sage: (cot(pi + x) == 0).test_relation() NotImplemented + + Check that :trac:`18896` is fixed:: + + sage: m=540579833922455191419978421211010409605356811833049025*sqrt(1/2) + sage: m1=382247666339265723780973363167714496025733124557617743 + sage: (m==m1).test_relation(domain=QQbar) + False + sage: (m==m1).test_relation() + False """ cdef int k, eq_count = 0 cdef bint is_interval @@ -2523,14 +2544,24 @@ cdef class Expression(CommutativeRingElement): from sage.rings.real_mpfi import is_RealIntervalField from sage.rings.complex_interval_field import is_ComplexIntervalField from sage.rings.all import RIF, CIF + from sage.rings.qqbar import is_AlgebraicField, is_AlgebraicRealField, AA, QQbar if domain is None: is_interval = True - if op == equal or op == not_equal: - domain = CIF + if self.lhs().is_algebraic() and self.rhs().is_algebraic(): + if op == equal or op == not_equal: + domain = QQbar + else: + domain = AA else: - domain = RIF + if op == equal or op == not_equal: + domain = CIF + else: + domain = RIF else: - is_interval = is_RealIntervalField(domain) or is_ComplexIntervalField(domain) + is_interval = (is_RealIntervalField(domain) + or is_ComplexIntervalField(domain) + or is_AlgebraicField(domain) + or is_AlgebraicRealField(domain)) zero = domain(0) diff = self.lhs() - self.rhs() vars = diff.variables() @@ -3010,6 +3041,25 @@ cdef class Expression(CommutativeRingElement): sage: ex = -(x1 + r2 - x2*r1)/x3 sage: ex.substitute(a=z, b=z) (r1*x2 - r2 - x1)/x3 + + Check that floating point numbers +/- 1.0 are treated + differently from integers +/- 1 (:trac:`12257`):: + + sage: (1*x).operator() + sage: (1.0*x).operator() + right @@ -3160,8 +3210,7 @@ cdef class Expression(CommutativeRingElement): """ return 1/self - # Boilerplate code from sage/structure/element.pyx - def __cmp__(left, right): + cpdef int _cmp_(left, Element right) except -2: """ Compare self and right, returning -1, 0, or 1, depending on if self < right, self == right, or self > right, respectively. @@ -3169,11 +3218,22 @@ cdef class Expression(CommutativeRingElement): Use this instead of the operators <=, <, etc. to compare symbolic expressions when you do not want to get a formal inequality back. - IMPORTANT: Both self and right *must* have the same type, or + IMPORTANT: Both self and right *must* have the same parent, or this function will not be called. + INPUT: + + - ``right`` -- A :class:`Expression` instance. + + OUTPUT: -1, 0 or 1 + EXAMPLES:: + sage: a = sqrt(3) + sage: b = x^2+1 + sage: a.__cmp__(b) # indirect doctest + -1 + sage: x,y = var('x,y') sage: x.__cmp__(y) 1 @@ -3286,27 +3346,6 @@ cdef class Expression(CommutativeRingElement): sage: t.subs(x=I*x).subs(x=0).is_positive() False """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element right) except -2: - """ - Compare ``left`` and ``right``. - - INPUT: - - - ``right`` -- A :class:`Expression` instance. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: a = sqrt(3) - sage: b = x^2+1 - sage: a.__cmp__(b) # indirect doctest - -1 - """ return print_order_compare(left._gobj, (right)._gobj) cpdef int _cmp_add(Expression left, Expression right) except -2: @@ -3484,6 +3523,21 @@ cdef class Expression(CommutativeRingElement): sage: SR(0)^SR(0) 1 + + Check that floating point numbers +/- 1.0 are treated + differently from integers +/- 1 (:trac:`12257`):: + + sage: (x^1).operator() + sage: (x^1.0).operator() + + sage: x^1.0 + x^1.00000000000000 + sage: x^-1.0 + x^(-1.00000000000000) + sage: 0^1.0 + 0.000000000000000 + sage: exp(x)^1.0 + (e^x)^1.00000000000000 """ cdef Expression base, nexp diff --git a/src/sage/tests/gap_packages.py b/src/sage/tests/gap_packages.py index ee68d7012c5..b0961c78421 100644 --- a/src/sage/tests/gap_packages.py +++ b/src/sage/tests/gap_packages.py @@ -12,7 +12,7 @@ These are packages in the ``database_gap`` package:: - sage: test_packages(['atlasrep', 'tomlib']) # optional - database_gap + sage: test_packages(['atlasrep', 'tomlib']) # optional - database_gap gap_packages Status Package GAP Output +--------+----------+------------+ atlasrep true diff --git a/src/sage/version.py b/src/sage/version.py index 1f2b28580d7..2e19d638c50 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.8.beta8' -date = '2015-07-10' +version = '6.9.beta0' +date = '2015-07-29' diff --git a/src/setup.py b/src/setup.py index c8c55a2ea22..4c7b0939c87 100755 --- a/src/setup.py +++ b/src/setup.py @@ -10,7 +10,7 @@ def excepthook(*exc): When an error occurs, display an error message similar to the error messages from ``sage-spkg``. - In particular, ``build/install`` will recognize "sage" as a failed + In particular, ``build/make/install`` will recognize "sage" as a failed package, see :trac:`16774`. """ stars = '*' * 72