diff --git a/VERSION.txt b/VERSION.txt index 20905998067..9d90d562953 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.9.rc0, released 2015-09-25 +Sage version 6.10.beta0, released 2015-10-15 diff --git a/build/make/deps b/build/make/deps index c9becf07a8a..3d0fb935312 100644 --- a/build/make/deps +++ b/build/make/deps @@ -44,6 +44,12 @@ all-sage: \ $(EXTCODE) \ $(SCRIPTS) +# Download all packages which should be inside an sdist tarball (the -B +# option to make forces all targets to be built unconditionally) +download-for-sdist: base + env SAGE_INSTALL_FETCH_ONLY=yes $(MAKE) -B SAGERUNTIME= \ + $(STANDARD_PACKAGES) gcc mpir python2 + # TOOLCHAIN consists of dependencies determined by build/make/install, # including for example the GCC package. toolchain: $(TOOLCHAIN) diff --git a/build/pkgs/boehm_gc/dependencies b/build/pkgs/boehm_gc/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/boehm_gc/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/boost_cropped/dependencies b/build/pkgs/boost_cropped/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/boost_cropped/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/buckygen/dependencies b/build/pkgs/buckygen/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/buckygen/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/cephes/dependencies b/build/pkgs/cephes/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cephes/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/cliquer/dependencies b/build/pkgs/cliquer/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cliquer/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/combinatorial_designs/dependencies b/build/pkgs/combinatorial_designs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/combinatorial_designs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/compilerwrapper/dependencies b/build/pkgs/compilerwrapper/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/compilerwrapper/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index cf5c6b5db1c..0c170545927 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=8cfd9c770b0b2b6740da8861b8e5fb55be6a1961 -md5=e24e7847a5df019a174b8a8f4d1928dd -cksum=1203608624 +sha1=3e0d10789b34d6f890e1575c2a06894a90e4807e +md5=020a9b7f31e61b57056969b6816455f1 +cksum=2662451870 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index ee977b5ecd7..52bd8e43afb 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -115 +120 diff --git a/build/pkgs/cryptominisat/dependencies b/build/pkgs/cryptominisat/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/cryptominisat/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/cython/checksums.ini b/build/pkgs/cython/checksums.ini index 95899805812..f3ac4410434 100644 --- a/build/pkgs/cython/checksums.ini +++ b/build/pkgs/cython/checksums.ini @@ -1,4 +1,4 @@ tarball=Cython-VERSION.tar.gz -sha1=2ff0f863d3b996d2265d0bf06e567e5dd23d004d -md5=db3c5b365e1c3f71c7cd90e96473a3ab -cksum=1672168057 +sha1=d5592dc3d529c55a5ef95346caccf11c556993bd +md5=813df20f7ce5f00e60568e0371fbd07c +cksum=365027876 diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index e13359b8fa3..9e40e75c5d2 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -0.23.1.p0 +0.23.3 diff --git a/build/pkgs/d3js/dependencies b/build/pkgs/d3js/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/d3js/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_cremona_ellcurve/dependencies b/build/pkgs/database_cremona_ellcurve/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_cremona_ellcurve/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_jones_numfield/dependencies b/build/pkgs/database_jones_numfield/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_jones_numfield/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_stein_watkins/dependencies b/build/pkgs/database_stein_watkins/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_stein_watkins_mini/dependencies b/build/pkgs/database_stein_watkins_mini/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_stein_watkins_mini/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/database_symbolic_data/dependencies b/build/pkgs/database_symbolic_data/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/database_symbolic_data/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/gcc/spkg-install b/build/pkgs/gcc/spkg-install index a93411334ad..f9c7fe690e9 100755 --- a/build/pkgs/gcc/spkg-install +++ b/build/pkgs/gcc/spkg-install @@ -137,3 +137,7 @@ $MAKE install # Force re-installation of mpir, mpfr and mpc with the GCC we just built. cd "$SAGE_SPKG_INST" rm -f mpir-* mpfr-* mpc-* + +# Force re-configuration: the next time that "make" is run, we don't +# want GCC to be built again, see Trac #19324 +touch "$SAGE_ROOT/configure" diff --git a/build/pkgs/graphs/dependencies b/build/pkgs/graphs/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/graphs/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/iconv/dependencies b/build/pkgs/iconv/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/iconv/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libogg/dependencies b/build/pkgs/libogg/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/libogg/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/lrcalc/dependencies b/build/pkgs/lrcalc/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/lrcalc/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/nauty/dependencies b/build/pkgs/nauty/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/nauty/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/ncurses/dependencies b/build/pkgs/ncurses/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/ncurses/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/networkx/checksums.ini b/build/pkgs/networkx/checksums.ini index 50122f00a66..9aaf119a3b8 100644 --- a/build/pkgs/networkx/checksums.ini +++ b/build/pkgs/networkx/checksums.ini @@ -1,4 +1,4 @@ tarball=networkx-VERSION.tar.gz -sha1=d6c1524724d3e47f7621bb2072863463924bfb99 -md5=b4a9e68ecd1b0164446ee432d2e20bd0 -cksum=3256827710 +sha1=99292e464c25be5e96de295752880bf5e5f1848a +md5=eb7a065e37250a4cc009919dacfe7a9d +cksum=2520536431 diff --git a/build/pkgs/networkx/dependencies b/build/pkgs/networkx/dependencies index edf27112135..d4db0ff5eae 100644 --- a/build/pkgs/networkx/dependencies +++ b/build/pkgs/networkx/dependencies @@ -1,4 +1,4 @@ -$(INST)/$(PYTHON) +$(INST)/$(PYTHON) $(INST)/$(DECORATOR) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/networkx/package-version.txt b/build/pkgs/networkx/package-version.txt index a8fdfda1c78..c044b1a3269 100644 --- a/build/pkgs/networkx/package-version.txt +++ b/build/pkgs/networkx/package-version.txt @@ -1 +1 @@ -1.8.1 +1.10 diff --git a/build/pkgs/networkx/spkg-install b/build/pkgs/networkx/spkg-install index 6769f79adac..5cd75ee5791 100755 --- a/build/pkgs/networkx/spkg-install +++ b/build/pkgs/networkx/spkg-install @@ -15,4 +15,4 @@ rm -rf "$SAGE_LOCAL"/spkg/network* cd src -python setup.py install --home="$SAGE_LOCAL" --force +python setup.py install diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt index c5106e6d139..61d8a2af785 100644 --- a/build/pkgs/notebook/package-version.txt +++ b/build/pkgs/notebook/package-version.txt @@ -1 +1 @@ -4.0.4 +4.0.4.p1 diff --git a/build/pkgs/notebook/patches/jupyter_notebook_config.py b/build/pkgs/notebook/patches/jupyter_notebook_config.py new file mode 100644 index 00000000000..3bf63a1649a --- /dev/null +++ b/build/pkgs/notebook/patches/jupyter_notebook_config.py @@ -0,0 +1,7 @@ +# Configuration file for Sage's builtin Jupyter notebook server + +# Note for distributors: Sage uses mathjax, so the notebook server +# needs to have the mathjax_url set to wherever your distribution +# installs mathjax. + +c.NotebookApp.mathjax_url = '../nbextensions/mathjax/MathJax.js' diff --git a/build/pkgs/notebook/spkg-install b/build/pkgs/notebook/spkg-install index afb3f302fd1..10759a4d973 100755 --- a/build/pkgs/notebook/spkg-install +++ b/build/pkgs/notebook/spkg-install @@ -1,3 +1,8 @@ #!/usr/bin/env bash cd src && python setup.py install + +# Install the Jupyter notebook configuration +ETC_JUPYTER="$SAGE_ETC"/jupyter +mkdir -p "$ETC_JUPYTER" +cp ../patches/jupyter_notebook_config.py "$ETC_JUPYTER"/ diff --git a/build/pkgs/openssl/dependencies b/build/pkgs/openssl/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/openssl/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pari/checksums.ini b/build/pkgs/pari/checksums.ini index 8870cbb86ee..c62c530a888 100644 --- a/build/pkgs/pari/checksums.ini +++ b/build/pkgs/pari/checksums.ini @@ -1,4 +1,4 @@ tarball=pari-VERSION.tar.gz -sha1=307409c3917f6df71d2e10640c119e7d31c1f2e6 -md5=41936ce2dce6bd00a662bf43a772685f -cksum=855809013 +sha1=fa23e0c8b6e38a356048d19224dc9b9658d77724 +md5=c753faaa4780de5ad8d461f0ffd70ecf +cksum=1220765312 diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 8db184f072c..2b25bd18c62 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-1637-g489005a.p1 +2.8-1813-g6157df4.p0 diff --git a/build/pkgs/pari/patches/KERNELCFLAGS.patch b/build/pkgs/pari/patches/KERNELCFLAGS.patch deleted file mode 100644 index 537dbb52d1e..00000000000 --- a/build/pkgs/pari/patches/KERNELCFLAGS.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff -ru src/config/get_cc b/config/get_cc ---- src/config/get_cc 2014-02-01 21:41:54.534348273 +0100 -+++ b/config/get_cc 2014-02-01 21:42:50.850930971 +0100 -@@ -94,7 +94,11 @@ - OPTFLAGS="$OPTFLAGS -fno-strict-aliasing" - fi - rm -f $exe $exe$exe_suff -- KERNELCFLAGS=-funroll-loops -+ if [ "$SAGE_DEBUG" = yes ]; then -+ KERNELCFLAGS=-O1 -+ else -+ KERNELCFLAGS=-funroll-loops -+ fi - - DBGFLAGS=${DBGFLAGS:-"-g $warn"} - # Specific optimisations for some architectures diff --git a/build/pkgs/pari/patches/README.txt b/build/pkgs/pari/patches/README.txt index 93ac577bd55..d9aef73659a 100644 --- a/build/pkgs/pari/patches/README.txt +++ b/build/pkgs/pari/patches/README.txt @@ -11,17 +11,7 @@ Patches to configuration files: Darwin. Submitted upstream, but upstream only applied it for PowerPC. Since this doesn't break anything and only improves performance, add the flag unconditionally. -* KERNELCFLAGS.patch (Jeroen Demeyer): when SAGE_DEBUG=yes, compile - kernel files with -O1 instead of -funroll-loops; -O0 gives a - segmentation fault on some OS X systems when doing - factor(10356613*10694706299664611221) - See #13921, also reported upstream: - - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html C files: -* det_garbage.patch (Jeroen Demeyer, #15654): When computing a - determinant(), only collect garbage once per outer loop iteration. - Better increase PARI stack size instead of collecting garbage too - often. * public_memory_functions.patch (Jeroen Demeyer, #16997): Make some of PARI's private memory functions public to improve interface with Sage. diff --git a/build/pkgs/pari/patches/det_garbage.patch b/build/pkgs/pari/patches/det_garbage.patch deleted file mode 100644 index ab7af6643af..00000000000 --- a/build/pkgs/pari/patches/det_garbage.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/src/basemath/alglin1.c b/src/basemath/alglin1.c -index cada9eb..515eba9 100644 ---- a/src/basemath/alglin1.c -+++ b/src/basemath/alglin1.c -@@ -248,6 +248,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - a = RgM_shallowcopy(a); - for (i=1; ired(E,gcoeff(a,k,i)); -@@ -272,7 +273,7 @@ gen_det(GEN a, void *E, const struct bb_field *ff) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = ff->add(E, gcoeff(a,j,k), ff->mul(E,m,gcoeff(a,j,i))); -- if (gc_needed(av,1)) -+ if (gc_needed(av,1) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,4, &a,&x,&q,&m); -@@ -3722,6 +3723,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - a = RgM_shallowcopy(a); - for (i=1; i nbco) return gerepilecopy(av, gcoeff(a,i,i)); - if (k != i) -@@ -3741,7 +3743,7 @@ det_simple_gauss(GEN a, GEN data, pivot_fun pivot) - for (j=i+1; j<=nbco; j++) - { - gcoeff(a,j,k) = gsub(gcoeff(a,j,k), gmul(m,gcoeff(a,j,i))); -- if (gc_needed(av,3)) -+ if (gc_needed(av,3) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&x); -@@ -3792,6 +3794,7 @@ det_bareiss(GEN a) - { - GEN ci, ck, m; - int diveuc = (gequal1(pprec)==0); -+ int garbage = 0; /* Only gerepile() once per loop iteration */ - - p = gcoeff(a,i,i); - if (gequal0(p)) -@@ -3828,7 +3831,7 @@ det_bareiss(GEN a) - GEN p1 = gsub(gmul(p,gel(ck,j)), gmul(m,gel(ci,j))); - if (diveuc) p1 = mydiv(p1,pprec); - gel(ck,j) = gerepileupto(av2, p1); -- if (gc_needed(av,2)) -+ if (gc_needed(av,2) && (garbage++ == 0)) - { - if(DEBUGMEM>1) pari_warn(warnmem,"det. col = %ld",i); - gerepileall(av,2, &a,&pprec); diff --git a/build/pkgs/pari/patches/perl_regex.patch b/build/pkgs/pari/patches/perl_regex.patch deleted file mode 100644 index 038f4d604e8..00000000000 --- a/build/pkgs/pari/patches/perl_regex.patch +++ /dev/null @@ -1,200 +0,0 @@ -commit 257750686ae1fe928a2b4b489844c1b57a108bd3 -Author: Karim Belabas -Date: Tue Jul 14 15:23:42 2015 +0200 - - doc_make: escape all {} in regexps [ perl-5.22 warns on these => fatal - -diff --git a/src/desc/doc_make b/src/desc/doc_make -index bb41bc9..8521a9d 100755 ---- a/src/desc/doc_make -+++ b/src/desc/doc_make -@@ -38,13 +38,13 @@ while () - $v =~ s/\[\]/[\\,]/g; - $v =~ s/(\w\w+)/\\var{$1}/g; - $v =~ s/\^([a-z])/\\hbox{\\kbd{\\pow}}$1/g; -- $v =~ s/\\var{flag}/\\fl/g; -- $v =~ s/\\var{(\d+)}/{$1}/g; -+ $v =~ s/\\var\{flag\}/\\fl/g; -+ $v =~ s/\\var\{(\d+)\}/{$1}/g; - $v =~ s/_/\\_/g; # don't merge with first subst: \var{} rule kills it - - $v = "\$($v)\$"; - } -- if ($doc !~ /\\syn\w*{/ && $sec !~ /programming\/control/) { -+ if ($doc !~ /\\syn\w*\{/ && $sec !~ /programming\/control/) { - $doc .= library_syntax($fun, $args); - } - s/_def_//; -commit 742c70e505a7e75128720f619d63e882c03e9346 -Author: Karim Belabas -Date: Tue Jul 14 13:20:07 2015 +0200 - - gphelp: escape all {} in regexps [ perl-5.22 warns on these => fatal ] - -diff --git a/doc/gphelp.in b/doc/gphelp.in -index 00ff6bd..89f2768 100755 ---- a/doc/gphelp.in -+++ b/doc/gphelp.in -@@ -298,7 +298,7 @@ sub treat { - if ($pat =~ /[a-zA-Z0-9]$/) { $pat .= '\b'; } else { $pat .= '}'; } - while () - { -- if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label){$pat/) -+ if (/\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter|label)\{$pat/) - { $first = $_; last; } - } - if (eof(DOC)) -@@ -380,7 +380,7 @@ sub apropos_check { - return if ($line !~ /$help/i); - - local($_) = $current; -- s/\\b{(.)}/\\$1/; -+ s/\\b\{(.)\}/\\$1/; - s/\{\}//g; - s/\\pow/^/; - s/\\%/%/; -@@ -400,7 +400,7 @@ sub apropos { - @sentence_list = @list = ""; - while () - { -- if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter){/) -+ if (/^\\(subsubsec[a-z]*|subsec[a-z]*|section|chapter)\{/) - { - $new = &get_match($_,'{','}'); - &apropos_check($line, $current); -@@ -748,7 +748,7 @@ sub basic_subst { - s/\\fun\s*\{([^{}]*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*\{((?:[^{}]|\{[^{}]*\})*)\}/\\kbd{$1 \\key{$2}($3)}\\sidx{$2}/g; - - s/\\\\(?=[a-zA-Z])/\\bs /g; -- s/\\b{}\\b{}/\\bs\\bs /g; -+ s/\\b\{\}\\b\{\}/\\bs\\bs /g; - s/\\\\/\\bs/g; - s/(\'\'|\`\`)/"/g unless $to_pod; # (english) double quotes - # asymptotic or isomorphic (~) [beware of ties] -@@ -760,16 +760,16 @@ sub basic_subst { - s/\\(~|tilde)/~/g; - - s/\\(equiv)(?![a-zA-Z])/ = /g; -- s/\\`a/$tr{agrave}/; s/\\`{a}/$tr{agrave}/; -- s/\\"o/$tr{ouml}/; s/\\"{o}/$tr{ouml}/; -- s/\\"u/$tr{uuml}/; s/\\"{u}/$tr{uuml}/; -- s/\\'e/$tr{eacute}/; s/\\'{e}/$tr{eacute}/; -+ s/\\`a/$tr{agrave}/; s/\\`\{a\}/$tr{agrave}/; -+ s/\\"o/$tr{ouml}/; s/\\"\{o\}/$tr{ouml}/; -+ s/\\"u/$tr{uuml}/; s/\\"\{u\}/$tr{uuml}/; -+ s/\\'e/$tr{eacute}/; s/\\'\{e\}/$tr{eacute}/; - - s/(^|[^\\])%.*/$1/g; # comments - s/\\vadjust\s*\{\s*\\penalty\s*\d+\s*\}//g; - - # We do not strip %\n, thus: -- s/\\kbd{\n\s*/\\kbd{/g; -+ s/\\kbd\{\n\s*/\\kbd{/g; - s/\$\\bf(\b|(?=[\d_]))\s*([^\$]+)\$/\$$tr{startbcode}$1$tr{endbcode}\$/g; - s/\$/$tr{dollar}/g; # math mode - s/\t/ /g; s/\\,//g; s/\\[ ;]/ /g; # various spaces -@@ -779,7 +779,7 @@ sub basic_subst { - s/\\TeX\{\}/TeX/g; - s/\\TeX(\W)/TeX$1/g; - s/ *\\circ\b */ o /g; -- s/\\d?frac{\s*((?:[^{}]|\{[^{}]*\})*)}{\s*((?:[^{}]|\{[^{}]*\})*)}/($1)\/($2)/g; -+ s/\\d?frac\{\s*((?:[^{}]|\{[^{}]*\})*)\}\{\s*((?:[^{}]|\{[^{}]*\})*)\}/($1)\/($2)/g; - s(\\d?frac\s*(\d)\s*(\d))(($1/$2))g; - s[{\s*(\w)\s*\\over(?![a-zA-Z])\s*(\w)\s*}]{($1/$2)}g; - s[{\s*((?:[^{}]|\{[^{}]*\})*)\\over(?![a-zA-Z])\s*((?:[^{}]|\{[^{}]*\})*)}][($1)/($2)]g; -@@ -796,7 +796,7 @@ sub basic_subst { - - s/(\\string)?\\_/_/g; - s/\\([#\$&%|])/$1/g; -- s/\\(hat(?![a-zA-Z])|\^)({\\?\s*})?/^/g; -+ s/\\(hat(?![a-zA-Z])|\^)(\{\\?\s*\})?/^/g; - s/^(\@\[podleader\]head\d *)\\pow(?![a-zA-z])( *)/$1^$2/gm; - s/ *\\pow(?![a-zA-z]) */^/g; - -@@ -896,21 +896,21 @@ sub basic_subst { - s/\\(floor|ceil|round|binom)\{/$1\{/g; - s/\\(var|emph)\{([^\}]*)\}/$tr{startit}$2$tr{endit}/g; - s/\\fl(?![a-zA-Z])/$tr{startit}flag$tr{endit}/g; -- s/\\b{([^}]*)}/$tr{startcode}\\$1$tr{endcode}/g; -+ s/\\b\{([^}]*)\}/$tr{startcode}\\$1$tr{endcode}/g; - s/\\kbdsidx/\\sidx/g; - s/\\sidx\{[^\}]*\}//g unless $to_pod; - s/\\[a-zA-Z]*idx\{([^\}]*)\}/$1/g unless $to_pod; -- s/{\\text{(st|nd|th)}}/\\text{$1}/g; -- s/\^\\text{th}/-th/g; -- s/1\^\\text{st}/1st/g; -- s/2\^\\text{nd}/2nd/g; -+ s/\{\\text\{(st|nd|th)\}\}/\\text{$1}/g; -+ s/\^\\text\{th\}/-th/g; -+ s/1\^\\text\{st\}/1st/g; -+ s/2\^\\text\{nd\}/2nd/g; - - s/\\(text|hbox|Big)//g; - s/^([ \t]+)\{ *\\(it|sl|bf|tt)\b/S<$1>{\\$2/gm; - s/\{ *\\(it|sl) *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startit}$2$tr{endit}/g; - s/\{ *\\bf *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startbold}$1$tr{endbold}/g; - s/\{ *\\tt *(([^{}]+(?=[{}])|\{[^{}]*\})*)\}/$tr{startpodcode}$1$tr{endpodcode}/g; -- $seek=1 if (s/\\emph{ */$tr{startit}/g); -+ $seek=1 if (s/\\emph\{ */$tr{startit}/g); - if ($seek) { $seek=0 if (s/\}/$tr{endit}/) } - s/\\(backslash|bs)\{(\w)\}/\\$2/g; - s/\\(backslash|bs)(?![a-zA-Z]) */\\/g; -@@ -1028,21 +1028,21 @@ sub TeXprint_topod { - # Try to guard \label/\sidx (removing possible '.') - # This somehow breaks index... - # s/(\\(?:section|subsec(?:ref|idx|op)?(unix)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -- s/(\\(?:section|subsec(?:ref|idx|op)?)\s*{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)})\.?\s*\\(label|sidx)/$1\n\\$2/; -+ s/(\\(?:section|subsec(?:ref|idx|op)?)\s*\{(?:(?:[^{}]+(?=[{}])|{[^{}]+})+)\})\.?\s*\\(label|sidx)/$1\n\\$2/; - - # last if /\\subsec[\\{}ref]*[\\\${]$help[}\\\$]/o; -- s/\\chapter\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -- s/\\appendix\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -- s/\\section\s*{((?:[^{}]|\{[^{}]*\})*)}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; -+ s/\\chapter\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nlibPARI - $1\n\n/; -+ s/\\appendix\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/\n\n$tr{podleader}head1 NAME\n\nAppendix - $1\n\n/; -+ s/\\section\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*/"\n\n$tr{podleader}head1 " . indexify($1) . "\n\n"/e; - - # Try to delimit by : -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsubsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^\n]*):[\n ]/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsubsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}([^:]*):\s*/"\n\n$tr{podleader}head3 " . indexify("$1$3") . "\n\n"/e; - s/\\subsubsec\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(.*)$/"\n\n$tr{podleader}head3 " . indexify("$1") . "$3\n\n"/me; - s/\\subseckbd\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}([^:]*):\s*/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; - # Try to delimit by ' ' -- s/\\subsec(?:ref)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]+})+)}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -- s/\\subsec(?:title)?(?:unix)?\s*{(([^{}]+(?=[{}])|{[^{}]*})+)}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; -+ s/\\subsec(?:ref)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]+})+)\}(\S*)\s+/"\n\n$tr{podleader}head2 " . indexify("$1$3") . "\n\n"/e; -+ s/\\subsec(?:title)?(?:unix)?\s*\{(([^{}]+(?=[{}])|{[^{}]*})+)\}:?\s*/"\n\n$tr{podleader}head2 " . indexify("$1") . "\n\n"/e; - - # This is to skip preface in refcard: - /\Q$tr{podleader}\Ehead1|\\title(?![a-zA-Z])\s*\{/o and $seen_start = 1 -@@ -1097,18 +1097,18 @@ sub TeXprint_topod { - s/\$\$(.*?)\$\$\s*/\n\nS< >$tr{startcode}$1$tr{endcode}\n\n/gs; - s/\$([^\$]+)\$/$tr{startcode}$1$tr{endcode}/g; - -- s/\\s(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">"/ge; # -- s/\\(?:ref|idx){\s*([^{}]*)}/"X<" . for_index($1) . ">$1"/ge; -+ s/\\s(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">"/ge; # -+ s/\\(?:ref|idx)\{\s*([^{}]*)\}/"X<" . for_index($1) . ">$1"/ge; - - # Conflict between different versions of PARI and refcard: --# s/\\(?:key|li)\s*{(.*)}\s*{(.+)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; --# s/\\(?:key|li)\s*{(.*)}\s*{}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; --# s/\\(key|var)(?![a-zA-Z])\s*{(\w+)}/C<$2>/mg; -- s/\\var\s*{X<(\w+)>(\w+)}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -- s/\\var\s*{f{}lag}/$tr{startcode}flag$tr{endcode}/mg; -- -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{\s*(\w+)(?=C\<)(.*)}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -- s/\\metax(?![a-zA-Z])\s*{(.*)}\s*{(.*)}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{(.+)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/msg; -+# s/\\(?:key|li)\s*\{(.*)\}\s*\{\}[ \t]*\n/\n\n=back\n\n$1\n\n=over\n\n/mgs; -+# s/\\(key|var)(?![a-zA-Z])\s*\{(\w+)\}/C<$2>/mg; -+ s/\\var\s*\{X<(\w+)>(\w+)\}/X<$1>$tr{startcode}$2$tr{endcode}/mg; -+ s/\\var\s*\{f\{\}lag\}/$tr{startcode}flag$tr{endcode}/mg; -+ -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{\s*(\w+)(?=C\<)(.*)\}[ \t]*\n/\n\n=item C$3>\n\n$1\n\n/mg; -+ s/\\metax(?![a-zA-Z])\s*\{(.*)\}\s*\{(.*)\}[ \t]*\n/\n\n=item C<$2>\n\n$1\n\n/mg; - s/C\<\{\}=/C\<=/g; - s/\\fl(?![a-zA-Z])/I/g; - s/\\file(?![a-zA-Z])/F/g; diff --git a/build/pkgs/pari/spkg-install b/build/pkgs/pari/spkg-install index 6a7b1ebc9d3..55a64aec9a8 100755 --- a/build/pkgs/pari/spkg-install +++ b/build/pkgs/pari/spkg-install @@ -206,7 +206,14 @@ set_environment # Set CFLAGS if [ "$SAGE_DEBUG" = yes ]; then # Disable optimisation, add debug symbols. - CFLAGS="$CFLAGS -O0 -g" + CFLAGS="-O0 -g $CFLAGS" + + # Compile kernel files with -O1 instead of -funroll-loops; -O0 gives + # a segmentation fault on some OS X systems when doing + # factor(10356613*10694706299664611221) + # See #13921, also reported upstream: + # - http://pari.math.u-bordeaux.fr/archives/pari-dev-1301/msg00000.html + PARI_MAKEFLAGS="KERNELCFLAGS=-O1 $PARI_MAKEFLAGS" else # Use PARI's default CFLAGS (with -g added). # PARI's Configure adds -O3 to the CFLAGS, so we don't need to add diff --git a/build/pkgs/pari_galdata/dependencies b/build/pkgs/pari_galdata/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_galdata/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pari_seadata_small/dependencies b/build/pkgs/pari_seadata_small/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/pari_seadata_small/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/patch/dependencies b/build/pkgs/patch/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/patch/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/patchbot/SPKG.txt b/build/pkgs/patchbot/SPKG.txt new file mode 100644 index 00000000000..c34994b3818 --- /dev/null +++ b/build/pkgs/patchbot/SPKG.txt @@ -0,0 +1,26 @@ += SageMath patchbot = + +== Description == + +Apply branches and run tests on open Sage tickets. + +The patchbot is used to automate the testing of git branches. It has two +different aspects: a server side and a client side. + +Instructions for using the client side can be found at + +http://wiki.sagemath.org/buildbot/details + +== License == + +GPLv2+ + +== Upstream Contact == + +Robert Bradshaw +Frédéric Chapoton +https://github.com/robertwb/sage-patchbot/ + +== Dependencies == + +python, python-dateutil, sage-scripts diff --git a/build/pkgs/patchbot/checksums.ini b/build/pkgs/patchbot/checksums.ini new file mode 100644 index 00000000000..4a35077fa89 --- /dev/null +++ b/build/pkgs/patchbot/checksums.ini @@ -0,0 +1,4 @@ +tarball=patchbot-VERSION.tar.bz2 +sha1=1850ce7004fe49b669be0b53102d32e9095cc307 +md5=a84f244c2f6e6c715676a09028750b36 +cksum=1356602931 diff --git a/build/pkgs/patchbot/dependencies b/build/pkgs/patchbot/dependencies new file mode 100644 index 00000000000..4e1e0144211 --- /dev/null +++ b/build/pkgs/patchbot/dependencies @@ -0,0 +1 @@ +# No dependencies diff --git a/build/pkgs/patchbot/package-version.txt b/build/pkgs/patchbot/package-version.txt new file mode 100644 index 00000000000..f225a78adf0 --- /dev/null +++ b/build/pkgs/patchbot/package-version.txt @@ -0,0 +1 @@ +2.5.2 diff --git a/build/pkgs/patchbot/spkg-install b/build/pkgs/patchbot/spkg-install new file mode 100755 index 00000000000..5dcc96f4629 --- /dev/null +++ b/build/pkgs/patchbot/spkg-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +# Delete any currently existing patchbot +rm -rf "$SAGE_LOCAL/bin/patchbot" + +# Copy into final location. +# The sage-patchbot script knows how to call this... +cp -Rv src "$SAGE_LOCAL/bin/patchbot" diff --git a/build/pkgs/patchbot/type b/build/pkgs/patchbot/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/patchbot/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/planarity/dependencies b/build/pkgs/planarity/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/planarity/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/plantri/dependencies b/build/pkgs/plantri/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/plantri/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/polytopes_db/dependencies b/build/pkgs/polytopes_db/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/polytopes_db/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/rubiks/dependencies b/build/pkgs/rubiks/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rubiks/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/rw/dependencies b/build/pkgs/rw/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/rw/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/saclib/dependencies b/build/pkgs/saclib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/saclib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/symmetrica/dependencies b/build/pkgs/symmetrica/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/symmetrica/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/sympow/dependencies b/build/pkgs/sympow/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/sympow/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/termcap/dependencies b/build/pkgs/termcap/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/termcap/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/valgrind/dependencies b/build/pkgs/valgrind/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/valgrind/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zeromq/dependencies b/build/pkgs/zeromq/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zeromq/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/zlib/dependencies b/build/pkgs/zlib/dependencies new file mode 100644 index 00000000000..3546cda4614 --- /dev/null +++ b/build/pkgs/zlib/dependencies @@ -0,0 +1,5 @@ +# no dependencies + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index c1dfe89c8d8..c08d958e4c4 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -167,11 +167,13 @@ class SageDownloadFileApplication(object): def run(self): progress = True url = None + print_fastest_mirror = None destination = None for arg in sys.argv[1:]: if arg.startswith('--print-fastest-mirror'): - print(MirrorList().fastest) - sys.exit(0) + url = "" + print_fastest_mirror = True + continue if arg.startswith('--quiet'): progress = False continue @@ -184,13 +186,31 @@ def run(self): raise ValueError('too many arguments') if url is None: print(dedent(self.__doc__.format(SAGE_DISTFILES=SAGE_DISTFILES))) - sys.exit(1) - if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): - Download(url, destination, progress=progress, ignore_errors=True).run() - else: - # url is a tarball name - tarball = Tarball(url) - tarball.download() - if destination is not None: - tarball.save_as(destination) - + sys.exit(2) + + try: + if url.startswith('http://') or url.startswith('https://') or url.startswith('ftp://'): + Download(url, destination, progress=progress, ignore_errors=True).run() + elif print_fastest_mirror: + url = "fastest mirror" # For error message + print(MirrorList().fastest) + else: + # url is a tarball name + tarball = Tarball(url) + tarball.download() + if destination is not None: + tarball.save_as(destination) + except BaseException: + try: + stars = '*' * 72 + '\n' + sys.stderr.write(stars) + try: + import traceback + traceback.print_exc(file=sys.stderr) + sys.stderr.write(stars) + except: + pass + sys.stderr.write("Error downloading %s\n"%(url,)) + sys.stderr.write(stars) + finally: + sys.exit(1) diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 37584805324..4a95ae46233 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 6.9.rc0, Release Date: 2015-09-25 │ +│ SageMath Version 6.10.beta0, Release Date: 2015-10-15 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-sdist b/src/bin/sage-sdist index 6b08cec0dc1..69424b20148 100755 --- a/src/bin/sage-sdist +++ b/src/bin/sage-sdist @@ -43,12 +43,10 @@ echo "Sage version $SAGE_VERSION, release date $SAGE_RELEASE_DATE" TARGET="sage-$SAGE_VERSION" sage-clone-source "$SAGE_ROOT" "$TMP_DIR/$TARGET" -# Download and copy all upstream tarballs (the -B option to make forces -# all targets to be built unconditionally) +# Download and copy all upstream tarballs cd "$SAGE_ROOT/build/make" -SAGE_INSTALL_GCC=yes SAGE_SPKG_COPY_UPSTREAM="$TMP_DIR/$TARGET/upstream" \ -SAGE_INSTALL_FETCH_ONLY=yes \ -./install -B all-build +export SAGE_SPKG_COPY_UPSTREAM="$TMP_DIR/$TARGET/upstream" +$MAKE download-for-sdist # Create source .tar.gz cd "$TMP_DIR" diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index e5b60c9973f..b8eaf7940b3 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,4 +1,4 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='6.9.rc0' -SAGE_RELEASE_DATE='2015-09-25' +SAGE_VERSION='6.10.beta0' +SAGE_RELEASE_DATE='2015-10-15' diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 30c65ff6557..d8b74192727 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -503,7 +503,7 @@ documentation for more information on its behaviour and optional arguments. def my_new_function(): ... - my_old_function = deprecated_function_alias(my_new_function) + my_old_function = deprecated_function_alias(666, my_new_function) * **Moving an object to a different module:** if you rename a source file or move some function (or class) to a diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index f80229432e5..f07d5a3f78e 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -4,6 +4,8 @@ Algebras .. toctree:: :maxdepth: 2 + sage/algebras/associated_graded + sage/algebras/catalog sage/algebras/clifford_algebra @@ -25,6 +27,8 @@ Algebras sage/algebras/free_algebra_quotient sage/algebras/free_algebra_quotient_element + sage/algebras/free_zinbiel_algebra + sage/algebras/group_algebra sage/algebras/iwahori_hecke_algebra diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 6ee9d20e54d..518d9e6582f 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -63,6 +63,10 @@ Individual Categories sage/categories/enumerated_sets sage/categories/euclidean_domains sage/categories/fields + sage/categories/filtered_algebras + sage/categories/filtered_algebras_with_basis + sage/categories/filtered_modules + sage/categories/filtered_modules_with_basis sage/categories/finite_coxeter_groups sage/categories/finite_crystals sage/categories/finite_dimensional_algebras_with_basis diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index 004458eba3f..6311e17f696 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -6,8 +6,10 @@ Coding Theory .. toctree:: :maxdepth: 1 - sage/coding/channels_catalog + sage/coding/encoder + sage/coding/encoders_catalog sage/coding/channel_constructions + sage/coding/channels_catalog sage/coding/codes_catalog sage/coding/linear_code sage/coding/code_constructions diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 8ee512b5bc7..b2f82fdae21 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -35,6 +35,7 @@ Comprehensive Module list sage/combinat/cluster_algebra_quiver/mutation_type sage/combinat/cluster_algebra_quiver/quiver sage/combinat/cluster_algebra_quiver/quiver_mutation_type + sage/combinat/colored_permutations sage/combinat/combinat sage/combinat/combinat_cython sage/combinat/combination diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index f71db685e66..a27c03f556a 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -91,6 +91,7 @@ Libraries of algorithms sage/graphs/graph_latex sage/graphs/graph_editor sage/graphs/graph_list + sage/graphs/graph_input sage/graphs/hyperbolicity sage/graphs/tutte_polynomial sage/graphs/generic_graph_pyx diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index d84fd747804..bd55ed8b014 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -35,6 +35,7 @@ whereas others have multiple bases. sage/rings/polynomial/real_roots sage/rings/polynomial/complex_roots + sage/rings/polynomial/refine_root sage/rings/polynomial/ideal sage/rings/polynomial/polynomial_quotient_ring diff --git a/src/doc/en/reference/sat/index.rst b/src/doc/en/reference/sat/index.rst index 3c86ef5cac2..7f6314ac5d8 100644 --- a/src/doc/en/reference/sat/index.rst +++ b/src/doc/en/reference/sat/index.rst @@ -18,33 +18,36 @@ should be true, we write:: Solvers ------- -Any SAT solver supporting the DIMACS input format is easily interfaced using the -:class:`sage.sat.solvers.dimacs.DIMACS` blueprint. Sage ships with pre-written interfaces for *RSat* -[RS]_ and *Glucose* [GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ SAT -solver which can be used interchangably with DIMACS-based solvers, but also provides advanced -features. For this, the optional CryptoMiniSat package must be -installed, this can be accomplished by typing the following in the +By default, Sage solves SAT instances as an Integer Linear Program (see +:mod:`sage.numerical.mip`), but any SAT solver supporting the DIMACS input +format is easily interfaced using the :class:`sage.sat.solvers.dimacs.DIMACS` +blueprint. Sage ships with pre-written interfaces for *RSat* [RS]_ and *Glucose* +[GL]_. Furthermore, Sage provides a C++ interface to the *CryptoMiniSat* [CMS]_ +SAT solver which can be used interchangably with DIMACS-based solvers, but also +provides advanced features. For this last solver, the optional CryptoMiniSat +package must be installed, this can be accomplished by typing the following in the shell:: sage -i cryptominisat sagelib -Since by default Sage does not include any SAT solver, we demonstrate key features by instantiating -a fake DIMACS-based solver. We start with a trivial example:: +We now show how to solve a simple SAT problem. :: (x1 OR x2 OR x3) AND (x1 OR x2 OR (NOT x3)) In Sage's notation:: - sage: from sage.sat.solvers.dimacs import DIMACS - sage: solver = DIMACS(command="sat-solver") + sage: solver = SAT() sage: solver.add_clause( ( 1, 2, 3) ) sage: solver.add_clause( ( 1, 2, -3) ) + sage: solver() # random + (None, True, True, False) .. NOTE:: - :meth:`sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables when necessary. In - particular, it creates *all* variables up to the given index. Hence, adding a literal involving - the variable 1000 creates up to 1000 internal variables. + :meth:`~sage.sat.solvers.dimacs.DIMACS.add_clause` creates new variables + when necessary. When using CryptoMiniSat, it creates *all* variables up to + the given index. Hence, adding a literal involving the variable 1000 creates + up to 1000 internal variables. DIMACS-base solvers can also be used to write DIMACS files:: @@ -76,14 +79,6 @@ Alternatively, there is :meth:`sage.sat.solvers.dimacs.DIMACS.clauses`:: These files can then be passed external SAT solvers. -We demonstrate solving using CryptoMiniSat:: - - sage: from sage.sat.solvers import CryptoMiniSat # optional - cryptominisat - sage: cms = CryptoMiniSat() # optional - cryptominisat - sage: cms.add_clause((1,2,-3)) # optional - cryptominisat - sage: cms() # optional - cryptominisat - (None, True, True, False) - Details on Specific Solvers ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,6 +87,7 @@ Details on Specific Solvers sage/sat/solvers/satsolver sage/sat/solvers/dimacs + sage/sat/solvers/sat_lp .. optional - cryptominisat .. sage/sat/solvers/cryptominisat/cryptominisat .. sage/sat/solvers/cryptominisat/solverconf diff --git a/src/module_list.py b/src/module_list.py index 7108872b586..d0a48218689 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -1570,6 +1570,10 @@ def uname_specific(name, value, alternative): sources = ['sage/rings/polynomial/real_roots.pyx'], libraries=['mpfr']), + Extension('sage.rings.polynomial.refine_root', + sources = ['sage/rings/polynomial/refine_root.pyx'], + libraries=['gmp', 'mpfr', 'mpfi']), + Extension('sage.rings.polynomial.symmetric_reduction', sources = ['sage/rings/polynomial/symmetric_reduction.pyx']), diff --git a/src/sage/algebras/associated_graded.py b/src/sage/algebras/associated_graded.py new file mode 100644 index 00000000000..750cbe33df1 --- /dev/null +++ b/src/sage/algebras/associated_graded.py @@ -0,0 +1,341 @@ +r""" +Associated Graded Algebras To Filtered Algebras + +AUTHORS: + +- Travis Scrimshaw (2014-10-08): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from copy import copy + +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.sets.family import Family +from sage.combinat.free_module import CombinatorialFreeModule + +class AssociatedGradedAlgebra(CombinatorialFreeModule): + r""" + The associated graded algebra/module `\operatorname{gr} A` + of a filtered algebra/module with basis `A`. + + Let `A` be a filtered module over a commutative ring `R`. + Let `(F_i)_{i \in I}` be the filtration of `A`, with `I` being + a totally ordered set. Define + + .. MATH:: + + G_i = F_i / \sum_{j < i} F_j + + for every `i \in I`, and then + + .. MATH:: + + \operatorname{gr} A = \bigoplus_{i \in I} G_i. + + There are canonical projections `p_i : F_i \to G_i` for + every `i \in I`. Moreover `\operatorname{gr} A` is naturally a + graded `R`-module with `G_i` being the `i`-th graded component. + This graded `R`-module is known as the *associated graded module* + (or, for short, just *graded module*) of `A`. + + Now, assume that `A` (endowed with the filtration + `(F_i)_{i \in I}`) is not just a filtered `R`-module, but also + a filtered `R`-algebra. + Let `u \in G_i` and `v \in G_j`, and let `u' \in F_i` and + `v' \in F_j` be lifts of `u` and `v`, respectively (so that + `u = p_i(u')` and `v = p_j(v')`). Then, we define a + multiplication `*` on `\operatorname{gr} A` (not to be mistaken + for the multiplication of the original algebra `A`) by + + .. MATH:: + + u * v = p_{i+j} (u' v'). + + The *associated graded algebra* (or, for short, just + *graded algebra*) of `A` is the graded algebra + `\operatorname{gr} A` (endowed with this multiplication). + + Now, assume that `A` is a filtered `R`-algebra with basis. + Let `(b_x)_{x \in X}` be the basis of `A`, + and consider the partition `X = \bigsqcup_{i \in I} X_i` of + the set `X`, which is part of the data of a filtered + algebra with basis. We know (see + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis`) + that `A` (being a filtered `R`-module with basis) is canonically + (when the basis is considered to be part of the data) + isomorphic to `\operatorname{gr} A` as an `R`-module. Therefore + the `k`-th graded component `G_k` can be identified with + the span of `(b_x)_{x \in X_k}`, or equivalently the + `k`-th homogeneous component of `A`. Suppose + that `u' v' = \sum_{k \leq i+j} m_k` where `m_k \in G_k` (which + has been identified with the `k`-th homogeneous component of `A`). + Then `u * v = m_{i+j}`. We also note that the choice of + identification of `G_k` with the `k`-th homogeneous component + of `A` depends on the given basis. + + The basis `(b_x)_{x \in X}` of `A` gives rise to a basis + of `\operatorname{gr} A`. This latter basis is still indexed + by the elements of `X`, and consists of the images of the + `b_x` under the `R`-module isomorphism from `A` to + `\operatorname{gr} A`. It makes `\operatorname{gr} A` into + a graded `R`-algebra with basis. + + In this class, the `R`-module isomorphism from `A` to + `\operatorname{gr} A` is implemented as + :meth:`to_graded_conversion` and also as the default + conversion from `A` to `\operatorname{gr} A`. Its + inverse map is implemented as + :meth:`from_graded_conversion`. + The projection `p_i : F_i \to G_i` is implemented as + :meth:`projection` ``(i)``. + + INPUT: + + - ``A`` -- a filtered module (or algebra) with basis + + OUTPUT: + + The associated graded module of `A`, if `A` is just a filtered + `R`-module. + The associated graded algebra of `A`, if `A` is a filtered + `R`-algebra. + + EXAMPLES: + + Associated graded module of a filtered module:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded modules with basis over Rational Field + sage: x = A.basis()[Partition([3,2,1])] + sage: grA(x) + Bbar[[3, 2, 1]] + + Associated graded algebra of a filtered algebra:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.category() + Category of graded algebras with basis over Rational Field + sage: x,y,z = map(lambda s: grA.algebra_generators()[s], ['x','y','z']) + sage: x + bar(U['x']) + sage: y * x + z + bar(U['x']*U['y']) + bar(U['z']) + sage: A(y) * A(x) + A(z) + U['x']*U['y'] + + We note that the conversion between ``A`` and ``grA`` is + the canonical ``QQ``-module isomorphism stemming from the + fact that the underlying ``QQ``-modules of ``A`` and + ``grA`` are isomorphic:: + + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: elt = A.an_element() + A.algebra_generators()['x'] + 2 + sage: grelt = grA(elt); grelt + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: A(grelt) == elt + True + + .. TODO:: + + The algebra ``A`` must currently be an instance of (a subclass of) + :class:`CombinatorialFreeModule`. This should work with any + filtered algebra with a basis. + + .. TODO:: + + Implement a version of associated graded algebra for + filtered algebras without a distinguished basis. + + REFERENCES: + + - :wikipedia:`Filtered_algebra#Associated_graded_algebra` + """ + def __init__(self, A, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: TestSuite(grA).run(elements=[prod(grA.algebra_generators())]) + """ + if A not in ModulesWithBasis(A.base_ring().category()).Filtered(): + raise ValueError("the base algebra must be filtered and with basis") + self._A = A + + base_ring = A.base_ring() + base_one = base_ring.one() + + category = A.category().Graded().or_subcategory(category) + try: + opts = copy(A.print_options()) + if not opts['prefix'] and not opts['bracket']: + opts['bracket'] = '(' + opts['prefix'] = opts['prefix'] + 'bar' + except AttributeError: + opts = {'prefix': 'Abar'} + + CombinatorialFreeModule.__init__(self, base_ring, A.basis().keys(), + category=category, **opts) + + # Setup the conversion back + phi = self.module_morphism(diagonal=lambda x: base_one, codomain=A) + self._A.register_conversion(phi) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + """ + from sage.categories.algebras_with_basis import AlgebrasWithBasis + if self in AlgebrasWithBasis: + return "Graded Algebra of {}".format(self._A) + return "Graded Module of {}".format(self._A) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: latex(A.graded_algebra()) + \operatorname{gr} ... + """ + from sage.misc.latex import latex + return "\\operatorname{gr} " + latex(self._A) + + def _element_constructor_(self, x): + r""" + Construct an element of ``self`` from ``x``. + + If ``self`` `= \operatorname{gr} A` for a filtered algebra + `A` with basis, and if ``x`` is an element of `A`, then + this returns the image of `x` under the canonical `R`-module + isomorphism `A \to \operatorname{gr} A`. (In this case, + this is equivalent to calling + ``self.to_graded_conversion()(x)``.) + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA(A.an_element()) + bar(U['x']^2*U['y']^2*U['z']^3) + sage: grA(A.an_element() + A.algebra_generators()['x'] + 2) + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + """ + if isinstance(x, CombinatorialFreeModule.Element): + if x.parent() is self._A: + return self._from_dict(dict(x)) + return super(AssociatedGradedAlgebra, self)._element_constructor_(x) + + def gen(self, *args, **kwds): + """ + Return a generator of ``self``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.gen('x') + bar(U['x']) + """ + try: + x = self._A.gen(*args, **kwds) + except AttributeError: + x = self._A.algebra_generators()[args[0]] + return self(x) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + This assumes that the algebra generators of `A` provided by + its ``algebra_generators`` method are homogeneous. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.algebra_generators() + Finite family {'y': bar(U['y']), 'x': bar(U['x']), 'z': bar(U['z'])} + """ + G = self._A.algebra_generators() + return Family(G.keys(), lambda x: self(G[x]), name="generator") + + def degree_on_basis(self, x): + """ + Return the degree of the basis element indexed by ``x``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: all(A.degree_on_basis(x) == grA.degree_on_basis(x) + ....: for g in grA.algebra_generators() for x in g.support()) + True + """ + return self._A.degree_on_basis(x) + + @cached_method + def one_basis(self): + """ + Return the basis index of the element `1` of + `\operatorname{gr} A`. + + This assumes that the unity `1` of `A` belongs to `F_0`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: grA.one_basis() + 1 + """ + return self._A.one_basis() + + def product_on_basis(self, x, y): + """ + Return the product on basis elements given by the + indices ``x`` and ``y``. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: grA = A.graded_algebra() + sage: G = grA.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: x * y # indirect doctest + bar(U['x']*U['y']) + sage: y * x + bar(U['x']*U['y']) + sage: z * y * x + bar(U['x']*U['y']*U['z']) + """ + ret = self._A.product_on_basis(x, y) + deg = self._A.degree_on_basis(x) + self._A.degree_on_basis(y) + return self.sum_of_terms([(i,c) for i,c in ret + if self._A.degree_on_basis(i) == deg], + distinct=True) + diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index da062022b5d..262e8113f44 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -16,6 +16,7 @@ - :class:`algebras.FiniteDimensional ` - :class:`algebras.Free ` +- :class:`algebras.FreeZinbiel ` - :class:`algebras.PreLieAlgebra ` - :func:`algebras.GradedCommutative ` @@ -48,6 +49,7 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.algebras.nil_coxeter_algebra', 'NilCoxeterAlgebra', 'NilCoxeter') +lazy_import('sage.algebras.free_zinbiel_algebra', 'FreeZinbielAlgebra', 'FreeZinbiel') lazy_import('sage.algebras.hall_algebra', 'HallAlgebra', 'Hall') lazy_import('sage.algebras.jordan_algebra', 'JordanAlgebra', 'Jordan') lazy_import('sage.algebras.shuffle_algebra', 'ShuffleAlgebra', 'Shuffle') diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index b419d985be3..bb88f5e3bb4 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -18,8 +18,7 @@ from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis -from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis -from sage.categories.graded_hopf_algebras_with_basis import GradedHopfAlgebrasWithBasis +from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.modules.with_basis.morphism import ModuleMorphismByLinearity from sage.categories.poor_man_map import PoorManMap from sage.rings.all import ZZ @@ -423,6 +422,14 @@ class CliffordAlgebra(CombinatorialFreeModule): canonical isomorphism. The inclusion `i` is commonly used to identify `V` with a vector subspace of `Cl(V)`. + The Clifford algebra `Cl(V, Q)` is a `\ZZ_2`-graded algebra + (where `\ZZ_2 = \ZZ / 2 \ZZ`); this grading is determined by + placing all elements of `V` in degree `1`. It is also an + `\NN`-filtered algebra, with the filtration too being defined + by placing all elements of `V` in degree `1`. The :meth:`degree` gives + the `\NN`-*filtration* degree, and to get the super degree use instead + :meth:`~sage.categories.super_modules.SuperModules.ElementMethods.is_even_odd`. + The Clifford algebra also can be considered as a covariant functor from the category of vector spaces equipped with quadratic forms to the category of algebras. In fact, if `(V, Q)` and `(W, R)` @@ -465,7 +472,8 @@ class CliffordAlgebra(CombinatorialFreeModule): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) sage: Cl - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -480,11 +488,6 @@ class CliffordAlgebra(CombinatorialFreeModule): a*d sage: d*c*b*a + a + 4*b*c a*b*c*d + 4*b*c + a - - .. WARNING:: - - The Clifford algebra is not graded, but instead filtered. This - will be changed once :trac:`17096` is finished. """ @staticmethod def __classcall_private__(cls, Q, names=None): @@ -520,6 +523,9 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) + sage: Cl.category() + Category of finite dimensional super algebras with basis + over (euclidean domains and infinite enumerated sets) sage: TestSuite(Cl).run() TESTS: @@ -536,8 +542,7 @@ def __init__(self, Q, names, category=None): """ self._quadratic_form = Q R = Q.base_ring() - if category is None: - category = GradedAlgebrasWithBasis(R) + category = AlgebrasWithBasis(R.category()).Super().Filtered().or_subcategory(category) indices = SubsetsSorted(range(Q.dim())) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -550,7 +555,8 @@ def _repr_(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: CliffordAlgebra(Q) - The Clifford algebra of the Quadratic form in 3 variables over Integer Ring with coefficients: + The Clifford algebra of the Quadratic form in 3 variables + over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -826,7 +832,7 @@ def quadratic_form(self): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.quadratic_form() - Quadratic form in 3 variables over Integer Ring with coefficients: + Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 2 3 ] [ * 4 5 ] [ * * 6 ] @@ -837,19 +843,8 @@ def degree_on_basis(self, m): r""" Return the degree of the monomial indexed by ``m``. - This degree is a nonnegative integer, and should be interpreted - as a residue class modulo `2`, since we consider ``self`` to be - `\ZZ_2`-graded (not `\ZZ`-graded, although there is a natural - *filtration* by the length of ``m``). The degree of the monomial - ``m`` in this `\ZZ_2`-grading is defined to be the length of ``m`` - taken mod `2`. - - .. WARNING: - - On the :class:`ExteriorAlgebra` class (which inherits from - :class:`CliffordAlgebra`), the :meth:`degree_on_basis` - method is overridden to return an actual `\NN`-degree. So - don't count on this method always returning `0` or `1` !! + We are considering the Clifford algebra to be `\NN`-filtered, + and the degree of the monomial ``m`` is the length of ``m``. EXAMPLES:: @@ -858,9 +853,22 @@ def degree_on_basis(self, m): sage: Cl.degree_on_basis((0,)) 1 sage: Cl.degree_on_basis((0,1)) - 0 + 2 """ - return len(m) % ZZ(2) + return ZZ(len(m)) + + def graded_algebra(self): + """ + Return the associated graded algebra of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: Cl.graded_algebra() + The exterior algebra of rank 3 over Integer Ring + """ + return ExteriorAlgebra(self.base_ring(), self.variable_names()) @cached_method def free_module(self): @@ -1051,7 +1059,7 @@ def lift_module_morphism(self, m, names=None): remove_zeros=True ) for i in x) return Cl.module_morphism(on_basis=f, codomain=self, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) def lift_isometry(self, m, names=None): r""" @@ -1116,7 +1124,7 @@ def lift_isometry(self, m, names=None): remove_zeros=True ) for i in x) return self.module_morphism(on_basis=f, codomain=Cl, - category=GradedAlgebrasWithBasis(self.base_ring())) + category=AlgebrasWithBasis(self.base_ring()).Super()) # This is a general method for finite dimensional algebras with bases # and should be moved to the corresponding category once there is @@ -1339,7 +1347,7 @@ class ExteriorAlgebra(CliffordAlgebra): `Q(v) = 0` for all vectors `v \in V`. See :class:`CliffordAlgebra` for the notion of a Clifford algebra. - The exterior algebra of an `R`-module `V` is a `\ZZ`-graded connected + The exterior algebra of an `R`-module `V` is a connected `\ZZ`-graded Hopf superalgebra. It is commutative in the super sense (i.e., the odd elements anticommute and square to `0`). @@ -1353,10 +1361,6 @@ class ExteriorAlgebra(CliffordAlgebra): Hopf superalgebra with the odd-degree components forming the odd part. So use Hopf-algebraic methods with care! - .. TODO:: - - Add a category for Hopf superalgebras (perhaps part of :trac:`16513`). - INPUT: - ``R`` -- the base ring, *or* the free module whose exterior algebra @@ -1417,9 +1421,13 @@ def __init__(self, R, names): EXAMPLES:: sage: E. = ExteriorAlgebra(QQ) + sage: E.category() + Category of finite dimensional super hopf algebras with basis + over Rational Field sage: TestSuite(E).run() """ - CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, GradedHopfAlgebrasWithBasis(R)) + cat = HopfAlgebrasWithBasis(R).Super() + CliffordAlgebra.__init__(self, QuadraticForm(R, len(names)), names, cat) # TestSuite will fail if the HopfAlgebra classes will ever have tests for # the coproduct being an algebra morphism -- since this is really a # Hopf superalgebra, not a Hopf algebra. @@ -1563,7 +1571,7 @@ def lift_morphism(self, phi, names=None): f = lambda x: E.prod(E._from_dict( {(j,): phi[j,i] for j in range(n)}, remove_zeros=True ) for i in x) - return self.module_morphism(on_basis=f, codomain=E, category=GradedAlgebrasWithBasis(R)) + return self.module_morphism(on_basis=f, codomain=E, category=AlgebrasWithBasis(R).Super()) def volume_form(self): """ diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py new file mode 100644 index 00000000000..9c00304eea7 --- /dev/null +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -0,0 +1,253 @@ +""" +Free Zinbiel Algebras + +AUTHORS: + +- Travis Scrimshaw (2015-09): initial version +""" + +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.magmatic_algebras import MagmaticAlgebras +from sage.categories.rings import Rings +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.words.words import Words +from sage.combinat.words.alphabet import Alphabet +from sage.sets.family import Family + +class FreeZinbielAlgebra(CombinatorialFreeModule): + r""" + The free Zinbiel algebra on `n` generators. + + Let `R` be a ring. A *Zinbiel algebra* is a non-associative + algebra with multiplication `\circ` that satisfies + + .. MATH:: + + a \circ (b \circ c) = a \circ (b \circ c) + a \circ (c \circ b). + + Zinbiel algebras were first introduced by Loday as the Koszul + dual to Leibniz algebras (hence the name coined by Lemaire). + + Zinbiel algebras are divided power algebras, in that for + + .. MATH:: + + x^{\circ n} = \bigl(x \circ (x \circ \cdots \circ( x \circ x) \cdots + ) \bigr) + + we have + + .. MATH:: + + x^{\circ m} \circ x^{\circ n} = \binom{n+m-1}{m} x^{n+m} + + and + + .. MATH:: + + \underbrace{\bigl( ( x \circ \cdots \circ x \circ (x \circ x) \cdots + ) \bigr)}_{n+1 \text{ times}} = n! x^n. + + .. NOTE:: + + This implies that Zinbiel algebras are not power associative. + + To every Zinbiel algebra, we can construct a corresponding commutative + associative algebra by using the symmetrized product: + + .. MATH:: + + a * b = a \circ b + b \circ a. + + The free Zinbiel algebra on `n` generators is isomorphic as `R`-modules + to the reduced tensor algebra `\bar{T}(R^n)` with the product + + .. MATH:: + + (x_0 x_1 \cdots x_p) \circ (x_{p+1} x_{p+2} \cdots x_{p+q}) + = \sum_{\sigma \in S_{p,q}} x_0 (x_{\sigma(1)} x_{\sigma(2)} + \cdots x_{\sigma(p+q)}, + + where `S_{p,q}` is the set of `(p,q)`-shuffles. + + The free Zinbiel algebra is free as a divided power algebra. Moreover, + the corresponding commutative algebra is isomorphic to the (non-unital) + shuffle algebra. + + INPUT: + + - ``R`` -- a ring + - ``n`` -- (optional) the number of generators + - ``names`` -- the generator names + + .. WARNING:: + + Currently the basis is indexed by all words over the variables, + incuding the empty word. This is a slight abuse as it is suppose + to be the indexed by all non-empty words. + + EXAMPLES: + + We create the free Zinbiel algebra and check the defining relation:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z + Z[xyz] + Z[xzy] + sage: x*(y*z) + x*(z*y) + Z[xyz] + Z[xzy] + + We see that the Zinbiel algebra is not associative, nor even + power associative:: + + sage: x*(y*z) + Z[xyz] + sage: x*(x*x) + Z[xxx] + sage: (x*x)*x + 2*Z[xxx] + + We verify that it is a divided powers algebra:: + + sage: (x*(x*x)) * (x*(x*(x*x))) + 15*Z[xxxxxxx] + sage: binomial(3+4-1,4) + 15 + sage: (x*(x*(x*x))) * (x*(x*x)) + 20*Z[xxxxxxx] + sage: binomial(3+4-1,3) + 20 + sage: ((x*x)*x)*x + 6*Z[xxxx] + sage: (((x*x)*x)*x)*x + 24*Z[xxxxx] + + REFERENCES: + + - :wikipedia:`Zinbiel_algebra` + + .. [Loday95] Jean-Louis Loday. + *Cup-product for Leibniz cohomology and dual Leibniz algebras*. + Math. Scand., pp. 189--196 (1995). + http://www.math.uiuc.edu/K-theory/0015/cup_product.pdf + .. [LV12] Jean-Louis Loday and Bruno Vallette. *Algebraic Operads*. + Springer-Verlag Berlin Heidelberg (2012). + :doi:`10.1007/978-3-642-30362-3`. + """ + @staticmethod + def __classcall_private__(cls, R, n=None, names=None): + """ + Standardize input to ensure a unqiue representation. + + TESTS:: + + sage: Z1. = algebras.FreeZinbiel(QQ) + sage: Z2. = algebras.FreeZinbiel(QQ, 3) + sage: Z3 = algebras.FreeZinbiel(QQ, 3, 'x,y,z') + sage: Z4. = algebras.FreeZinbiel(QQ, 'x,y,z') + sage: Z1 is Z2 and Z1 is Z3 and Z1 is Z4 + True + """ + if isinstance(n, (list,tuple)): + names = n + n = len(names) + elif isinstance(n, str): + names = n.split(',') + n = len(names) + elif isinstance(names, str): + names = names.split(',') + elif n is None: + n = len(names) + return super(FreeZinbielAlgebra, cls).__classcall__(cls, R, n, tuple(names)) + + def __init__(self, R, n, names): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: TestSuite(Z).run() + """ + if R not in Rings: + raise TypeError("argument R must be a ring") + indices = Words(Alphabet(n, names=names)) + cat = MagmaticAlgebras(R).WithBasis() + self._n = n + CombinatorialFreeModule.__init__(self, R, indices, prefix='Z', + category=cat) + self._assign_names(names) + + def _repr_term(self, t): + """ + Return a string representation of the basis element indexed by ``t``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z._repr_term(Z._indices('xyzxxy')) + 'Z[xyzxxy]' + """ + return "{!s}[{!s}]".format(self._print_options['prefix'], repr(t)[6:]) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z + Free Zinbiel algebra on generators (Z[x], Z[y], Z[z]) over Rational Field + """ + return "Free Zinbiel algebra on generators {} over {}".format( + self.gens(), self.base_ring()) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: list(Z.algebra_generators()) + [Z[x], Z[y], Z[z]] + """ + A = self.variable_names() + return Family( A, lambda g: self.monomial(self._indices(g)) ) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: Z.gens() + (Z[x], Z[y], Z[z]) + """ + return tuple(self.algebra_generators()) + + def product_on_basis(self, x, y): + """ + Return the product of the basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: Z. = algebras.FreeZinbiel(QQ) + sage: (x*y)*z # indirect doctest + Z[xyz] + Z[xzy] + """ + if not x: + return self.monomial(y) + x0 = self._indices(x[0]) + return self.sum_of_monomials(x0 + sh for sh in x[1:].shuffle(y)) + diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index 366b6bd99f2..d94fbd37f9d 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -434,6 +434,23 @@ def list(self): return sorted(self.__monomials.items(), key=lambda x: (-sum(x[0][1]), x[0][1], -sum(x[0][0]), x[0][0]) ) + def support(self): + """ + Return the support of ``self``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = dy - (3*x - z)*dx + 1 + sage: elt.support() + [((0, 0, 0), (0, 1, 0)), + ((1, 0, 0), (1, 0, 0)), + ((0, 0, 0), (0, 0, 0)), + ((0, 0, 1), (1, 0, 0))] + """ + return self.__monomials.keys() + # This is essentially copied from # sage.combinat.free_module.CombinatorialFreeModuleElement def __div__(self, x, self_on_left=False): @@ -527,6 +544,11 @@ class DifferentialWeylAlgebra(Algebra, UniqueRepresentation): sage: W. = DifferentialWeylAlgebra(QQ); W Differential Weyl algebra of polynomials in a, b over Rational Field + + .. TODO:: + + Implement the :meth:`graded_algebra` as a polynomial ring once + they are considered to be graded rings (algebras). """ @staticmethod def __classcall__(cls, R, names=None): @@ -567,7 +589,12 @@ def __init__(self, R, names=None): raise ValueError("variable names cannot differ by a leading 'd'") # TODO: Make this into a filtered algebra under the natural grading of # x_i and dx_i have degree 1 - Algebra.__init__(self, R, names, category=AlgebrasWithBasis(R).NoZeroDivisors()) + # Filtered is not included because it is a supercategory of super + if R.is_field(): + cat = AlgebrasWithBasis(R).NoZeroDivisors().Super() + else: + cat = AlgebrasWithBasis(R).Super() + Algebra.__init__(self, R, names, category=cat) def _repr_(self): r""" @@ -661,6 +688,24 @@ def _coerce_map_from_(self, R): and self.base_ring().has_coerce_map_from(R.base_ring()) ) return super(DifferentialWeylAlgebra, self)._coerce_map_from_(R) + def degree_on_basis(self, i): + """ + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: W.degree_on_basis( ((1, 3, 2), (0, 1, 3)) ) + 10 + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = y*dy - (3*x - z)*dx + sage: elt.degree() + 2 + """ + return sum(i[0]) + sum(i[1]) + def polynomial_ring(self): """ Return the associated polynomial ring of ``self``. diff --git a/src/sage/all.py b/src/sage/all.py index 95e0c36e8b5..3ddfe089bff 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -106,6 +106,7 @@ from sage.monoids.all import * from sage.algebras.all import * from sage.modular.all import * +from sage.sat.all import * from sage.schemes.all import * from sage.graphs.all import * from sage.groups.all import * diff --git a/src/sage/categories/algebras.py b/src/sage/categories/algebras.py index 5e81cee137a..8e1c4f9a1f2 100644 --- a/src/sage/categories/algebras.py +++ b/src/sage/categories/algebras.py @@ -109,7 +109,9 @@ def Semisimple(self): return self & SemisimpleAlgebras(self.base_ring()) Commutative = LazyImport('sage.categories.commutative_algebras', 'CommutativeAlgebras', at_startup=True) + Filtered = LazyImport('sage.categories.filtered_algebras', 'FilteredAlgebras') Graded = LazyImport('sage.categories.graded_algebras', 'GradedAlgebras') + Super = LazyImport('sage.categories.super_algebras', 'SuperAlgebras') WithBasis = LazyImport('sage.categories.algebras_with_basis', 'AlgebrasWithBasis') #if/when Semisimple becomes an axiom Semisimple = LazyImport('sage.categories.semisimple_algebras', 'SemisimpleAlgebras') diff --git a/src/sage/categories/algebras_with_basis.py b/src/sage/categories/algebras_with_basis.py index d4b63827895..a490faf7c31 100644 --- a/src/sage/categories/algebras_with_basis.py +++ b/src/sage/categories/algebras_with_basis.py @@ -117,8 +117,10 @@ def example(self, alphabet = ('a','b','c')): from sage.categories.examples.algebras_with_basis import Example return Example(self.base_ring(), alphabet) + Filtered = LazyImport('sage.categories.filtered_algebras_with_basis', 'FilteredAlgebrasWithBasis') FiniteDimensional = LazyImport('sage.categories.finite_dimensional_algebras_with_basis', 'FiniteDimensionalAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_algebras_with_basis', 'GradedAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_algebras_with_basis', 'SuperAlgebrasWithBasis') class ParentMethods: diff --git a/src/sage/categories/bialgebras.py b/src/sage/categories/bialgebras.py index e78f080ef18..e779d8acb08 100644 --- a/src/sage/categories/bialgebras.py +++ b/src/sage/categories/bialgebras.py @@ -11,6 +11,7 @@ from sage.categories.category_types import Category_over_base_ring from sage.categories.all import Algebras, Coalgebras +from sage.categories.super_modules import SuperModulesCategory class Bialgebras(Category_over_base_ring): """ @@ -56,8 +57,6 @@ def additional_structure(self): """ return None - class ParentMethods: + class Super(SuperModulesCategory): pass - class ElementMethods: - pass diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index d338cc4e2cc..9a80ddff627 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -2257,6 +2257,8 @@ def _repr_object_names_static(category, axioms): result = result.replace(" over ", " with basis over ", 1) elif axiom == "Connected" and "graded " in result: result = result.replace("graded ", "graded connected ", 1) + elif axiom == "Connected" and "filtered " in result: + result = result.replace("filtered ", "filtered connected ", 1) elif axiom == "Endset" and "homsets" in result: # Without the space at the end to handle Homsets().Endset() result = result.replace("homsets", "endsets", 1) diff --git a/src/sage/categories/coalgebras.py b/src/sage/categories/coalgebras.py index a4ebc8fc0af..a5fdc492111 100644 --- a/src/sage/categories/coalgebras.py +++ b/src/sage/categories/coalgebras.py @@ -13,6 +13,7 @@ from sage.categories.all import Modules from sage.categories.tensor import TensorProductsCategory, tensor from sage.categories.dual import DualObjectsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.categories.realizations import RealizationsCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.misc.abstract_method import abstract_method @@ -50,19 +51,6 @@ class ParentMethods: # # Will declare the coproduct of self to the coercion mechanism when it exists # pass - @cached_method - def tensor_square(self): - """ - Returns the tensor square of ``self`` - - EXAMPLES:: - - sage: A = HopfAlgebrasWithBasis(QQ).example() - sage: A.tensor_square() - An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field # An example of Hopf algebra with basis: the group algebra of the Dihedral group of order 6 as a permutation group over Rational Field - """ - return tensor([self, self]) - @abstract_method def counit(self, x): """ @@ -192,6 +180,31 @@ def extra_super_categories(self): from sage.categories.algebras import Algebras return [Algebras(self.base_category().base_ring())] + class Super(SuperModulesCategory): + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Coalgebras(ZZ).Super().extra_super_categories() + [Join of Category of graded modules over Integer Ring + and Category of coalgebras over Integer Ring] + sage: Coalgebras(ZZ).Super().super_categories() + [Category of super modules over Integer Ring, + Category of coalgebras over Integer Ring] + + Compare this with the situation for bialgebras:: + + sage: Bialgebras(ZZ).Super().extra_super_categories() + [] + sage: Bialgebras(ZZ).Super().super_categories() + [Category of super algebras over Integer Ring, + Category of super coalgebras over Integer Ring] + + The category of bialgebras does not occur in these results, + since super bialgebras are not bialgebras. + """ + return [self.base_category().Graded()] + class WithRealizations(WithRealizationsCategory): class ParentMethods: diff --git a/src/sage/categories/coalgebras_with_basis.py b/src/sage/categories/coalgebras_with_basis.py index 0b9ff24f0ec..ddcaad16d26 100644 --- a/src/sage/categories/coalgebras_with_basis.py +++ b/src/sage/categories/coalgebras_with_basis.py @@ -13,6 +13,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.all import ModulesWithBasis, tensor, Hom +from sage.categories.super_modules import SuperModulesCategory class CoalgebrasWithBasis(CategoryWithAxiom_over_base_ring): """ @@ -129,3 +130,5 @@ def counit(self): class ElementMethods: pass + class Super(SuperModulesCategory): + pass diff --git a/src/sage/categories/examples/filtered_algebras_with_basis.py b/src/sage/categories/examples/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..9e7765aefa8 --- /dev/null +++ b/src/sage/categories/examples/filtered_algebras_with_basis.py @@ -0,0 +1,178 @@ +r""" +Examples of filtered algebra with basis +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_algebras_with_basis import FilteredAlgebrasWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid +from sage.sets.family import Family + +class PBWBasisCrossProduct(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered algebra + with basis: the universal enveloping algebra of the Lie algebra + of `\RR^3` under the cross product. + + The Lie algebra is generated by `x,y,z` with brackets defined by + `[x, y] = z`, `[y, z] = x`, and `[x, z] = -y`. The universal enveloping + algebra has a (PBW) basis consisting of monomials `x^i y^j z^k`. + Despite these monomials not commuting with each other, we + nevertheless label them by the elements of the free abelian monoid + on three generators. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A set of algebra generators -- the set of generators `x,y,z`. + + - The index of the unit element -- the unit element in the monoid + of monomials. + + - A product -- this is given on basis elements by using + :meth:`product_on_basis`. + + - A degree function -- this is determined on the basis elements + by using :meth:`degree_on_basis` which returns the sum of exponents + of the monomial. + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: TestSuite(A).run(elements=[x*y+z]) + """ + I = IndexedFreeAbelianMonoid(['x', 'y', 'z'], prefix='U') + gen_cmp = lambda x,y: cmp((-len(x), x.to_word_list()), (-len(y), y.to_word_list())) + CombinatorialFreeModule.__init__(self, base_ring, I, bracket=False, prefix='', + generator_cmp=gen_cmp, + category=FilteredAlgebrasWithBasis(base_ring)) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered().example() + An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Rational Field + """ + return "An example of a filtered algebra with basis: the universal enveloping algebra of Lie algebra of RR^3 with cross product over {}".format(self.base_ring()) + + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: list(A.algebra_generators()) + [U['x'], U['y'], U['z']] + """ + G = self._indices.monoid_generators() + I = sorted(G.keys()) + return Family(I, lambda x: self.monomial(G[x])) + + def one_basis(self): + """ + Return the index of the unit of ``self``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: A.one_basis() + 1 + """ + return self._indices.one() + + def degree_on_basis(self, m): + """ + The degree of the basis element of ``self`` labelled by ``m``. + + INPUT: + + - ``m`` -- an element of the free abelian monoid + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x = A.algebra_generators()['x'] + sage: A.degree_on_basis((x^4).leading_support()) + 4 + sage: a = A.an_element(); a + U['x']^2*U['y']^2*U['z']^3 + sage: A.degree_on_basis(a.leading_support()) + 7 + """ + return len(m) + + # TODO: This is a general procedure of expanding multiplication defined + # on generators to arbitrary monomials and could likely be factored out + # and be useful elsewhere. + def product_on_basis(self, s, t): + """ + Return the product of two basis elements indexed by ``s`` and ``t``. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: G = A.algebra_generators() + sage: x,y,z = G['x'], G['y'], G['z'] + sage: A.product_on_basis(x.leading_support(), y.leading_support()) + U['x']*U['y'] + sage: y*x + U['x']*U['y'] - U['z'] + sage: x*y*x + U['x']^2*U['y'] - U['x']*U['z'] + sage: z*y*x + U['x']*U['y']*U['z'] - U['x']^2 + U['y']^2 - U['z']^2 + """ + if len(s) == 0: + return self.monomial(t) + if len(t) == 0: + return self.monomial(s) + if s.trailing_support() <= t.leading_support(): + return self.monomial(s*t) + + if len(t) == 1: + if len(s) == 1: + # Do the product of the generators + a = s.leading_support() + b = t.leading_support() + cur = self.monomial(s*t) + if a <= b: + return cur + if a == 'z': + if b == 'y': + return cur - self.monomial(self._indices.gen('x')) + # b == 'x' + return cur + self.monomial(self._indices.gen('y')) + # a == 'y' and b == 'x' + return cur - self.monomial(self._indices.gen('z')) + + cur = self.monomial(t) + for a in reversed(s.to_word_list()): + cur = self.monomial(self._indices.gen(a)) * cur + return cur + + cur = self.monomial(s) + for a in t.to_word_list(): + cur = cur * self.monomial(self._indices.gen(a)) + return cur + +Example = PBWBasisCrossProduct + diff --git a/src/sage/categories/examples/filtered_modules_with_basis.py b/src/sage/categories/examples/filtered_modules_with_basis.py new file mode 100644 index 00000000000..9954d6a2ff5 --- /dev/null +++ b/src/sage/categories/examples/filtered_modules_with_basis.py @@ -0,0 +1,151 @@ +r""" +Examples of filtered modules with basis +""" +#***************************************************************************** +# Copyright (C) 2013 Frederic Chapoton +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.partition import Partitions + + +class FilteredPartitionModule(CombinatorialFreeModule): + r""" + This class illustrates an implementation of a filtered module + with basis: the free module on the set of all partitions. + + INPUT: + + - ``R`` -- base ring + + The implementation involves the following: + + - A choice of how to represent elements. In this case, the basis + elements are partitions. The algebra is constructed as a + :class:`CombinatorialFreeModule + ` on the + set of partitions, so it inherits all of the methods for such + objects, and has operations like addition already defined. + + :: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + + - If the algebra is called ``A``, then its basis function is + stored as ``A.basis``. Thus the function can be used to + find a basis for the degree `d` piece: essentially, just call + ``A.basis(d)``. More precisely, call ``x`` for + each ``x`` in ``A.basis(d)``. + + :: + + sage: [m for m in A.basis(4)] + [P[4], P[3, 1], P[2, 2], P[2, 1, 1], P[1, 1, 1, 1]] + + - For dealing with basis elements: :meth:`degree_on_basis`, and + :meth:`_repr_term`. The first of these defines the degree of any + monomial, and then the :meth:`degree + ` method for elements -- + see the next item -- uses it to compute the degree for a linear + combination of monomials. The last of these determines the + print representation for monomials, which automatically produces + the print representation for general elements. + + :: + + sage: A.degree_on_basis(Partition([4,3])) + 7 + sage: A._repr_term(Partition([4,3])) + 'P[4, 3]' + + - There is a class for elements, which inherits from + :class:`CombinatorialFreeModuleElement + `. An + element is determined by a dictionary whose keys are partitions and whose + corresponding values are the coefficients. The class implements + two things: an :meth:`is_homogeneous + ` method and a + :meth:`degree ` method. + + :: + + sage: p = A.monomial(Partition([3,2,1])); p + P[3, 2, 1] + sage: p.is_homogeneous() + True + sage: p.degree() + 6 + """ + def __init__(self, base_ring): + """ + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example(); A + An example of a filtered module with basis: the free module on partitions over Rational Field + sage: TestSuite(A).run() + """ + CombinatorialFreeModule.__init__(self, base_ring, Partitions(), + category=FilteredModulesWithBasis(base_ring)) + + # FIXME: this is currently required, because the implementation of ``basis`` + # in CombinatorialFreeModule overrides that of GradedModulesWithBasis + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] + + # This could be a default implementation + def degree_on_basis(self, t): + """ + The degree of the basis element indexed by the partition ``t`` + in this filtered module. + + INPUT: + + - ``t`` -- the index of an element of the basis of this module, + i.e. a partition + + OUTPUT: an integer, the degree of the corresponding basis element + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + sage: type(A.degree_on_basis(Partition((1,1)))) + + """ + return t.size() + + def _repr_(self): + """ + Print representation of ``self``. + + EXAMPLES:: + + sage: ModulesWithBasis(QQ).Filtered().example() # indirect doctest + An example of a filtered module with basis: the free module on partitions over Rational Field + """ + return "An example of a filtered module with basis: the free module on partitions over %s" % self.base_ring() + + def _repr_term(self, t): + """ + Print representation for the basis element represented by the + partition ``t``. + + This governs the behavior of the print representation of all elements + of the algebra. + + EXAMPLES:: + + sage: A = ModulesWithBasis(QQ).Filtered().example() + sage: A._repr_term(Partition((4,2,1))) + 'P[4, 2, 1]' + """ + return 'P' + t._repr_() + +Example = FilteredPartitionModule + diff --git a/src/sage/categories/examples/graded_modules_with_basis.py b/src/sage/categories/examples/graded_modules_with_basis.py index ebd05587f55..e0f95880199 100644 --- a/src/sage/categories/examples/graded_modules_with_basis.py +++ b/src/sage/categories/examples/graded_modules_with_basis.py @@ -9,6 +9,7 @@ #***************************************************************************** from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.filtered_modules_with_basis import FilteredModulesWithBasis from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.partition import Partitions @@ -20,7 +21,7 @@ class GradedPartitionModule(CombinatorialFreeModule): INPUT: - - ``R`` - base ring + - ``R`` -- base ring The implementation involves the following: @@ -106,7 +107,7 @@ def __init__(self, base_ring): # FIXME: this is currently required, because the implementation of ``basis`` # in CombinatorialFreeModule overrides that of GradedModulesWithBasis - basis = GradedModulesWithBasis.ParentMethods.__dict__['basis'] + basis = FilteredModulesWithBasis.ParentMethods.__dict__['basis'] # This could be a default implementation def degree_on_basis(self, t): diff --git a/src/sage/categories/filtered_algebras.py b/src/sage/categories/filtered_algebras.py new file mode 100644 index 00000000000..321dd604ed3 --- /dev/null +++ b/src/sage/categories/filtered_algebras.py @@ -0,0 +1,62 @@ +r""" +Filtered Algebras +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebras(FilteredModulesCategory): + r""" + The category of filtered algebras. + + An algebra `A` over a commutative ring `R` is *filtered* if + `A` is endowed with a structure of a filtered `R`-module + (whose underlying `R`-module structure is identical with + that of the `R`-algebra `A`) such that the indexing set `I` + (typically `I = \NN`) is also an additive abelian monoid, + the unity `1` of `A` belongs to `F_0`, and we have + `F_i \cdot F_j \subseteq F_{i+j}` for all `i, j \in I`. + + EXAMPLES:: + + sage: Algebras(ZZ).Filtered() + Category of filtered algebras over Integer Ring + sage: Algebras(ZZ).Filtered().super_categories() + [Category of algebras over Integer Ring, + Category of filtered modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtered_algebra` + """ + class ParentMethods: + @abstract_method(optional=True) + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + .. TODO:: + + Implement a version of the associated graded algebra + which does not require ``self`` to have a + distinguished basis. + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + diff --git a/src/sage/categories/filtered_algebras_with_basis.py b/src/sage/categories/filtered_algebras_with_basis.py new file mode 100644 index 00000000000..053d119a040 --- /dev/null +++ b/src/sage/categories/filtered_algebras_with_basis.py @@ -0,0 +1,541 @@ +r""" +Filtered Algebras With Basis + +A filtered algebra with basis over a commutative ring `R` +is a filtered algebra over `R` endowed with the structure +of a filtered module with basis (with the same underlying +filtered-module structure). See +:class:`~sage.categories.filtered_algebras.FilteredAlgebras` and +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for these two notions. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory + +class FilteredAlgebrasWithBasis(FilteredModulesCategory): + """ + The category of filtered algebras with a distinguished + homogeneous basis. + + A filtered algebra with basis over a commutative ring `R` + is a filtered algebra over `R` endowed with the structure + of a filtered module with basis (with the same underlying + filtered-module structure). See + :class:`~sage.categories.filtered_algebras.FilteredAlgebras` and + :class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` + for these two notions. + + EXAMPLES:: + + sage: C = AlgebrasWithBasis(ZZ).Filtered(); C + Category of filtered algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of algebras with basis over Integer Ring, + Category of filtered algebras over Integer Ring, + Category of filtered modules with basis over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered algebra ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded algebra -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But many instances of :class:`FilteredAlgebrasWithBasis` + override this method, as the associated graded algebra + often is (isomorphic) to a simpler object (for instance, + the associated graded algebra of a graded algebra can be + identified with the graded algebra itself). Generic code + that uses associated graded algebras (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion`, and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + .. TODO:: + + Maybe the thing about the conversion from ``self`` + to ``self.graded_algebra()`` on the Clifford at least + could be made to work? (I would still warn the user + against ASSUMING that it must work -- as there is + probably no way to guarantee it in all cases, and + we shouldn't require users to mess with + element constructors.) + + EXAMPLES:: + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Algebra of An example of a filtered algebra with basis: + the universal enveloping algebra of + Lie algebra of RR^3 with cross product over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + bar(U['x']) + 2*bar(1) + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules, not of algebras. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.to_graded_conversion()(p) + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Algebras(QQ).WithBasis().Filtered().example() + sage: p = A.an_element() + A.algebra_generators()['x'] + 2; p + U['x']^2*U['y']^2*U['z']^3 + U['x'] + 2 + sage: q = A.projection(7)(p); q + bar(U['x']^2*U['y']^2*U['z']^3) + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(8)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + algebras of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered algebras with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the universal enveloping algebra of the + Lie algebra `\RR^3` (with the cross product serving as + Lie bracket):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example(); A + An example of a filtered algebra with basis: the + universal enveloping algebra of Lie algebra of RR^3 + with cross product over Rational Field + sage: M = A.indices(); M + Free abelian monoid indexed by {'x', 'y', 'z'} + sage: x,y,z = [A.basis()[M.gens()[i]] for i in "xyz"] + + Let us define a stupid filtered map from ``A`` to + itself:: + + sage: def map_on_basis(m): + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (y ** (i+j)) * (z ** k) + ....: if i > 0: + ....: g += i * (x ** (i-1)) * (y ** j) * (z ** k) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(x) + U['y'] + 1 + sage: f(x*y*z) + U['y']^2*U['z'] + U['y']*U['z'] + sage: f(x*x*y*z) + U['y']^3*U['z'] + 2*U['x']*U['y']*U['z'] + sage: f(A.one()) + 1 + sage: f(y*z) + U['y']*U['z'] + + (There is nothing here that is peculiar to this + universal enveloping algebra; we are only using its + module structure, and we could just as well be using + a polynomial algebra in its stead.) + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Algebra of An example of a filtered algebra with + basis: the universal enveloping algebra of Lie algebra + of RR^3 with cross product over Rational Field + sage: xx, yy, zz = [A.to_graded_conversion()(i) for i in [x, y, z]] + sage: xx+yy*zz + bar(U['y']*U['z']) + bar(U['x']) + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Algebra of An example + of a filtered algebra with basis: the universal + enveloping algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(xx) + bar(U['y']) + sage: grf(xx*yy*zz) + bar(U['y']^2*U['z']) + sage: grf(xx*xx*yy*zz) + bar(U['y']^3*U['z']) + sage: grf(grA.one()) + bar(1) + sage: grf(yy*zz) + bar(U['y']*U['z']) + sage: grf(yy*zz-2*yy) + bar(U['y']*U['z']) - 2*bar(U['y']) + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(m): # redefining map_on_basis + ....: d = m.dict() + ....: i = d.get('x', 0); j = d.get('y', 0); k = d.get('z', 0) + ....: g = (h[1] ** i) * (h[2] ** (floor(j/2))) * (h[3] ** (floor(k/3))) + ....: g += i * (h[1] ** (i+j+k)) + ....: return g + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(x) + 2*h[1] + sage: f(y) + h[] + sage: f(z) + h[] + sage: f(y**2) + h[2] + sage: f(x**2) + 3*h[1, 1] + sage: f(x*y*z) + h[1] + h[1, 1, 1] + sage: f(x*x*y*y*z) + 2*h[1, 1, 1, 1, 1] + h[2, 1, 1] + sage: f(A.one()) + h[] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(xx) + 2*h[1] + sage: grf(yy) + 0 + sage: grf(zz) + 0 + sage: grf(yy**2) + h[2] + sage: grf(xx**2) + 3*h[1, 1] + sage: grf(xx*yy*zz) + h[1, 1, 1] + sage: grf(xx*xx*yy*yy*zz) + 2*h[1, 1, 1, 1, 1] + sage: grf(grA.one()) + h[] + + **Example 3.** + + After having had a graded algebra as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return x ** (sum(lam)) + y ** (len(lam)) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + U['x'] + U['y'] + sage: f(h[2]) + U['x']^2 + U['y'] + sage: f(h[1, 1]) + U['x']^2 + U['y']^2 + sage: f(h[2, 2]) + U['x']^4 + U['y']^2 + sage: f(h[3, 2, 1]) + U['x']^6 + U['y']^3 + sage: f(h.one()) + 2 + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Algebra of An example of a filtered + algebra with basis: the universal enveloping + algebra of Lie algebra of RR^3 with cross + product over Rational Field + sage: grf(h[1]) + bar(U['x']) + bar(U['y']) + sage: grf(h[2]) + bar(U['x']^2) + sage: grf(h[1, 1]) + bar(U['x']^2) + bar(U['y']^2) + sage: grf(h[2, 2]) + bar(U['x']^4) + sage: grf(h[3, 2, 1]) + bar(U['x']^6) + sage: grf(h.one()) + 2*bar(1) + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded algebras. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h[lam] + h[len(lam)] + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + 2*h[1] + sage: f(h[2]) + h[1] + h[2] + sage: f(h[1, 1]) + h[1, 1] + h[2] + sage: f(h[2, 1]) + h[2] + h[2, 1] + sage: f(h.one()) + 2*h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + 2*h[1] + sage: grf(h[2]) + h[2] + sage: grf(h[1, 1]) + h[1, 1] + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + 2*h[] + + **Example 5.** + + For another example, let us compute `\operatorname{gr} f` for a + map `f` between two Clifford algebras:: + + sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) + sage: B = CliffordAlgebra(Q, names=['u','v']); B + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 1 2 ] + [ * 3 ] + sage: m = Matrix(ZZ, [[1, 2], [1, -1]]) + sage: f = B.lift_module_morphism(m, names=['x','y']) + sage: A = f.domain(); A + The Clifford algebra of the Quadratic form in 2 + variables over Integer Ring with coefficients: + [ 6 0 ] + [ * 3 ] + sage: x, y = A.gens() + sage: f(x) + u + v + sage: f(y) + 2*u - v + sage: f(x**2) + 6 + sage: f(x*y) + -3*u*v + 3 + sage: grA = A.graded_algebra(); grA + The exterior algebra of rank 2 over Integer Ring + sage: A.to_graded_conversion()(x) + x + sage: A.to_graded_conversion()(y) + y + sage: A.to_graded_conversion()(x*y) + x^y + sage: u = A.to_graded_conversion()(x*y+1); u + x^y + 1 + sage: A.from_graded_conversion()(u) + x*y + 1 + sage: A.projection(2)(x*y+1) + x^y + sage: A.projection(1)(x+2*y-2) + x + 2*y + sage: grf = A.induced_graded_map(B, f); grf + Generic morphism: + From: The exterior algebra of rank 2 over Integer Ring + To: The exterior algebra of rank 2 over Integer Ring + sage: grf(A.to_graded_conversion()(x)) + u + v + sage: grf(A.to_graded_conversion()(y)) + 2*u - v + sage: grf(A.to_graded_conversion()(x**2)) + 6 + sage: grf(A.to_graded_conversion()(x*y)) + -3*u^v + sage: grf(grA.one()) + 1 + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + pass + diff --git a/src/sage/categories/filtered_modules.py b/src/sage/categories/filtered_modules.py new file mode 100644 index 00000000000..8a9a79d6c08 --- /dev/null +++ b/src/sage/categories/filtered_modules.py @@ -0,0 +1,161 @@ +r""" +Filtered Modules + +A *filtered module* over a ring `R` with a totally ordered +indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped +with a family `(F_i)_{i \in I}` of `R`-submodules satisfying +`F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and +`M = \bigcup_{i \in I} F_i`. This family is called a *filtration* +of the given module `M`. + +.. TODO:: + + Implement a notion for decreasing filtrations: where `F_j \subseteq F_i` + when `i \leq j`. + +.. TODO:: + + Implement filtrations for all concrete categories. + +.. TODO:: + + Implement `\operatorname{gr}` as a functor. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory + +class FilteredModulesCategory(RegressiveCovariantConstructionCategory, Category_over_base_ring): + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Filtered() + sage: C + Category of filtered algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of algebras over Rational Field, + Category of filtered modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Filtered().base_ring() + Rational Field + """ + super(FilteredModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Filtered" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Filtered() # indirect doctest + Category of filtered algebras with basis over Rational Field + """ + return "filtered {}".format(self.base_category()._repr_object_names()) + +class FilteredModules(FilteredModulesCategory): + r""" + The category of filtered modules over a given ring `R`. + + A *filtered module* over a ring `R` with a totally ordered + indexing set `I` (typically `I = \NN`) is an `R`-module `M` equipped + with a family `(F_i)_{i \in I}` of `R`-submodules satisfying + `F_i \subseteq F_j` for all `i,j \in I` having `i \leq j`, and + `M = \bigcup_{i \in I} F_i`. This family is called a *filtration* + of the given module `M`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + sage: Modules(ZZ).Filtered().super_categories() + [Category of modules over Integer Ring] + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered()).run() + + REFERENCES: + + - :wikipedia:`Filtration_(mathematics)` + """ + def extra_super_categories(self): + r""" + Add :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Filtered().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Filtered().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Filtered()`` returns an + instance of :class:`FilteredModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Filtered()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class SubcategoryMethods: + + @cached_method + def Connected(self): + r""" + Return the full subcategory of the connected objects of ``self``. + + A filtered `R`-module `M` with filtration + `(F_0, F_1, F_2, \ldots)` (indexed by `\NN`) + is said to be *connected* if `F_0` is isomorphic + to `R`. + + EXAMPLES:: + + sage: Modules(ZZ).Filtered().Connected() + Category of filtered connected modules over Integer Ring + sage: Coalgebras(QQ).Filtered().Connected() + Join of Category of filtered connected modules over Rational Field + and Category of coalgebras over Rational Field + sage: AlgebrasWithBasis(QQ).Filtered().Connected() + Category of filtered connected algebras with basis over Rational Field + + TESTS:: + + sage: TestSuite(Modules(ZZ).Filtered().Connected()).run() + sage: Coalgebras(QQ).Filtered().Connected.__module__ + 'sage.categories.filtered_modules' + """ + return self._with_axiom("Connected") + + class Connected(CategoryWithAxiom_over_base_ring): + pass + diff --git a/src/sage/categories/filtered_modules_with_basis.py b/src/sage/categories/filtered_modules_with_basis.py new file mode 100644 index 00000000000..7f2320f7f19 --- /dev/null +++ b/src/sage/categories/filtered_modules_with_basis.py @@ -0,0 +1,928 @@ +r""" +Filtered Modules With Basis + +A *filtered module with basis* over a ring `R` means +(for the purpose of this code) a filtered `R`-module `M` +with filtration `(F_i)_{i \in I}` (typically `I = \NN`) +endowed with a basis `(b_j)_{j \in J}` of `M` and a partition +`J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed +that some `J_i` are empty) such that for every `n \in I`, +the subfamily `(b_j)_{j \in U_n}`, where +`U_n = \bigcup_{i \leq n} J_i`, is a basis of the +`R`-submodule `F_n`. + +For every `i \in I`, the `R`-submodule of `M` spanned by +`(b_j)_{j \in J_i}` is called the `i`-*th graded component* +(aka the `i`-*th homogeneous component*) of the filtered +module with basis `M`; the elements of this submodule are +referred to as *homogeneous elements of degree* `i`. + +See the class documentation +:class:`~sage.categories.filtered_modules_with_basis.FilteredModulesWithBasis` +for further details. +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.filtered_modules import FilteredModulesCategory +from sage.misc.abstract_method import abstract_method + +class FilteredModulesWithBasis(FilteredModulesCategory): + r""" + The category of filtered modules with a distinguished basis. + + A *filtered module with basis* over a ring `R` means + (for the purpose of this code) a filtered `R`-module `M` + with filtration `(F_i)_{i \in I}` (typically `I = \NN`) + endowed with a basis `(b_j)_{j \in J}` of `M` and a partition + `J = \bigsqcup_{i \in I} J_i` of the set `J` (it is allowed + that some `J_i` are empty) such that for every `n \in I`, + the subfamily `(b_j)_{j \in U_n}`, where + `U_n = \bigcup_{i \leq n} J_i`, is a basis of the + `R`-submodule `F_n`. + + For every `i \in I`, the `R`-submodule of `M` spanned by + `(b_j)_{j \in J_i}` is called the `i`-*th graded component* + (aka the `i`-*th homogeneous component*) of the filtered + module with basis `M`; the elements of this submodule are + referred to as *homogeneous elements of degree* `i`. + The `R`-module `M` is the direct sum of its `i`-th graded + components over all `i \in I`, and thus becomes a graded + `R`-module with basis. + Conversely, any graded `R`-module with basis canonically + becomes a filtered `R`-module with basis (by defining + `F_n = \bigoplus_{i \leq n} G_i` where `G_i` is the `i`-th + graded component, and defining `J_i` as the indexing set + of the basis of the `i`-th graded component). Hence, the + notion of a filtered `R`-module with basis is equivalent + to the notion of a graded `R`-module with basis. + + However, the *category* of filtered `R`-modules with basis is not + the category of graded `R`-modules with basis. Indeed, the *morphisms* + of filtered `R`-modules with basis are defined to be morphisms of + `R`-modules which send each `F_n` of the domain to the corresponding + `F_n` of the target; in contrast, the morphisms of graded `R`-modules + with basis must preserve each homogeneous component. Also, + the notion of a filtered algebra with basis differs from + that of a graded algebra with basis. + + .. NOTE:: + + Currently, to make use of the functionality of this class, + an instance of ``FilteredModulesWithBasis`` should fulfill + the contract of a :class:`CombinatorialFreeModule` (most + likely by inheriting from it). It should also have the + indexing set `J` encoded as its ``_indices`` attribute, + and ``_indices.subset(size=i)`` should yield the subset + `J_i` (as an iterable). If the latter conditions are not + satisfied, then :meth:`basis` must be overridden. + + .. NOTE:: + + One should implement a ``degree_on_basis`` method in the parent + class in order to fully utilize the methods of this category. + This might become a required abstract method in the future. + + EXAMPLES:: + + sage: C = ModulesWithBasis(ZZ).Filtered(); C + Category of filtered modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules over Integer Ring, + Category of modules with basis over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Filtered() + True + + TESTS:: + + sage: C = ModulesWithBasis(ZZ).Filtered() + sage: TestSuite(C).run() + sage: C = ModulesWithBasis(QQ).Filtered() + sage: TestSuite(C).run() + """ + class ParentMethods: + + # TODO: which syntax do we prefer? + # A.basis(degree = 3) + # A.basis().subset(degree=3) + + # This is related to the following design question: + # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` + # be the elements of F of degree 3 or those whose index is of degree 3? + + def basis(self, d=None): + r""" + Return the basis for (the ``d``-th homogeneous component + of) ``self``. + + INPUT: + + - ``d`` -- (optional, default ``None``) nonnegative integer + or ``None`` + + OUTPUT: + + If ``d`` is ``None``, returns the basis of the module. + Otherwise, returns the basis of the homogeneous component + of degree ``d`` (i.e., the subfamily of the basis of the + whole module which consists only of the basis vectors + lying in `F_d \setminus \bigcup_{i = ExteriorAlgebra(QQ) + sage: E.basis() + Lazy family (Term map from Subsets of {0, 1} to + The exterior algebra of rank 2 over Rational Field(i))_{i in + Subsets of {0, 1}} + """ + from sage.sets.family import Family + if d is None: + return Family(self._indices, self.monomial) + else: + return Family(self._indices.subset(size=d), self.monomial) + + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + If the filtered module ``self`` with basis is called `A`, + then this method returns `\operatorname{gr} A`. The method + :meth:`to_graded_conversion` returns the canonical + `R`-module isomorphism `A \to \operatorname{gr} A` induced + by the basis of `A`, and the method + :meth:`from_graded_conversion` returns the inverse of this + isomorphism. The method :meth:`projection` projects + elements of `A` onto `\operatorname{gr} A` according to + their place in the filtration on `A`. + + .. WARNING:: + + When not overridden, this method returns the default + implementation of an associated graded module -- + namely, ``AssociatedGradedAlgebra(self)``, where + ``AssociatedGradedAlgebra`` is + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra`. + But some instances of :class:`FilteredModulesWithBasis` + override this method, as the associated graded module + often is (isomorphic) to a simpler object (for instance, + the associated graded module of a graded module can be + identified with the graded module itself). Generic code + that uses associated graded modules (such as the code + of the :meth:`induced_graded_map` method below) should + make sure to only communicate with them via the + :meth:`to_graded_conversion`, + :meth:`from_graded_conversion` and + :meth:`projection` methods (in particular, + do not expect there to be a conversion from ``self`` + to ``self.graded_algebra()``; this currently does not + work for Clifford algebras). Similarly, when + overriding :meth:`graded_algebra`, make sure to + accordingly redefine these three methods, unless their + definitions below still apply to your case (this will + happen whenever the basis of your :meth:`graded_algebra` + has the same indexing set as ``self``, and the partition + of this indexing set according to degree is the same as + for ``self``). + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: A.graded_algebra() + Graded Module of An example of a filtered module with basis: + the free module on partitions over Integer Ring + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + + # Maps + + def to_graded_conversion(self): + r""" + Return the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`from_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.module_morphism(diagonal=lambda x: base_one, + codomain=self.graded_algebra()) + + def from_graded_conversion(self): + r""" + Return the inverse of the canonical `R`-module isomorphism + `A \to \operatorname{gr} A` induced by the basis of `A` + (where `A = ` ``self``). This inverse is an isomorphism + `\operatorname{gr} A \to A`. + + This is an isomorphism of `R`-modules. See + the class documentation :class:`AssociatedGradedAlgebra`. + + .. SEEALSO:: + + :meth:`to_graded_conversion` + + EXAMPLES:: + + sage: A = Modules(QQ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.to_graded_conversion()(p); q + -4*Bbar[[]] - 4*Bbar[[1]] - 6*Bbar[[2]] + sage: A.from_graded_conversion()(q) == p + True + sage: q.parent() is A.graded_algebra() + True + """ + base_one = self.base_ring().one() + return self.graded_algebra().module_morphism(diagonal=lambda x: base_one, + codomain=self) + + def projection(self, i): + r""" + Return the `i`-th projection `p_i : F_i \to G_i` (in the + notations of the class documentation + :class:`AssociatedGradedAlgebra`, where `A = ` ``self``). + + This method actually does not return the map `p_i` itself, + but an extension of `p_i` to the whole `R`-module `A`. + This extension is the composition of the `R`-module + isomorphism `A \to \operatorname{gr} A` with the canonical + projection of the graded `R`-module `\operatorname{gr} A` + onto its `i`-th graded component `G_i`. The codomain of + this map is `\operatorname{gr} A`, although its actual + image is `G_i`. The map `p_i` is obtained from this map + by restricting its domain to `F_i` and its image to `G_i`. + + EXAMPLES:: + + sage: A = Modules(ZZ).WithBasis().Filtered().example() + sage: p = -2 * A.an_element(); p + -4*P[] - 4*P[1] - 6*P[2] + sage: q = A.projection(2)(p); q + -6*Bbar[[2]] + sage: q.parent() is A.graded_algebra() + True + sage: A.projection(3)(p) + 0 + """ + base_zero = self.base_ring().zero() + base_one = self.base_ring().one() + grA = self.graded_algebra() + proj = lambda x: (base_one if self.degree_on_basis(x) == i + else base_zero) + return self.module_morphism(diagonal=proj, codomain=grA) + + def induced_graded_map(self, other, f): + r""" + Return the graded linear map between the associated graded + modules of ``self`` and ``other`` canonically induced by + the filtration-preserving map ``f : self -> other``. + + Let `A` and `B` be two filtered modules with basis, and let + `(F_i)_{i \in I}` and `(G_i)_{i \in I}` be their + filtrations. Let `f : A \to B` be a linear map which + preserves the filtration (i.e., satisfies `f(F_i) \subseteq + G_i` for all `i \in I`). Then, there is a canonically + defined graded linear map + `\operatorname{gr} f : \operatorname{gr} A \to + \operatorname{gr} B` which satisfies + + .. MATH:: + + (\operatorname{gr} f) (p_i(a)) = p_i(f(a)) + \qquad \text{for all } i \in I \text{ and } a \in F_i , + + where the `p_i` on the left hand side is the canonical + projection from `F_i` onto the `i`-th graded component + of `\operatorname{gr} A`, while the `p_i` on the right + hand side is the canonical projection from `G_i` onto + the `i`-th graded component of `\operatorname{gr} B`. + + INPUT: + + - ``other`` -- a filtered algebra with basis + + - ``f`` -- a filtration-preserving linear map from ``self`` + to ``other`` (can be given as a morphism or as a function) + + OUTPUT: + + The graded linear map `\operatorname{gr} f`. + + EXAMPLES: + + **Example 1.** + + We start with the free `\QQ`-module with basis the set of all + partitions:: + + sage: A = Modules(QQ).WithBasis().Filtered().example(); A + An example of a filtered module with basis: the free module + on partitions over Rational Field + sage: M = A.indices(); M + Partitions + sage: p1, p2, p21, p321 = [A.basis()[Partition(i)] for i in [[1], [2], [2,1], [3,2,1]]] + + Let us define a map from ``A`` to itself which acts on the + basis by sending every partition `\lambda` to the sum of + the conjugates of all partitions `\mu` for which + `\lambda / \mu` is a horizontal strip:: + + sage: def map_on_basis(lam): + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=A) + sage: f(p1) + P[] + P[1] + sage: f(p2) + P[] + P[1] + P[1, 1] + sage: f(p21) + P[1] + P[1, 1] + P[2] + P[2, 1] + sage: f(p21 - p1) + -P[] + P[1, 1] + P[2] + P[2, 1] + sage: f(p321) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + + We now compute `\operatorname{gr} f` :: + + sage: grA = A.graded_algebra(); grA + Graded Module of An example of a filtered module with basis: + the free module on partitions over Rational Field + sage: pp1, pp2, pp21, pp321 = [A.to_graded_conversion()(i) for i in [p1, p2, p21, p321]] + sage: pp2 + 4 * pp21 + Bbar[[2]] + 4*Bbar[[2, 1]] + sage: grf = A.induced_graded_map(A, f); grf + Generic endomorphism of Graded Module of An example of a + filtered module with basis: + the free module on partitions over Rational Field + sage: grf(pp1) + Bbar[[1]] + sage: grf(pp2 + 4 * pp21) + Bbar[[1, 1]] + 4*Bbar[[2, 1]] + + **Example 2.** + + We shall now construct `\operatorname{gr} f` for a + different map `f` out of the same ``A``; the new map + `f` will lead into a graded algebra already, namely into + the algebra of symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = A.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(p1) + h[] + h[1] + sage: f(p2) + h[] + h[1] + h[1, 1] + sage: f(A.zero()) + 0 + sage: f(p2 - 3*p1) + -2*h[] - 2*h[1] + h[1, 1] + + The algebra ``h`` of symmetric functions in the `h`-basis + is already graded, so its associated graded algebra is + implemented as itself:: + + sage: grh = h.graded_algebra(); grh is h + True + sage: grf = A.induced_graded_map(h, f); grf + Generic morphism: + From: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + To: Symmetric Functions over Rational Field + in the homogeneous basis + sage: grf(pp1) + h[1] + sage: grf(pp2) + h[1, 1] + sage: grf(pp321) + h[3, 2, 1] + sage: grf(pp2 - 3*pp1) + -3*h[1] + h[1, 1] + sage: grf(pp21) + h[2, 1] + sage: grf(grA.zero()) + 0 + + **Example 3.** + + After having had a graded module as the codomain, let us try to + have one as the domain instead. Our new ``f`` will go from ``h`` + to ``A``:: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return A.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=A) # redefining f + sage: f(h[1]) + P[] + P[1] + sage: f(h[2]) + P[] + P[1] + P[1, 1] + sage: f(h[1, 1]) + P[1] + P[2] + sage: f(h[2, 2]) + P[1, 1] + P[2, 1] + P[2, 2] + sage: f(h[3, 2, 1]) + P[2, 1] + P[2, 1, 1] + P[2, 2] + P[2, 2, 1] + + P[3, 1] + P[3, 1, 1] + P[3, 2] + P[3, 2, 1] + sage: f(h.one()) + P[] + sage: grf = h.induced_graded_map(A, f); grf + Generic morphism: + From: Symmetric Functions over Rational Field + in the homogeneous basis + To: Graded Module of An example of a filtered + module with basis: the free module on partitions + over Rational Field + sage: grf(h[1]) + Bbar[[1]] + sage: grf(h[2]) + Bbar[[1, 1]] + sage: grf(h[1, 1]) + Bbar[[2]] + sage: grf(h[2, 2]) + Bbar[[2, 2]] + sage: grf(h[3, 2, 1]) + Bbar[[3, 2, 1]] + sage: grf(h.one()) + Bbar[[]] + + **Example 4.** + + The construct `\operatorname{gr} f` also makes sense when `f` + is a filtration-preserving map between graded modules. :: + + sage: def map_on_basis(lam): # redefining map_on_basis + ....: return h.sum_of_monomials([Partition(mu).conjugate() for k in range(sum(lam) + 1) + ....: for mu in lam.remove_horizontal_border_strip(k)]) + sage: f = h.module_morphism(on_basis=map_on_basis, + ....: codomain=h) # redefining f + sage: f(h[1]) + h[] + h[1] + sage: f(h[2]) + h[] + h[1] + h[1, 1] + sage: f(h[1, 1]) + h[1] + h[2] + sage: f(h[2, 1]) + h[1] + h[1, 1] + h[2] + h[2, 1] + sage: f(h.one()) + h[] + sage: grf = h.induced_graded_map(h, f); grf + Generic endomorphism of Symmetric Functions over Rational + Field in the homogeneous basis + sage: grf(h[1]) + h[1] + sage: grf(h[2]) + h[1, 1] + sage: grf(h[1, 1]) + h[2] + sage: grf(h[2, 1]) + h[2, 1] + sage: grf(h.one()) + h[] + """ + grA = self.graded_algebra() + grB = other.graded_algebra() + from sage.categories.graded_modules_with_basis import GradedModulesWithBasis + cat = GradedModulesWithBasis(self.base_ring()) + from_gr = self.from_graded_conversion() + def on_basis(m): + i = grA.degree_on_basis(m) + lifted_img_of_m = f(from_gr(grA.monomial(m))) + return other.projection(i)(lifted_img_of_m) + return grA.module_morphism(on_basis=on_basis, + codomain=grB, category=cat) + # If we could assume that the projection of the basis + # element of ``self`` indexed by an index ``m`` is the + # basis element of ``grA`` indexed by ``m``, then this + # could go faster: + # + # def on_basis(m): + # i = grA.degree_on_basis(m) + # return grB.projection(i)(f(self.monomial(m))) + # return grA.module_morphism(on_basis=on_basis, + # codomain=grB, category=cat) + # + # But this assumption might come back to bite us in the + # ass one day. What do you think? + + class ElementMethods: + + def is_homogeneous(self): + r""" + Return whether the element ``self`` is homogeneous. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x=A(Partition((3,2,1))) + sage: y=A(Partition((4,4,1))) + sage: z=A(Partition((2,2,2))) + sage: (3*x).is_homogeneous() + True + sage: (x - y).is_homogeneous() + False + sage: (x+2*z).is_homogeneous() + True + + Here is an example with a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: (3*x).is_homogeneous() + True + sage: (x^3 - y^2).is_homogeneous() + True + sage: ((x + y)^2).is_homogeneous() + False + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis, or at least on a definition of homogeneous + components):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).is_homogeneous() + True + sage: (y*x).is_homogeneous() + False + sage: A.one().is_homogeneous() + True + sage: A.zero().is_homogeneous() + True + sage: (A.one()+x).is_homogeneous() + False + """ + degree_on_basis = self.parent().degree_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = degree_on_basis(m) + else: + if degree != degree_on_basis(m): + return False + return True + + @abstract_method(optional=True) + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m`` + in ``self``. + + EXAMPLES:: + + sage: A = GradedModulesWithBasis(QQ).example() + sage: A.degree_on_basis(Partition((2,1))) + 3 + sage: A.degree_on_basis(Partition((4,2,1,1,1,1))) + 10 + """ + + def homogeneous_degree(self): + r""" + The degree of a nonzero homogeneous element ``self`` in the + filtered module. + + .. NOTE:: + + This raises an error if the element is not homogeneous. + To compute the maximum of the degrees of the homogeneous + summands of a (not necessarily homogeneous) element, use + :meth:`maximal_degree` instead. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.degree() + 6 + sage: (x + 2*z).degree() + 6 + sage: (y - x).degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + An example in a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.homogeneous_degree() + 2 + sage: (x^3 + 4*y^2).homogeneous_degree() + 6 + sage: ((1 + x)^3).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + Let us now test a filtered algebra (but remember that the + notion of homogeneity now depends on the choice of a + basis):: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).homogeneous_degree() + 2 + sage: (y*x).homogeneous_degree() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + sage: A.one().homogeneous_degree() + 0 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent().degree_on_basis(self.leading_support()) + + # default choice for degree; will be overridden as necessary + degree = homogeneous_degree + + def maximal_degree(self): + """ + The maximum of the degrees of the homogeneous components + of ``self``. + + This is also the smallest `i` such that ``self`` belongs + to `F_i`. Hence, it does not depend on the basis of the + parent of ``self``. + + .. SEEALSO:: :meth:`homogeneous_degree` + + EXAMPLES: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A(Partition((3,2,1))) + sage: y = A(Partition((4,4,1))) + sage: z = A(Partition((2,2,2))) + sage: x.maximal_degree() + 6 + sage: (x + 2*z).maximal_degree() + 6 + sage: (y - x).maximal_degree() + 9 + sage: (3*z).maximal_degree() + 6 + + Now, we test this on a graded algebra:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: (x, y) = (S[2], S[3]) + sage: x.maximal_degree() + 2 + sage: (x^3 + 4*y^2).maximal_degree() + 6 + sage: ((1 + x)^3).maximal_degree() + 6 + + Let us now test a filtered algebra:: + + sage: A = AlgebrasWithBasis(QQ).Filtered().example() + sage: x,y,z = A.algebra_generators() + sage: (x*y).maximal_degree() + 2 + sage: (y*x).maximal_degree() + 2 + sage: A.one().maximal_degree() + 0 + sage: A.zero().maximal_degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + sage: (A.one()+x).maximal_degree() + 1 + + TESTS:: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S.zero().degree() + Traceback (most recent call last): + ... + ValueError: the zero element does not have a well-defined degree + """ + if self.is_zero(): + raise ValueError("the zero element does not have a well-defined degree") + degree_on_basis = self.parent().degree_on_basis + return max(degree_on_basis(m) for m in self.support()) + + def homogeneous_component(self, n): + """ + Return the homogeneous component of degree ``n`` of the + element ``self``. + + Let `m` be an element of a filtered `R`-module `M` with + basis. Then, `m` can be uniquely written in the form + `m = \sum_{i \in I} m_i`, where each `m_i` is a + homogeneous element of degree `i`. For `n \in I`, we + define the homogeneous component of degree `n` of the + element `m` to be `m_n`. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.homogeneous_component(-1) + 0 + sage: x.homogeneous_component(0) + 2*P[] + sage: x.homogeneous_component(1) + 2*P[1] + sage: x.homogeneous_component(2) + 3*P[2] + sage: x.homogeneous_component(3) + 0 + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.homogeneous_component(-1) + 0 + sage: g.homogeneous_component(0) + 0 + sage: g.homogeneous_component(2) + -2*U['x']*U['y'] + sage: g.homogeneous_component(5) + 0 + sage: g.homogeneous_component(7) + U['x']^2*U['y']^2*U['z']^3 + sage: g.homogeneous_component(8) + 0 + + TESTS: + + Check that this really returns ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.homogeneous_component(3).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if degree_on_basis(i) == n) + + def truncate(self, n): + """ + Return the sum of the homogeneous components of degree + strictly less than ``n`` of ``self``. + + See :meth:`homogeneous_component` for the notion of a + homogeneous component. + + EXAMPLES:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = ModulesWithBasis(ZZ).Graded().example() + sage: x = A.an_element(); x + 2*P[] + 2*P[1] + 3*P[2] + sage: x.truncate(0) + 0 + sage: x.truncate(1) + 2*P[] + sage: x.truncate(2) + 2*P[] + 2*P[1] + sage: x.truncate(3) + 2*P[] + 2*P[1] + 3*P[2] + + sage: A = AlgebrasWithBasis(ZZ).Filtered().example() + sage: g = A.an_element() - 2 * A.algebra_generators()['x'] * A.algebra_generators()['y']; g + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + sage: g.truncate(-1) + 0 + sage: g.truncate(0) + 0 + sage: g.truncate(2) + 0 + sage: g.truncate(3) + -2*U['x']*U['y'] + sage: g.truncate(5) + -2*U['x']*U['y'] + sage: g.truncate(7) + -2*U['x']*U['y'] + sage: g.truncate(8) + U['x']^2*U['y']^2*U['z']^3 - 2*U['x']*U['y'] + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: A = ModulesWithBasis(ZZ).Filtered().example() + sage: x = A.an_element() + sage: x.truncate(0).parent() is A + True + """ + degree_on_basis = self.parent().degree_on_basis + return self.parent().sum_of_terms((i, c) for (i, c) in self + if degree_on_basis(i) < n) + diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index 06ff1fa25f6..06e741dd0fa 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -1678,7 +1678,7 @@ def toggling_orbit_iter(self, vs, oideal, element_constructor=set, stop=True, ch next = self.order_ideal_toggles(next, vs) yield element_constructor(next) - def order_ideals_lattice(self, as_ideals=True, facade=False): + def order_ideals_lattice(self, as_ideals=True, facade=None): r""" Return the lattice of order ideals of a poset ``self``, ordered by inclusion. @@ -1700,10 +1700,13 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): - ``as_ideals`` -- Boolean, if ``True`` (default) returns a poset on the set of order ideals, otherwise on the set of antichains + - ``facade`` -- Boolean or ``None`` (default). Whether to + return a facade lattice or not. By default return facade + lattice if the poset is a facade poset. EXAMPLES:: - sage: P = Posets.PentagonPoset(facade = True) + sage: P = Posets.PentagonPoset() sage: P.cover_relations() [[0, 1], [0, 2], [1, 4], [2, 3], [3, 4]] sage: J = P.order_ideals_lattice(); J @@ -1727,10 +1730,15 @@ def order_ideals_lattice(self, as_ideals=True, facade=False): sage: J.cover_relations() [[{}, {0}], [{0}, {0, 2}], [{0}, {0, 1}], [{0, 2}, {0, 1, 2}], [{0, 1}, {0, 1, 2}], [{0, 1, 2}, {0, 1, 2, 3}]] - .. NOTE:: we use facade posets in the examples above just - to ensure a nicer ordering in the output. + sage: P = Poset({1:[2]}) + sage: J_facade = P.order_ideals_lattice() + sage: J_nonfacade = P.order_ideals_lattice(facade=False) + sage: type(J_facade[0]) == type(J_nonfacade[0]) + False """ from sage.combinat.posets.lattices import LatticePoset + if facade is None: + facade = self._is_facade if as_ideals: from sage.misc.misc import attrcall from sage.sets.set import Set diff --git a/src/sage/categories/graded_algebras.py b/src/sage/categories/graded_algebras.py index 98e89f81993..d03f648d499 100644 --- a/src/sage/categories/graded_algebras.py +++ b/src/sage/categories/graded_algebras.py @@ -20,16 +20,29 @@ class GradedAlgebras(GradedModulesCategory): sage: GradedAlgebras(ZZ) Category of graded algebras over Integer Ring sage: GradedAlgebras(ZZ).super_categories() - [Category of algebras over Integer Ring, + [Category of filtered algebras over Integer Ring, Category of graded modules over Integer Ring] TESTS:: sage: TestSuite(GradedAlgebras(ZZ)).run() """ - class ParentMethods: - pass + def graded_algebra(self): + """ + Return the associated graded algebra to ``self``. + + Since ``self`` is already graded, this just returns + ``self``. + + EXAMPLES:: + + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True + """ + return self class ElementMethods: pass + diff --git a/src/sage/categories/graded_algebras_with_basis.py b/src/sage/categories/graded_algebras_with_basis.py index 66a608fad2e..949fceddfb3 100644 --- a/src/sage/categories/graded_algebras_with_basis.py +++ b/src/sage/categories/graded_algebras_with_basis.py @@ -20,7 +20,7 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: C = GradedAlgebrasWithBasis(ZZ); C Category of graded algebras with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of algebras with basis over Integer Ring, + [Category of filtered algebras with basis over Integer Ring, Category of graded algebras over Integer Ring, Category of graded modules with basis over Integer Ring] @@ -28,107 +28,60 @@ class GradedAlgebrasWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ - class ParentMethods: - pass - - class ElementMethods: - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: (3*x).is_homogeneous() - True - sage: (x^3 - y^2).is_homogeneous() - True - sage: ((x + y)^2).is_homogeneous() - False + # This needs to be copied in GradedAlgebras because we need to have + # FilteredAlgebrasWithBasis as an extra super category + def graded_algebra(self): """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def homogeneous_degree(self): - """ - The degree of this element. - - .. note:: - - This raises an error if the element is not homogeneous. - To obtain the maximum of the degrees of the homogeneous - summands, use :meth:`maximal_degree` + Return the associated graded algebra to ``self``. - .. seealso: :meth:`maximal_degree` + This is ``self``, because ``self`` is already graded. + See :meth:`~sage.categories.filtered_algebras_with_basis.FilteredAlgebrasWithBasis.graded_algebra` + for the general behavior of this method, and see + :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and properties of associated graded + algebras. EXAMPLES:: - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.homogeneous_degree() - 2 - sage: (x^3 + 4*y^2).homogeneous_degree() - 6 - sage: ((1 + x)^3).homogeneous_degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - - TESTS:: + sage: m = SymmetricFunctions(QQ).m() + sage: m.graded_algebra() is m + True - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - try: - assert self.is_homogeneous() - return self.parent().degree_on_basis(self.leading_support()) - except AssertionError: - raise ValueError("Element is not homogeneous.") + TESTS: - # default choice for degree; will be overridden as necessary - degree = homogeneous_degree + Let us check that the three methods + :meth:`to_graded_conversion`, :meth:`from_graded_conversion` + and :meth:`projection` (which form the interface of the + associated graded algebra) work correctly here:: - def maximal_degree(self): + sage: to_gr = m.to_graded_conversion() + sage: from_gr = m.from_graded_conversion() + sage: m[2] == to_gr(m[2]) == from_gr(m[2]) + True + sage: u = 3*m[1] - (1/2)*m[3] + sage: u == to_gr(u) == from_gr(u) + True + sage: m.zero() == to_gr(m.zero()) == from_gr(m.zero()) + True + sage: p2 = m.projection(2) + sage: p2(m[2] - 4*m[1,1] + 3*m[1] - 2*m[[]]) + -4*m[1, 1] + m[2] + sage: p2(4*m[1]) + 0 + sage: p2(m.zero()) == m.zero() + True """ - The maximum of the degrees of the homogeneous summands. + return self - .. seealso: :meth:`homogeneous_degree` + # .. TODO:: + # Possibly override ``to_graded_conversion`` and + # ``from_graded_conversion`` with identity morphisms? + # I have to admit I don't know the right way to construct + # identity morphisms other than using the identity matrix. + # Also, ``projection`` could be overridden by, well, a + # projection. - EXAMPLES:: - - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: (x, y) = (S[2], S[3]) - sage: x.maximal_degree() - 2 - sage: (x^3 + 4*y^2).maximal_degree() - 6 - sage: ((1 + x)^3).maximal_degree() - 6 - - TESTS:: + class ElementMethods: + pass - sage: S = NonCommutativeSymmetricFunctions(QQ).S() - sage: S.zero().degree() - Traceback (most recent call last): - ... - ValueError: The zero element does not have a well-defined degree. - """ - if self.is_zero(): - raise ValueError("The zero element does not have a well-defined degree.") - else: - degree_on_basis = self.parent().degree_on_basis - return max(degree_on_basis(m) for m in self.support()) diff --git a/src/sage/categories/graded_modules.py b/src/sage/categories/graded_modules.py index 65f8a6ba7bb..ca31efe005b 100644 --- a/src/sage/categories/graded_modules.py +++ b/src/sage/categories/graded_modules.py @@ -11,6 +11,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category from sage.categories.category_types import Category_over_base_ring from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory @@ -26,7 +27,7 @@ def __init__(self, base_category): sage: C.base_category() Category of algebras over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of algebras over Rational Field, + [Category of filtered algebras over Rational Field, Category of graded modules over Rational Field] sage: AlgebrasWithBasis(QQ).Graded().base_ring() @@ -56,16 +57,64 @@ def _repr_object_names(self): """ return "graded {}".format(self.base_category()._repr_object_names()) + @classmethod + def default_super_categories(cls, category, *args): + r""" + Return the default super categories of ``category.Graded()``. + + Mathematical meaning: every graded object (module, algebra, + etc.) is a filtered object with the (implicit) filtration + defined by `F_i = \bigoplus_{j \leq i} G_j`. + + INPUT: + + - ``cls`` -- the class ``GradedModulesCategory`` + - ``category`` -- a category + + OUTPUT: a (join) category + + In practice, this returns ``category.Filtered()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() ` + (that is the join of ``category.Filtered()`` and ``cat`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Algebras()``, which has ``cat=Modules()`` + as super category. Then, a grading of an algebra `G` + is also a filtration of `G`:: + + sage: Algebras(QQ).Graded().super_categories() + [Category of filtered algebras over Rational Field, + Category of graded modules over Rational Field] + + This resulted from the following call:: + + sage: sage.categories.graded_modules.GradedModulesCategory.default_super_categories(Algebras(QQ)) + Join of Category of filtered algebras over Rational Field + and Category of graded modules over Rational Field + """ + cat = super(GradedModulesCategory, cls).default_super_categories(category, *args) + return Category.join([category.Filtered(), cat]) + class GradedModules(GradedModulesCategory): - """ + r""" The category of graded modules. + We consider every graded module `M = \bigoplus_i M_i` as a + filtered module under the (natural) filtration given by + + .. MATH:: + + F_i = \bigoplus_{j < i} M_j. + EXAMPLES:: sage: GradedModules(ZZ) Category of graded modules over Integer Ring sage: GradedModules(ZZ).super_categories() - [Category of modules over Integer Ring] + [Category of filtered modules over Integer Ring] The category of graded modules defines the graded structure which shall be preserved by morphisms:: @@ -77,71 +126,9 @@ class GradedModules(GradedModulesCategory): sage: TestSuite(GradedModules(ZZ)).run() """ - - def extra_super_categories(self): - r""" - Adds :class:`VectorSpaces` to the super categories of ``self`` if - the base ring is a field. - - EXAMPLES:: - - sage: Modules(QQ).Graded().extra_super_categories() - [Category of vector spaces over Rational Field] - sage: Modules(ZZ).Graded().extra_super_categories() - [] - - This makes sure that ``Modules(QQ).Graded()`` returns an - instance of :class:`GradedModules` and not a join category of - an instance of this class and of ``VectorSpaces(QQ)``:: - - sage: type(Modules(QQ).Graded()) - - - .. TODO:: - - Get rid of this workaround once there is a more systematic - approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. - Probably the later should be a category with axiom, and - covariant constructions should play well with axioms. - """ - from sage.categories.modules import Modules - from sage.categories.fields import Fields - base_ring = self.base_ring() - if base_ring in Fields: - return [Modules(base_ring)] - else: - return [] - - class SubcategoryMethods: - - @cached_method - def Connected(self): - r""" - Return the full subcategory of the connected objects of ``self``. - - EXAMPLES:: - - sage: Modules(ZZ).Graded().Connected() - Category of graded connected modules over Integer Ring - sage: Coalgebras(QQ).Graded().Connected() - Join of Category of graded connected modules over Rational Field - and Category of coalgebras over Rational Field - sage: GradedAlgebrasWithBasis(QQ).Connected() - Category of graded connected algebras with basis over Rational Field - - TESTS:: - - sage: TestSuite(Modules(ZZ).Graded().Connected()).run() - sage: Coalgebras(QQ).Graded().Connected.__module__ - 'sage.categories.graded_modules' - """ - return self._with_axiom("Connected") - - class Connected(CategoryWithAxiom_over_base_ring): - pass - class ParentMethods: pass class ElementMethods: pass + diff --git a/src/sage/categories/graded_modules_with_basis.py b/src/sage/categories/graded_modules_with_basis.py index 9bb99fc5a95..a1c4a5d5c2e 100644 --- a/src/sage/categories/graded_modules_with_basis.py +++ b/src/sage/categories/graded_modules_with_basis.py @@ -20,8 +20,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: C = GradedModulesWithBasis(ZZ); C Category of graded modules with basis over Integer Ring sage: sorted(C.super_categories(), key=str) - [Category of graded modules over Integer Ring, - Category of modules with basis over Integer Ring] + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] sage: C is ModulesWithBasis(ZZ).Graded() True @@ -30,164 +30,8 @@ class GradedModulesWithBasis(GradedModulesCategory): sage: TestSuite(C).run() """ class ParentMethods: - - # TODO: which syntax do we prefer? - # A.basis(degree = 3) - # A.basis().subset(degree=3) - - # This is related to the following design question: - # If F = (f_i)_{i\in I} is a family, should ``F.subset(degree = 3)`` - # be the elements of F of degree 3 or those whose index is of degree 3? - - def basis(self, d=None): - """ - Returns the basis for (an homogeneous component of) this graded module - - INPUT: - - - `d` -- non negative integer or ``None``, optional (default: ``None``) - - If `d` is None, returns a basis of the module. - Otherwise, returns the basis of the homogeneous component of degree `d`. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: A.basis(4) - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions of the integer 4} - - Without arguments, the full basis is returned:: - - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - sage: A.basis() - Lazy family (Term map from Partitions to An example of a graded module with basis: the free module on partitions over Integer Ring(i))_{i in Partitions} - """ - from sage.sets.family import Family - if d is None: - return Family(self._indices, self.monomial) - else: - return Family(self._indices.subset(size=d), self.monomial) + pass class ElementMethods: + pass - def is_homogeneous(self): - """ - Return whether this element is homogeneous. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x=A(Partition((3,2,1))) - sage: y=A(Partition((4,4,1))) - sage: z=A(Partition((2,2,2))) - sage: (3*x).is_homogeneous() - True - sage: (x - y).is_homogeneous() - False - sage: (x+2*z).is_homogeneous() - True - """ - degree_on_basis = self.parent().degree_on_basis - degree = None - for m in self.support(): - if degree is None: - degree = degree_on_basis(m) - else: - if degree != degree_on_basis(m): - return False - return True - - def degree(self): - """ - The degree of this element in the graded module. - - .. note:: - - This raises an error if the element is not homogeneous. - Another implementation option would be to return the - maximum of the degrees of the homogeneous summands. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A(Partition((3,2,1))) - sage: y = A(Partition((4,4,1))) - sage: z = A(Partition((2,2,2))) - sage: x.degree() - 6 - sage: (x + 2*z).degree() - 6 - sage: (y - x).degree() - Traceback (most recent call last): - ... - ValueError: Element is not homogeneous. - """ - if not self.support(): - raise ValueError("The zero element does not have a well-defined degree.") - if self.is_homogeneous(): - return self.parent().degree_on_basis(self.leading_support()) - else: - raise ValueError("Element is not homogeneous.") - - def homogeneous_component(self, n): - """ - Return the homogeneous component of degree ``n`` of this - element. - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.homogeneous_component(-1) - 0 - sage: x.homogeneous_component(0) - 2*P[] - sage: x.homogeneous_component(1) - 2*P[1] - sage: x.homogeneous_component(2) - 3*P[2] - sage: x.homogeneous_component(3) - 0 - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.homogeneous_component(3).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) - for (i, c) in self - if degree_on_basis(i) == n) - - def truncate(self, n): - """ - Return the sum of the homogeneous components of degree ``< n`` of this element - - EXAMPLES:: - - sage: A = GradedModulesWithBasis(ZZ).example() - sage: x = A.an_element(); x - 2*P[] + 2*P[1] + 3*P[2] - sage: x.truncate(0) - 0 - sage: x.truncate(1) - 2*P[] - sage: x.truncate(2) - 2*P[] + 2*P[1] - sage: x.truncate(3) - 2*P[] + 2*P[1] + 3*P[2] - - TESTS: - - Check that this really return ``A.zero()`` and not a plain ``0``:: - - sage: x.truncate(0).parent() is A - True - """ - degree_on_basis = self.parent().degree_on_basis - return self.parent().sum_of_terms((i, c) for (i, c) in self - if degree_on_basis(i) < n) diff --git a/src/sage/categories/hopf_algebras.py b/src/sage/categories/hopf_algebras.py index eba5add5d9a..a28911b4448 100644 --- a/src/sage/categories/hopf_algebras.py +++ b/src/sage/categories/hopf_algebras.py @@ -15,6 +15,7 @@ from sage.categories.bialgebras import Bialgebras from sage.categories.tensor import TensorProductsCategory # tensor from sage.categories.realizations import RealizationsCategory +from sage.categories.super_modules import SuperModulesCategory from sage.misc.cachefunc import cached_method #from sage.misc.lazy_attribute import lazy_attribute @@ -104,6 +105,8 @@ class Morphism(Category): """ pass + class Super(SuperModulesCategory): + pass class TensorProducts(TensorProductsCategory): """ diff --git a/src/sage/categories/hopf_algebras_with_basis.py b/src/sage/categories/hopf_algebras_with_basis.py index 3a2f8a1dbd2..26be399ee97 100644 --- a/src/sage/categories/hopf_algebras_with_basis.py +++ b/src/sage/categories/hopf_algebras_with_basis.py @@ -147,6 +147,7 @@ def example(self, G = None): FiniteDimensional = LazyImport('sage.categories.finite_dimensional_hopf_algebras_with_basis', 'FiniteDimensionalHopfAlgebrasWithBasis') Graded = LazyImport('sage.categories.graded_hopf_algebras_with_basis', 'GradedHopfAlgebrasWithBasis') + Super = LazyImport('sage.categories.super_hopf_algebras_with_basis', 'SuperHopfAlgebrasWithBasis') class ParentMethods: diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index fb721b36e3c..80c74638a97 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -17,7 +17,7 @@ from sage.categories.homsets import HomsetsCategory from category import Category, JoinCategory from category_types import Category_module, Category_over_base_ring -from tensor import TensorProductsCategory +from sage.categories.tensor import TensorProductsCategory, tensor from dual import DualObjectsCategory from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.sets_cat import Sets @@ -348,6 +348,43 @@ def FiniteDimensional(self): """ return self._with_axiom("FiniteDimensional") + @cached_method + def Filtered(self, base_ring=None): + r""" + Return the subcategory of the filtered objects of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Filtered() + Category of filtered modules over Integer Ring + + sage: Coalgebras(QQ).Filtered() + Join of Category of filtered modules over Rational Field + and Category of coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Filtered() + Category of filtered algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Graded.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.filtered_modules import FilteredModulesCategory + return FilteredModulesCategory.category_of(self) + @cached_method def Graded(self, base_ring=None): r""" @@ -384,6 +421,42 @@ def Graded(self, base_ring=None): from sage.categories.graded_modules import GradedModulesCategory return GradedModulesCategory.category_of(self) + @cached_method + def Super(self, base_ring=None): + r""" + Return the super-analogue category of ``self``. + + INPUT: + + - ``base_ring`` -- this is ignored + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + + sage: Coalgebras(QQ).Super() + Category of super coalgebras over Rational Field + + sage: AlgebrasWithBasis(QQ).Super() + Category of super algebras with basis over Rational Field + + .. TODO:: + + - Explain why this does not commute with :meth:`WithBasis` + - Improve the support for covariant functorial + constructions categories over a base ring so as to + get rid of the ``base_ring`` argument. + + TESTS:: + + sage: Coalgebras(QQ).Super.__module__ + 'sage.categories.modules' + """ + assert base_ring is None or base_ring is self.base_ring() + from sage.categories.super_modules import SuperModulesCategory + return SuperModulesCategory.category_of(self) + @cached_method def WithBasis(self): r""" @@ -430,11 +503,28 @@ def extra_super_categories(self): else: return [] + Filtered = LazyImport('sage.categories.filtered_modules', 'FilteredModules') Graded = LazyImport('sage.categories.graded_modules', 'GradedModules') + Super = LazyImport('sage.categories.super_modules', 'SuperModules') WithBasis = LazyImport('sage.categories.modules_with_basis', 'ModulesWithBasis') class ParentMethods: - pass + @cached_method + def tensor_square(self): + """ + Returns the tensor square of ``self`` + + EXAMPLES:: + + sage: A = HopfAlgebrasWithBasis(QQ).example() + sage: A.tensor_square() + An example of Hopf algebra with basis: + the group algebra of the Dihedral group of order 6 + as a permutation group over Rational Field # An example + of Hopf algebra with basis: the group algebra of the Dihedral + group of order 6 as a permutation group over Rational Field + """ + return tensor([self, self]) class ElementMethods: diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index c780127492a..282b6fc7fa9 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -123,6 +123,12 @@ class ModulesWithBasis(CategoryWithAxiom_over_base_ring): .. TODO:: ``End(X)`` is an algebra. + .. NOTE:: + + This category currently requires an implementation of an + element method ``support``. Once :trac:`18066` is merged, an + implementation of an ``items`` method will be required. + TESTS:: sage: TestSuite(ModulesWithBasis(ZZ)).run() @@ -181,7 +187,9 @@ def is_abelian(self): return self.base_ring().is_field() FiniteDimensional = LazyImport('sage.categories.finite_dimensional_modules_with_basis', 'FiniteDimensionalModulesWithBasis') + Filtered = LazyImport('sage.categories.filtered_modules_with_basis', 'FilteredModulesWithBasis') Graded = LazyImport('sage.categories.graded_modules_with_basis', 'GradedModulesWithBasis') + Super = LazyImport('sage.categories.super_modules_with_basis', 'SuperModulesWithBasis') class ParentMethods: @cached_method @@ -777,7 +785,6 @@ class ElementMethods: # """ # return self._lmul_(-self.parent().base_ring().one(), self) - def support_of_term(self): """ Return the support of ``self``, where ``self`` is a monomial diff --git a/src/sage/categories/super_algebras.py b/src/sage/categories/super_algebras.py new file mode 100644 index 00000000000..6071afdbce8 --- /dev/null +++ b/src/sage/categories/super_algebras.py @@ -0,0 +1,67 @@ +r""" +Super Algebras +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.misc.lazy_import import LazyImport + +class SuperAlgebras(SuperModulesCategory): + """ + The category of super algebras. + + An `R`-*super algebra* is an `R`-super module `A` endowed with an + `R`-algebra structure satisfying + + .. MATH:: + + A_0 A_0 \subseteq A_0, \qquad + A_0 A_1 \subseteq A_1, \qquad + A_1 A_0 \subseteq A_1, \qquad + A_1 A_1 \subseteq A_0 + + and `1 \in A_0`. + + EXAMPLES:: + + sage: Algebras(ZZ).Super() + Category of super algebras over Integer Ring + + TESTS:: + + sage: TestSuite(Algebras(ZZ).Super()).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Algebras(ZZ).Super().super_categories() # indirect doctest + [Category of graded algebras over Integer Ring, + Category of super modules over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded algebra to ``self``. + + .. WARNING:: + + Because a super module `M` is naturally `\ZZ / 2 \ZZ`-graded, and + graded modules have a natural filtration induced by the grading, if + `M` has a different filtration, then the associated graded module + `\operatorname{gr} M \neq M`. This is most apparent with super + algebras, such as the :class:`differential Weyl algebra + `, and the + multiplication may not coincide. + """ + raise NotImplementedError + diff --git a/src/sage/categories/super_algebras_with_basis.py b/src/sage/categories/super_algebras_with_basis.py new file mode 100644 index 00000000000..9a4a1bc05a0 --- /dev/null +++ b/src/sage/categories/super_algebras_with_basis.py @@ -0,0 +1,61 @@ +r""" +Super algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules + +class SuperAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super algebras with a distinguished basis + + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super(); C + Category of super algebras with basis over Integer Ring + + TESTS:: + + sage: TestSuite(C).run() + """ + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: C = Algebras(ZZ).WithBasis().Super() + sage: sorted(C.super_categories(), key=str) # indirect doctest + [Category of graded algebras with basis over Integer Ring, + Category of super algebras over Integer Ring, + Category of super modules with basis over Integer Ring] + """ + return [self.base_category().Graded()] + + class ParentMethods: + def graded_algebra(self): + r""" + Return the associated graded module to ``self``. + + See :class:`~sage.algebras.associated_graded.AssociatedGradedAlgebra` + for the definition and the properties of this. + + .. SEEALSO:: + + :meth:`~sage.categories.filtered_modules_with_basis.ParentMethods.graded_algebra` + + EXAMPLES:: + + sage: W. = algebras.DifferentialWeyl(QQ) + sage: W.graded_algebra() + Graded Algebra of Differential Weyl algebra of + polynomials in x, y over Rational Field + """ + from sage.algebras.associated_graded import AssociatedGradedAlgebra + return AssociatedGradedAlgebra(self) + diff --git a/src/sage/categories/super_hopf_algebras_with_basis.py b/src/sage/categories/super_hopf_algebras_with_basis.py new file mode 100644 index 00000000000..28347832bc4 --- /dev/null +++ b/src/sage/categories/super_hopf_algebras_with_basis.py @@ -0,0 +1,30 @@ +r""" +Super Hopf algebras with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperHopfAlgebrasWithBasis(SuperModulesCategory): + """ + The category of super Hopf algebras with a distinguished basis. + + EXAMPLES:: + + sage: C = HopfAlgebras(ZZ).WithBasis().Super(); C + Category of super hopf algebras with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of super algebras with basis over Integer Ring, + Category of super coalgebras with basis over Integer Ring, + Category of super hopf algebras over Integer Ring] + + TESTS:: + + sage: TestSuite(C).run() + """ + diff --git a/src/sage/categories/super_modules.py b/src/sage/categories/super_modules.py new file mode 100644 index 00000000000..a0a06dddf3d --- /dev/null +++ b/src/sage/categories/super_modules.py @@ -0,0 +1,229 @@ +r""" +Super modules +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_class_attribute +from sage.categories.category import Category +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.covariant_functorial_construction import CovariantConstructionCategory +from sage.categories.modules import Modules + +# Note, a commutative algebra is not a commutative super algebra, +# therefore the following whitelist. +axiom_whitelist = frozenset(["Facade", "Finite", "Infinite", + "FiniteDimensional", "Connected", "WithBasis", + # "Commutative", + "Associative", "Inverse", "Unital", "Division", + "AdditiveCommutative", "AdditiveAssociative", + "AdditiveInverse", "AdditiveUnital", + "NoZeroDivisors", "Distributive"]) + +class SuperModulesCategory(CovariantConstructionCategory, Category_over_base_ring): + @classmethod + def default_super_categories(cls, category, *args): + """ + Return the default super categories of `F_{Cat}(A,B,...)` for + `A,B,...` parents in `Cat`. + + INPUT: + + - ``cls`` -- the category class for the functor `F` + - ``category`` -- a category `Cat` + - ``*args`` -- further arguments for the functor + + OUTPUT: + + A join category. + + This implements the property that subcategories constructed by + the set of whitelisted axioms is a subcategory. + + EXAMPLES:: + + sage: HopfAlgebras(ZZ).WithBasis().FiniteDimensional().Super() # indirect doctest + Category of finite dimensional super hopf algebras with basis over Integer Ring + """ + axioms = axiom_whitelist.intersection(category.axioms()) + C = super(SuperModulesCategory, cls).default_super_categories(category, *args) + return C._with_axioms(axioms) + + def __init__(self, base_category): + """ + EXAMPLES:: + + sage: C = Algebras(QQ).Super() + sage: C + Category of super algebras over Rational Field + sage: C.base_category() + Category of algebras over Rational Field + sage: sorted(C.super_categories(), key=str) + [Category of graded algebras over Rational Field, + Category of super modules over Rational Field] + + sage: AlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + sage: HopfAlgebrasWithBasis(QQ).Super().base_ring() + Rational Field + """ + super(SuperModulesCategory, self).__init__(base_category, base_category.base_ring()) + + _functor_category = "Super" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: AlgebrasWithBasis(QQ).Super() # indirect doctest + Category of super algebras with basis over Rational Field + """ + return "super {}".format(self.base_category()._repr_object_names()) + +class SuperModules(SuperModulesCategory): + r""" + The category of super modules. + + An `R`-*super module* (where `R` is a ring) is an `R`-module `M` equipped + with a decomposition `M = M_0 \oplus M_1` into two `R`-submodules + `M_0` and `M_1` (called the *even part* and the *odd part* of `M`, + respectively). + + Thus, an `R`-super module automatically becomes a `\ZZ / 2 \ZZ`-graded + `R`-module, with `M_0` being the degree-`0` component and `M_1` being the + degree-`1` component. + + EXAMPLES:: + + sage: Modules(ZZ).Super() + Category of super modules over Integer Ring + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + The category of super modules defines the super structure which + shall be preserved by morphisms:: + + sage: Modules(ZZ).Super().additional_structure() + Category of super modules over Integer Ring + + TESTS:: + + sage: TestSuite(Modules(ZZ).Super()).run() + """ + def super_categories(self): + """ + EXAMPLES:: + + sage: Modules(ZZ).Super().super_categories() + [Category of graded modules over Integer Ring] + + Nota bene:: + + sage: Modules(QQ).Super() + Category of super modules over Rational Field + sage: Modules(QQ).Super().super_categories() + [Category of graded modules over Rational Field] + """ + return [self.base_category().Graded()] + + def extra_super_categories(self): + r""" + Adds :class:`VectorSpaces` to the super categories of ``self`` if + the base ring is a field. + + EXAMPLES:: + + sage: Modules(QQ).Super().extra_super_categories() + [Category of vector spaces over Rational Field] + sage: Modules(ZZ).Super().extra_super_categories() + [] + + This makes sure that ``Modules(QQ).Super()`` returns an + instance of :class:`SuperModules` and not a join category of + an instance of this class and of ``VectorSpaces(QQ)``:: + + sage: type(Modules(QQ).Super()) + + + .. TODO:: + + Get rid of this workaround once there is a more systematic + approach for the alias ``Modules(QQ)`` -> ``VectorSpaces(QQ)``. + Probably the latter should be a category with axiom, and + covariant constructions should play well with axioms. + """ + from sage.categories.modules import Modules + from sage.categories.fields import Fields + base_ring = self.base_ring() + if base_ring in Fields: + return [Modules(base_ring)] + else: + return [] + + class ParentMethods: + pass + + class ElementMethods: + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element or ``1`` + if an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even_odd() + 1 + sage: C.basis()[2,2].is_even_odd() + 0 + """ + return self.degree() % 2 + + def is_even(self): + """ + Return if ``self`` is an even element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_even() + False + sage: C.basis()[2,2].is_even() + True + """ + return self.is_even_odd() == 0 + + def is_odd(self): + """ + Return if ``self`` is an odd element. + + EXAMPLES:: + + sage: cat = Algebras(QQ).WithBasis().Super() + sage: C = CombinatorialFreeModule(QQ, Partitions(), category=cat) + sage: C.degree_on_basis = sum + sage: C.basis()[2,2,1].is_odd() + True + sage: C.basis()[2,2].is_odd() + False + """ + return self.is_even_odd() == 1 + diff --git a/src/sage/categories/super_modules_with_basis.py b/src/sage/categories/super_modules_with_basis.py new file mode 100644 index 00000000000..43b3dc1215a --- /dev/null +++ b/src/sage/categories/super_modules_with_basis.py @@ -0,0 +1,185 @@ +r""" +Super modules with basis +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.super_modules import SuperModulesCategory + +class SuperModulesWithBasis(SuperModulesCategory): + """ + The category of super modules with a distinguished basis. + + An `R`-*super module with a distinguished basis* is an + `R`-super module equipped with an `R`-module basis whose elements are + homogeneous. + + EXAMPLES:: + + sage: C = GradedModulesWithBasis(ZZ); C + Category of graded modules with basis over Integer Ring + sage: sorted(C.super_categories(), key=str) + [Category of filtered modules with basis over Integer Ring, + Category of graded modules over Integer Ring] + sage: C is ModulesWithBasis(ZZ).Graded() + True + + TESTS:: + + sage: TestSuite(C).run() + """ + class ParentMethods: + def _even_odd_on_basis(self, m): + """ + Return the parity of the basis element indexed by ``m``. + + OUTPUT: + + ``0`` if ``m`` is for an even element or ``1`` if ``m`` + is for an odd element. + + .. NOTE:: + + The default implementation assumes that the even/odd is + determined by the parity of :meth:`degree`. + + Overwrite this method if the even/odd behavior is desired + to be independent. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: C._even_odd_on_basis((0,)) + 1 + sage: C._even_odd_on_basis((0,1)) + 0 + """ + return self.degree_on_basis(m) % 2 + + class ElementMethods: + def is_super_homogeneous(self): + r""" + Return whether this element is homogeneous, in the sense + of a super module (i.e., is even or odd). + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_super_homogeneous() + True + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a = x*y + x - 3*y + 4 + sage: a.is_super_homogeneous() + False + + The exterior algebra has a `\ZZ` grading, which induces the + `\ZZ / 2\ZZ` grading. However the definition of homogeneous + elements differs because of the different gradings:: + + sage: E. = ExteriorAlgebra(QQ) + sage: a = x*y + 4 + sage: a.is_super_homogeneous() + True + sage: a.is_homogeneous() + False + """ + even_odd = self.parent()._even_odd_on_basis + degree = None + for m in self.support(): + if degree is None: + degree = even_odd(m) + else: + if degree != even_odd(m): + return False + return True + + def is_even_odd(self): + """ + Return ``0`` if ``self`` is an even element and ``1`` if + ``self`` is an odd element. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x + y + sage: a.is_even_odd() + 1 + sage: a = x*y + 4 + sage: a.is_even_odd() + 0 + sage: a = x + 4 + sage: a.is_even_odd() + Traceback (most recent call last): + ... + ValueError: element is not homogeneous + + sage: E. = ExteriorAlgebra(QQ) + sage: (x*y).is_even_odd() + 0 + """ + if not self.support(): + raise ValueError("the zero element does not have a well-defined degree") + if not self.is_super_homogeneous(): + raise ValueError("element is not homogeneous") + return self.parent()._even_odd_on_basis(self.leading_support()) + + def even_component(self): + """ + Return the even component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.even_component() + x*y + 4 + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x + y + sage: a.even_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 0) + + def odd_component(self): + """ + Return the odd component of ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(QQ, 2, [1,2,3]) + sage: C. = CliffordAlgebra(Q) + sage: a = x*y + x - 3*y + 4 + sage: a.odd_component() + x - 3*y + + TESTS: + + Check that this really return ``A.zero()`` and not a plain ``0``:: + + sage: a = x*y + sage: a.odd_component().parent() is C + True + """ + even_odd = self.parent()._even_odd_on_basis + return self.parent().sum_of_terms((i, c) + for (i, c) in self + if even_odd(i) == 1) + diff --git a/src/sage/categories/unique_factorization_domains.py b/src/sage/categories/unique_factorization_domains.py index 50c6fd107a4..ddedb674d3b 100644 --- a/src/sage/categories/unique_factorization_domains.py +++ b/src/sage/categories/unique_factorization_domains.py @@ -124,6 +124,87 @@ def is_unique_factorization_domain(self, proof=True): """ return True + def _gcd_univariate_polynomial(self, f, g): + """ + Return the greatest common divisor of ``f`` and ``g``. + + INPUT: + + - ``f``, ``g`` -- two polynomials defined over this UFD. + + .. NOTE:: + + This is a helper method for + :meth:`sage.rings.polynomial.polynomial_element.Polynomial.gcd`. + + ALGORITHM: + + Algorithm 3.3.1 in [GTM138]_, based on pseudo-division. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if f.degree() < g.degree(): + A,B = g, f + else: + A,B = f, g + + if B.is_zero(): + return A + + a = b = self.zero() + for c in A.coefficients(): + a = a.gcd(c) + if a.is_one(): + break + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + parent = f.parent() + + d = a.gcd(b) + A = parent(A/a) + B = parent(B/b) + g = h = 1 + + delta = A.degree()-B.degree() + _,R = A.pseudo_quo_rem(B) + + while R.degree() > 0: + A = B + B = parent(R/(g*h**delta)) + g = A.leading_coefficient() + h = self(h*g**delta/h**delta) + delta = A.degree() - B.degree() + _, R = A.pseudo_quo_rem(B) + + if R.is_zero(): + b = self.zero() + for c in B.coefficients(): + b = b.gcd(c) + if b.is_one(): + break + + return parent(d*B/b) + + return d + class ElementMethods: # prime? # squareFree diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index 84e1b6e81f5..5895ac96efc 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -1,7 +1,6 @@ from sage.misc.lazy_import import lazy_import -from code_constructions import (permutation_action, - walsh_matrix,cyclotomic_cosets) +from code_constructions import (permutation_action, walsh_matrix,cyclotomic_cosets) from sage.misc.superseded import deprecated_callable_import deprecated_callable_import(15445, @@ -62,14 +61,14 @@ elias_bound_asymp, mrrw1_bound_asymp) -from linear_code import (LinearCode, LinearCodeFromVectorSpace, - best_known_linear_code, - best_known_linear_code_www, - bounds_minimum_distance, - self_orthogonal_binary_codes) +lazy_import("sage.coding.linear_code", ["LinearCode",\ + "LinearCodeFromVectorSpace",\ + "best_known_linear_code",\ + "best_known_linear_code_www",\ + "bounds_minimum_distance", + "self_orthogonal_binary_codes"]) from sd_codes import self_dual_codes_binary - lazy_import("sage.coding.delsarte_bounds", ["Krawtchouk", "delsarte_bound_hamming_space", "delsarte_bound_additive_hamming_space"]) diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index d97b3b99c87..9af7b3a908c 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -22,7 +22,7 @@ CyclicCode, CyclicCodeFromCheckPolynomial, DuadicCodeEvenPair, DuadicCodeOddPair, ExtendedBinaryGolayCode, ExtendedQuadraticResidueCode, ExtendedTernaryGolayCode, - HammingCode, LinearCodeFromCheckMatrix, + HammingCode, LinearCode, LinearCodeFromCheckMatrix, QuadraticResidueCode, QuadraticResidueCodeEvenPair, QuadraticResidueCodeOddPair, RandomLinearCode, ReedSolomonCode, TernaryGolayCode, @@ -30,6 +30,7 @@ from guava import BinaryReedMullerCode, QuasiQuadraticResidueCode, RandomLinearCodeGuava -from sage.misc.rest_index_of_methods import gen_rest_table_index -import sys -__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__], only_local_functions=False)) +import encoders_catalog as encoders +from sage.misc.rest_index_of_methods import gen_rest_table_index as _gen_rest_table_index +import sys as _sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=_gen_rest_table_index(_sys.modules[__name__], only_local_functions=False)) diff --git a/src/sage/coding/encoder.py b/src/sage/coding/encoder.py new file mode 100644 index 00000000000..d5051e7ccfe --- /dev/null +++ b/src/sage/coding/encoder.py @@ -0,0 +1,325 @@ +r""" +Encoder + +Representation of a bijection between a message space and a code. +""" + +#***************************************************************************** +# Copyright (C) 2015 David Lucas +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.modules.free_module_element import vector +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.structure.sage_object import SageObject + +class Encoder(SageObject): + r""" + Abstract top-class for :class:`Encoder` objects. + + Every encoder class should inherit from this abstract class. + + To implement an encoder, you need to: + + - inherit from :class:`Encoder`, + + - call ``Encoder.__init__`` in the subclass constructor. + Example: ``super(SubclassName, self).__init__(code)``. + By doing that, your subclass will have its ``code`` parameter initialized. + + - Then, if the message space is a vector space, default implementations of :meth:`encode` and + :meth:`unencode_nocheck` methods are provided. These implementations rely on :meth:`generator_matrix` + which you need to override to use the default implementations. + + - If the message space is not of the form `F^k`, where `F` is a finite field, + you cannot have a generator matrix. + In that case, you need to override :meth:`encode`, :meth:`unencode_nocheck` and + :meth:`message_space`. + + - By default, comparison of :class:`Encoder` (using methods ``__eq__`` and ``__ne__`` ) are + by memory reference: if you build the same encoder twice, they will be different. If you + need something more clever, override ``__eq__`` and ``__ne__`` in your subclass. + + - As :class:`Encoder` is not designed to be instantiated, it does not have any representation + methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + + REFERENCES: + + .. [Nielsen] Johan S. R. Nielsen, (https://bitbucket.org/jsrn/codinglib/) + """ + + def __init__(self, code): + r""" + Initializes mandatory parameters for an :class:`Encoder` object. + + This method only exists for inheritance purposes as it initializes + parameters that need to be known by every linear code. An abstract + encoder object should never be created. + + INPUT: + + - ``code`` -- the associated code of ``self`` + + EXAMPLES: + + We first create a new :class:`Encoder` subclass:: + + sage: class EncoderExample(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(EncoderExample, self).__init__(code) + + We now create a member of our newly made class:: + + sage: G = Matrix(GF(2), [[1, 0, 0, 1], [0, 1, 1, 1]]) + sage: C = LinearCode(G) + sage: E = EncoderExample(C) + + We can check its parameters:: + + sage: E.code() + Linear code of length 4, dimension 2 over Finite Field of size 2 + """ + self._code = code + + def encode(self, word): + r""" + Transforms an element of the message space into a codeword. + + This is a default implementation which assumes that the message + space of the encoder is `F^{k}`, where `F` is + :meth:`sage.coding.linear_code.AbstractLinearCode.base_field` + and `k` is :meth:`sage.coding.linear_code.AbstractLinearCode.dimension`. + If this is not the case, this method should be overwritten by the subclass. + + .. NOTE:: + + :meth:`encode` might be a partial function over ``self``'s :meth:`message_space`. + One should use the exception :class:`EncodingError` to catch attempts + to encode words that are outside of the message space. + + INPUT: + + - ``word`` -- a vector of the message space of the ``self``. + + OUTPUT: + + - a vector of :meth:`code`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector(GF(2), (0, 1, 1, 0)) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + If ``word`` is not in the message space of ``self``, it will return an exception:: + + sage: word = random_vector(GF(7), 4) + sage: E.encode(word) + Traceback (most recent call last): + ... + ValueError: The value to encode must be in Vector space of dimension 4 over Finite Field of size 2 + """ + M = self.message_space() + if word not in M: + raise ValueError("The value to encode must be in %s" % M) + return vector(word) * self.generator_matrix() + + def unencode(self, c, nocheck=False): + r""" + Returns the message corresponding to the codeword ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a vector of the same length as :meth:`code` over the + base field of :meth:`code`. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + OUTPUT: + + - an element of the message space of ``self`` + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode(c) + (0, 1, 1, 0) + + TESTS: + + If ``nocheck`` is set to ``False``, and one provides a word which is not in + :meth:`code`, :meth:`unencode` will return an error:: + + sage: c = vector(GF(2), (0, 1, 0, 0, 1, 1, 0)) + sage: c in C + False + sage: E.unencode(c, False) + Traceback (most recent call last): + ... + EncodingError: Given word is not in the code + + If ones tries to unencode a codeword of a code of dimension 0, it + returns the empty vector:: + + sage: G = Matrix(GF(17), []) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: c = C.random_element() + sage: E.unencode(c) + () + """ + if nocheck == False and c not in self.code(): + raise EncodingError("Given word is not in the code") + return self.unencode_nocheck(c) + + @cached_method + def _unencoder_matrix(self): + r""" + Finds an information set for the matrix ``G`` returned by :meth:`generator_matrix`, + and returns the inverse of that submatrix of ``G``. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E._unencoder_matrix() + ( + [0 0 1 1] + [0 1 0 1] + [1 1 1 0] + [0 1 1 1], (0, 1, 2, 3) + ) + """ + info_set = self.code().information_set() + Gt = self.generator_matrix().matrix_from_columns(info_set) + return (Gt.inverse(), info_set) + + def unencode_nocheck(self, c): + r""" + Returns the message corresponding to ``c``. + + When ``c`` is not a codeword, the output is unspecified. + + AUTHORS: + + This function is taken from codinglib [Nielsen]_ + + INPUT: + + - ``c`` -- a vector of the same length as ``self`` over the + base field of ``self`` + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: c in C + True + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + + Taking a vector that does not belong to ``C`` will not raise an error but + probably just give a non-sensical result:: + + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 1)) + sage: c in C + False + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.unencode_nocheck(c) + (0, 1, 1, 0) + sage: m = vector(GF(2), (0, 1, 1, 0)) + sage: c1 = E.encode(m) + sage: c == c1 + False + """ + U, info_set = self._unencoder_matrix() + cc = vector(self.code().base_ring(), [c[i] for i in info_set]) + return cc * U + + def code(self): + r""" + Returns the code for this :class:`Encoder`. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.code() == C + True + """ + return self._code + + def message_space(self): + r""" + Returns the ambient space of allowed input to :meth:`encode`. + Note that :meth:`encode` is possibly a partial function over + the ambient space. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.message_space() + Vector space of dimension 4 over Finite Field of size 2 + """ + return self.code().base_field()**(self.code().dimension()) + + @abstract_method(optional = True) + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + This is an abstract method and it should be implemented separately. + Reimplementing this for each subclass of :class:`Encoder` is not mandatory + (as a generator matrix only makes sense when the message space is of the `F^k`, + where `F` is the base field of :meth:`code`.) + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = C.encoder() + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + +class EncodingError(Exception): + r""" + Special exception class to indicate an error during encoding or unencoding. + """ + pass diff --git a/src/sage/coding/encoders_catalog.py b/src/sage/coding/encoders_catalog.py new file mode 100644 index 00000000000..36a0f84e34a --- /dev/null +++ b/src/sage/coding/encoders_catalog.py @@ -0,0 +1,16 @@ +r""" +Index of encoders + +The ``codes.encoders`` object may be used to access the encoders that Sage can build. + +:class:`linear_code.LinearCodeGeneratorMatrixEncoder ` + +.. NOTE:: + + To import these names into the global namespace, use: + + sage: from sage.coding.encoders_catalog import * +""" + +from sage.misc.lazy_import import lazy_import as _lazy_import +_lazy_import('sage.coding.linear_code', 'LinearCodeGeneratorMatrixEncoder') diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 2674dc4565d..f39e6e6d77c 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -216,6 +216,7 @@ import sage.modules.free_module as fm import sage.modules.module as module from sage.categories.modules import Modules +from copy import copy from sage.interfaces.all import gap from sage.rings.finite_rings.constructor import FiniteField as GF from sage.groups.perm_gps.permgroup import PermutationGroup @@ -237,7 +238,7 @@ from sage.misc.decorators import rename_keyword from sage.misc.cachefunc import cached_method from sage.misc.superseded import deprecated_function_alias - +from encoder import Encoder ZZ = IntegerRing() VectorSpace = fm.VectorSpace @@ -709,12 +710,6 @@ class AbstractLinearCode(module.Module): So, every Linear Code-related class should inherit from this abstract class. - This class provides: - - - ``length``, the length of the code - - - numerous methods that will work for any linear code (including families) - To implement a linear code, you need to: - inherit from AbstractLinearCode @@ -726,19 +721,35 @@ class AbstractLinearCode(module.Module): You need of course to complete the constructor by adding any additional parameter needed to describe properly the code defined in the subclass. - - reimplement ``generator_matrix()`` method + - fill the dictionary of its encoders in ``sage.coding.__init__.py`` file. Example: + I want to link the encoder ``MyEncoderClass`` to ``MyNewCodeClass`` + under the name ``MyEncoderName``. + All I need to do is to write this line in the ``__init__.py`` file: + ``MyNewCodeClass._registered_encoders["NameOfMyEncoder"] = MyEncoderClass`` and all instances of + ``MyNewCodeClass`` will be able to use instances of ``MyEncoderClass``. As AbstractLinearCode is not designed to be implemented, it does not have any representation - methods. You should implement ``_repr_`` and ``_latex_`` methods in the sublclass. + methods. You should implement ``_repr_`` and ``_latex_`` methods in the subclass. .. NOTE:: - AbstractLinearCode embeds some generic implementations of helper methods like ``__cmp__`` or ``__eq__``. - As they are designed to fit for every linear code, they mostly use the generator matrix - and thus can be long for certain families of code. In that case, overriding these methods is encouraged. + :class:`AbstractLinearCode` has generic implementations of the comparison methods ``__cmp`` + and ``__eq__`` which use the generator matrix and are quite slow. In subclasses you are + encouraged to override these functions. + + .. WARNING:: + + The default encoder should always have `F^{k}` as message space, with `k` the dimension + of the code and `F` is the base ring of the code. + A lot of methods of the abstract class rely on the knowledge of a generator matrix. + It is thus strongly recommended to set an encoder with a generator matrix implemented + as a default encoder. """ - def __init__(self, base_field, length): + + _registered_encoders = {} + + def __init__(self, base_field, length, default_encoder_name): """ Initializes mandatory parameters for a Linear Code object. @@ -752,13 +763,15 @@ def __init__(self, base_field, length): - ``length`` -- the length of ``self`` + - ``default_encoder_name`` -- the name of the default encoder of ``self`` + EXAMPLES: We first create a new LinearCode subclass:: sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): ....: def __init__(self, field, length, dimension, generator_matrix): - ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length) + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "GeneratorMatrix") ....: self._dimension = dimension ....: self._generator_matrix = generator_matrix ....: def generator_matrix(self): @@ -802,10 +815,31 @@ def __init__(self, base_field, length): Traceback (most recent call last): ... ValueError: length must be a Python int or a Sage Integer + + If the name of the default encoder is not known by the class, it will raise + an exception:: + + sage: class CodeExample(sage.coding.linear_code.AbstractLinearCode): + ....: def __init__(self, field, length, dimension, generator_matrix): + ....: sage.coding.linear_code.AbstractLinearCode.__init__(self,field, length, "Fail") + ....: self._dimension = dimension + ....: self._generator_matrix = generator_matrix + ....: def generator_matrix(self): + ....: return self._generator_matrix + ....: def _repr_(self): + ....: return "Dummy code of length %d, dimension %d over %s" % (self.length(), self.dimension(), self.base_field()) + + sage: C = CodeExample(GF(17), 10, 5, generator_matrix) + Traceback (most recent call last): + ... + ValueError: You must set a valid encoder as default encoder for this code, by completing __init__.py """ if not isinstance(length, (int, Integer)): raise ValueError("length must be a Python int or a Sage Integer") self._length = Integer(length) + if not default_encoder_name in self._registered_encoders: + raise ValueError("You must set a valid encoder as default encoder for this code, by completing __init__.py") + self._default_encoder_name = default_encoder_name cat = Modules(base_field).FiniteDimensional().WithBasis().Finite() facade_for = VectorSpace(base_field, self._length) self.Element = type(facade_for.an_element()) #for when we made this a non-facade parent @@ -843,6 +877,68 @@ def _an_element_(self): """ return self.gens()[0] + def add_encoder(self, name, encoder): + r""" + Adds an encoder to the list of registered encoders of ``self``. + + .. NOTE:: + + This method only adds ``encoder`` to ``self``, and not to any member of the class + of ``self``. To know how to add an :class:`sage.coding.encoder.Encoder`, please refer + to the documentation of :class:`AbstractLinearCode`. + + INPUT: + + - ``name`` -- the string name for the encoder + + - ``encoder`` -- the class name of the encoder + + EXAMPLES: + + First of all, we create a (very basic) new encoder:: + + sage: class MyEncoder(sage.coding.encoder.Encoder): + ....: def __init__(self, code): + ....: super(MyEncoder, self).__init__(code) + ....: def _repr_(self): + ....: return "MyEncoder encoder with associated code %s" % self.code() + + We now create a new code:: + + sage: C = codes.HammingCode(3, GF(2)) + + We can add our new encoder to the list of available encoders of C:: + + sage: C.add_encoder("MyEncoder", MyEncoder) + sage: C.encoders_available() + ['MyEncoder', 'GeneratorMatrix'] + + We can verify that any new code will not know MyEncoder:: + + sage: C2 = codes.HammingCode(3, GF(3)) + sage: C2.encoders_available() + ['GeneratorMatrix'] + + TESTS: + + It is impossible to use a name which is in the dictionnary of available encoders:: + + sage: C.add_encoder("GeneratorMatrix", MyEncoder) + Traceback (most recent call last): + ... + ValueError: There is already a registered encoder with this name + """ + if self._registered_encoders == self.__class__._registered_encoders: + self._registered_encoders = copy(self._registered_encoders) + reg_enc = self._registered_encoders + if name in reg_enc: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + else: + if name in self._registered_encoders: + raise ValueError("There is already a registered encoder with this name") + reg_enc[name] = encoder + def automorphism_group_gens(self, equivalence="semilinear"): r""" Return generators of the automorphism group of ``self``. @@ -1638,6 +1734,133 @@ def __eq__(self, right): return False return True + def encode(self, word, encoder_name=None, **kwargs): + r""" + Transforms an element of a message space into a codeword. + + INPUT: + + - ``word`` -- a vector of a message space of the code. + + - ``encoder_name`` -- (default: ``None``) Name of the encoder which will be used + to encode ``word``. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + OUTPUT: + + - a vector of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word) + (1, 1, 0, 0, 1, 1, 0) + + It is possible to manually choose the encoder amongst the list of the available ones:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: word = vector((0, 1, 1, 0)) + sage: C.encode(word, 'GeneratorMatrix') + (1, 1, 0, 0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.encode(word) + + @cached_method + def encoder(self, encoder_name=None, **kwargs): + r""" + Returns an encoder of ``self``. + + The returned encoder provided by this method is cached. + + This methods creates a new instance of the encoder subclass designated by ``encoder_name``. + While it is also possible to do the same by directly calling the subclass' constructor, + it is strongly advised to use this method to take advantage of the caching mechanism. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + returned. The default encoder of ``self`` will be used if + default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the constructor of the encoder + this method will return. + + OUTPUT: + + - an Encoder object. + + .. NOTE:: + + The default encoder always has `F^{k}` as message space, with `k` the dimension + of ``self`` and `F` the base ring of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoder() + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + + We check that the returned encoder is cached:: + + sage: C.encoder.is_in_cache() + True + + If the name of an encoder which is not known by ``self`` is passed, + an exception will be raised:: + + sage: C.encoders_available() + ['GeneratorMatrix'] + sage: C.encoder('NonExistingEncoder') + Traceback (most recent call last): + ... + ValueError: Passed Encoder name not known + """ + if encoder_name is None: + encoder_name = self._default_encoder_name + if encoder_name in self._registered_encoders: + encClass = self._registered_encoders[encoder_name] + E = encClass(self, **kwargs) + return E + else: + raise ValueError("Passed Encoder name not known") + + def encoders_available(self, classes=False): + r""" + Returns a list of the available encoders' names for ``self``. + + INPUT: + + - ``classes`` -- (default: ``False``) if ``classes`` is set to ``True``, it also + returns the encoders' classes associated with the encoders' names. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: C.encoders_available() + ['GeneratorMatrix'] + + sage: C.encoders_available(True) + {'GeneratorMatrix': + } + """ + if classes == True: + return copy(self._registered_encoders) + return self._registered_encoders.keys() + def extended_code(self): r""" If ``self`` is a linear code of length `n` defined over `F` then this @@ -1809,8 +2032,31 @@ def __getitem__(self, i): codeword.set_immutable() return codeword - def generator_matrix(self): - return NotImplementedError("This method must be set in subclasses") + def generator_matrix(self, encoder_name=None, **kwargs): + r""" + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. The default encoder of ``self`` + will be used if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + EXAMPLES:: + + sage: G = matrix(GF(3),2,[1,-1,1,-1,1,1]) + sage: code = LinearCode(G) + sage: code.generator_matrix() + [1 2 1] + [2 1 1] + """ + E = self.encoder(encoder_name, **kwargs) + return E.generator_matrix() + + gen_mat = deprecated_function_alias(17973, generator_matrix) def generator_matrix_systematic(self): """ @@ -1896,11 +2142,13 @@ def __iter__(self): FiniteFieldsubspace_iterator return FiniteFieldsubspace_iterator(self.generator_matrix(), immutable=True) - + @cached_method def information_set(self): """ Return an information set of the code. + Return value of this method is cached. + A set of column positions of a generator matrix of a code is called an information set if the corresponding columns form a square matrix of full rank. @@ -2391,7 +2639,7 @@ def permutation_automorphism_group(self, algorithm="partition"): print "\n Using the %s codewords of weight %s \n Supergroup size: \n %s\n "%(wts[wt],wt,size) gap.eval("Cwt:=Filtered(eltsC,c->WeightCodeword(c)=%s)"%wt) # bottleneck 2 (repeated gap.eval("matCwt:=List(Cwt,c->VectorCodeword(c))") # for each i until stop = 1) - if gap("Length(matCwt)") > 0: + if gap("Length(matCwt)") > 0: A = gap("MatrixAutomorphisms(matCwt)") G2 = gap("Intersection2(%s,%s)"%(str(A).replace("\n",""),str(Gp).replace("\n",""))) # bottleneck 3 Gp = G2 @@ -2873,7 +3121,7 @@ def spectrum(self, algorithm=None): input = code2leon(self) + "::code" import os, subprocess lines = subprocess.check_output([os.path.join(guava_bin_dir, 'wtdist'), input]) - import StringIO # to use the already present output parser + import StringIO # to use the already present output parser wts = [0]*(n+1) s = 0 for L in StringIO.StringIO(lines).readlines(): @@ -3010,6 +3258,42 @@ def syndrome(self, r): """ return self.parity_check_matrix()*r + def unencode(self, c, encoder_name=None, nocheck=False, **kwargs): + r""" + Returns the message corresponding to ``c``. + + This is the inverse of :meth:`encode`. + + INPUT: + + - ``c`` -- a codeword of ``self`` + + - ``encoder_name`` -- (default: ``None``) name of the decoder which will be used + to decode ``word``. The default decoder of ``self`` will be used if + default value is kept. + + - ``nocheck`` -- (default: ``False``) checks if ``c`` is in ``self``. You might set + this to ``True`` to disable the check for saving computation. Note that if ``c`` is + not in ``self`` and ``nocheck = True``, then the output of :meth:`unencode` is + not defined (except that it will be in the message space of ``self``). + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. + + OUTPUT: + + - an element of the message space of ``encoder_name`` of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: c = vector(GF(2), (1, 1, 0, 0, 1, 1, 0)) + sage: C.unencode(c) + (0, 1, 1, 0) + """ + E = self.encoder(encoder_name, **kwargs) + return E.unencode(c, nocheck) def weight_enumerator(self, names="xy", name2=None): """ @@ -3341,7 +3625,7 @@ def __init__(self, generator_matrix, d=None): if generator_matrix.nrows() == 0: raise ValueError("this linear code contains no non-zero vector") - super(LinearCode, self).__init__(base_ring, generator_matrix.ncols()) + super(LinearCode, self).__init__(base_ring, generator_matrix.ncols(), "GeneratorMatrix") self._generator_matrix = generator_matrix self._dimension = generator_matrix.rank() self._minimum_distance = d @@ -3360,9 +3644,18 @@ def _repr_(self): """ return "Linear code of length %s, dimension %s over %s"%(self.length(), self.dimension(), self.base_ring()) - def generator_matrix(self): + def generator_matrix(self, encoder_name=None, **kwargs): r""" - Return a generator matrix of this code. + Returns a generator matrix of ``self``. + + INPUT: + + - ``encoder_name`` -- (default: ``None``) name of the encoder which will be + used to compute the generator matrix. ``self._generator_matrix`` + will be returned if default value is kept. + + - ``kwargs`` -- all additional arguments are forwarded to the construction of the + encoder that is used. EXAMPLES:: @@ -3372,6 +3665,80 @@ def generator_matrix(self): [1 2 1] [2 1 1] """ - return self._generator_matrix + if encoder_name is None or encoder_name is 'GeneratorMatrix': + return self._generator_matrix + return super(LinearCode, self).generator_matrix(encoder_name, **kwargs) - gen_mat = deprecated_function_alias(17973, generator_matrix) + + +####################### encoders ############################### +class LinearCodeGeneratorMatrixEncoder(Encoder): + r""" + Encoder based on generator_matrix for Linear codes. + + This is the default encoder of a generic linear code, and should never be used for other codes than + :class:`LinearCode`. + + INPUT: + + - ``code`` -- The associated :class:`LinearCode` of this encoder. + """ + + def __init__(self, code): + r""" + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + super(LinearCodeGeneratorMatrixEncoder, self).__init__(code) + + def _repr_(self): + r""" + Returns a string representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E + Generator matrix-based encoder for Linear code of length 7, dimension 4 over Finite Field of size 2 + """ + return "Generator matrix-based encoder for %s" % self.code() + + def _latex_(self): + r""" + Returns a latex representation of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: latex(E) + \textnormal{Generator matrix-based encoder for }[7, 4]\textnormal{ Linear code over }\Bold{F}_{2} + """ + return "\\textnormal{Generator matrix-based encoder for }%s" % self.code()._latex_() + + @cached_method + def generator_matrix(self): + r""" + Returns a generator matrix of the associated code of ``self``. + + EXAMPLES:: + + sage: G = Matrix(GF(2), [[1,1,1,0,0,0,0],[1,0,0,1,1,0,0],[0,1,0,1,0,1,0],[1,1,0,1,0,0,1]]) + sage: C = LinearCode(G) + sage: E = codes.encoders.LinearCodeGeneratorMatrixEncoder(C) + sage: E.generator_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [0 1 0 1 0 1 0] + [1 1 0 1 0 0 1] + """ + return self.code().generator_matrix() +LinearCode._registered_encoders["GeneratorMatrix"] = LinearCodeGeneratorMatrixEncoder diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 6edebf986ea..7a0c223e36f 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -36,6 +36,8 @@ #Permutations from permutation import Permutation, Permutations, Arrangements, PermutationOptions, CyclicPermutations, CyclicPermutationsOfPartition from affine_permutation import AffinePermutationGroup +lazy_import('sage.combinat.colored_permutations', ['ColoredPermutations', + 'SignedPermutations']) from derangements import Derangements lazy_import('sage.combinat.baxter_permutations', ['BaxterPermutations']) diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 930a703e6af..8558936ef7a 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -104,6 +104,17 @@ def __init__(self, parent, asm): self._matrix = asm Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: A = AlternatingSignMatrices(3) + sage: elt = A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: hash(elt) + 12 + """ + return hash(self._matrix) + def _repr_(self): """ Return a string representation of ``self``. @@ -1101,7 +1112,9 @@ def _element_constructor_(self, asm): raise ValueError("Cannot convert between alternating sign matrices of different sizes") if asm in MonotoneTriangles(self._n): return self.from_monotone_triangle(asm) - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) Element = AlternatingSignMatrix @@ -1147,7 +1160,9 @@ def from_monotone_triangle(self, triangle): asm.append(row) prev = v - return self.element_class(self, self._matrix_space(asm)) + m = self._matrix_space(asm) + m.set_immutable() + return self.element_class(self, m) def from_corner_sum(self, corner): r""" diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index d019d916ccf..ad41db77a43 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -44,7 +44,8 @@ from sage.rings.all import FractionField, PolynomialRing from sage.rings.fraction_field_element import FractionFieldElement from sage.sets.all import Set -from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible +from sage.graphs.digraph import DiGraph +from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType_Irreducible, QuiverMutationType_Reducible from sage.combinat.cluster_algebra_quiver.mutation_type import is_mutation_finite from sage.misc.misc import exists from random import randint @@ -284,7 +285,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user self._user_labels_prefix = user_labels_prefix # initialize the rest - + self._C = matrix.identity(self._n) self._use_c_vec = True @@ -299,7 +300,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user self._mut_path = [ ] self._track_mut = True - + if user_labels: self._sanitize_init_vars(user_labels, user_labels_prefix) else: @@ -617,7 +618,7 @@ def use_fpolys(self, use=True, user_labels=None, user_labels_prefix=None): self._yhat = dict([ (self._U.gen(j),prod([self._R.gen(i)**self._M[i,j] for i in xrange(self._n+self._m)])) for j in xrange(self._n)]) elif self._cluster: raise ValueError("should not be possible to have cluster variables without f-polynomials") # added this as a sanity check. This error should never appear however. - elif self._track_mut == True: #If we can navigate from the root to where we are + elif self._track_mut == True: # If we can navigate from the root to where we are if not self._use_g_vec: self.use_g_vectors(True) catchup = ClusterSeed(self._b_initial, user_labels=user_labels, user_labels_prefix=user_labels_prefix) @@ -711,20 +712,20 @@ def track_mutations(self, use=True): def _sanitize_init_vars(self, user_labels, user_labels_prefix = 'x'): r""" Warning: This is an internal method that rewrites a user-given set of cluster variable names into a format that Sage can utilize. - + INPUT: - + - ``user_labels`` -- The labels that need sanitizing - ``user_labels_prefix`` -- (default:'x') The prefix to use for labels if integers given for labels - + EXAMPLES:: - + sage: S = ClusterSeed(['A',4]); S._init_vars {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} sage: S._sanitize_init_vars([1,2,3,4],'z') sage: S._init_vars {0: 'z1', 1: 'z2', 2: 'z3', 3: 'z4'} - + sage: S = ClusterSeed(['A',4]); S._init_vars {0: 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'y0', 5: 'y1', 6: 'y2', 7: 'y3'} sage: S._sanitize_init_vars(['a', 'b', 'c', 'd']) @@ -827,13 +828,13 @@ def __eq__(self, other): True sage: S.mutate([0,1,0,1,0]) - sage: S.__eq__( T ) + sage: S.__eq__( T ) False sage: S.cluster() [x1, x0] sage: T.cluster() [x0, x1] - + sage: S.mutate([0,1,0,1,0]) sage: S.__eq__( T ) True @@ -1182,18 +1183,18 @@ def mutations(self): sage: S.mutate([0,1,0,2]) sage: S.mutations() [0, 1, 0, 2] - + sage: S.track_mutations(False) sage: S.mutations() Traceback (most recent call last): ... - ValueError: Not recording mutation sequence. Need to track mutations. + ValueError: Not recording mutation sequence. Need to track mutations. """ if self._track_mut: - return copy(self._mut_path) + return copy(self._mut_path) else: - raise ValueError("Not recording mutation sequence. Need to track mutations.") - + raise ValueError("Not recording mutation sequence. Need to track mutations.") + def cluster_variable(self, k): r""" Generates a cluster variable using F-polynomials @@ -1224,7 +1225,7 @@ def cluster_variable(self, k): else: raise ValueError('No cluster variable with index or label ' + str(k) + '.') elif self._track_mut: # if we can recreate the clusters - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) return catchup.cluster_variable(k) @@ -1257,7 +1258,7 @@ def cluster(self): if not self._use_fpolys: if self._track_mut: # if we can recreate the clusters - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) return catchup.cluster() @@ -1344,7 +1345,7 @@ def f_polynomial(self,k): return self._F[IE[k]] elif self._track_mut: - catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) + catchup = ClusterSeed(self._b_initial, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix) catchup.use_c_vectors(use=self._use_c_vec, bot_is_c=self._bot_is_c) catchup.mutate(self.mutations()) @@ -1514,7 +1515,7 @@ def _g_mutate(self, k): J[j,k] += max(0, -eps*B[j,k]) J[k,k] = -1 self._G = self._G*J - + def c_vector(self,k): r""" Returns the ``k``-th *c-vector* of ``self``. It is obtained as the @@ -1630,7 +1631,7 @@ def d_vector(self, k): catchup.use_fpolys(False) catchup.use_g_vectors(False) catchup.use_c_vectors(False) - + catchup.mutate(self.mutations()) return copy(catchup._D).column(k) else: @@ -1639,7 +1640,7 @@ def d_vector(self, k): def d_matrix(self, show_warnings=True): r""" Returns the matrix of *d-vectors* of ``self``. - + EXAMPLES:: sage: S = ClusterSeed(['A',4]); S.d_matrix() [-1 0 0 0] @@ -1667,7 +1668,7 @@ def d_matrix(self, show_warnings=True): catchup.use_g_vectors(False) catchup.use_c_vectors(False) catchup.track_mutations(False) - + catchup.mutate(self.mutations()) return catchup.d_matrix() elif show_warnings: @@ -2300,7 +2301,7 @@ def mutate(self, sequence, inplace=True): # function should return either integer or sequence sequence = sequence(seed) - + if sequence is None: raise ValueError('Not mutating: No vertices given.') @@ -2311,17 +2312,17 @@ def mutate(self, sequence, inplace=True): n, m = seed.n(), seed.m() V = range(n)+IE - + if seed._use_fpolys and isinstance(sequence, str): sequence = seed.cluster_index(sequence) if sequence is None: raise ValueError("Variable provided is not in our cluster") - + if (sequence in xrange(n)) or (sequence in IE): seqq = [sequence] else: seqq = sequence - + if isinstance(seqq, tuple): @@ -2335,7 +2336,7 @@ def mutate(self, sequence, inplace=True): #raise ValueError('The quiver cannot be mutated at the vertex ' + str( v )) seq = iter(seqq) - + for k in seq: if k in xrange(n): @@ -2377,21 +2378,21 @@ def mutate(self, sequence, inplace=True): if not inplace: return seed - + def cluster_index(self, cluster_str): r""" Returns the index of a cluster if use_fpolys is on INPUT: - + - ``cluster_str`` -- The string to look for in the cluster - + OUTPUT: - + Returns an integer or None if the string is not a cluster variable - + EXAMPLES:: - + sage: S = ClusterSeed(['A',4],user_labels=['x','y','z','w']); S.mutate('x') sage: S.cluster_index('x') sage: S.cluster_index('(y+1)/x') @@ -2403,7 +2404,7 @@ def cluster_index(self, cluster_str): cluster_str = ClusterVariable( FractionField(self._R), c.numerator(), c.denominator(), mutation_type=self._mutation_type, variable_type='cluster variable',xdim=self._n ) if cluster_str in self.cluster(): return self.cluster().index(cluster_str) - + return None def mutation_sequence(self, sequence, show_sequence=False, fig_size=1.2,return_output='seed'): @@ -2720,7 +2721,7 @@ def exchangeable_part(self): from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part eval_dict = dict( [ ( self.y(i), 1 ) for i in xrange(self._m) ] ) - seed = ClusterSeed( _principal_part( self._M ), is_principal = True, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed( _principal_part( self._M ), is_principal = True, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2812,7 +2813,7 @@ def universal_extension(self): for alpha in almost_positive_coroots]) M = self._M.stack(C) - seed = ClusterSeed(M, is_principal = False, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed(M, is_principal = False, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2846,7 +2847,7 @@ def principal_extension(self): [ 0 0 1 0 0] [ 0 0 0 1 0] [ 0 0 0 0 1] - + sage: S = ClusterSeed(['A',4],user_labels=['a','b','c','d']) sage: T= S.principal_extension() sage: T.cluster() @@ -2870,7 +2871,7 @@ def principal_extension(self): self._user_labels = self._user_labels + ['y%s'%i for i in xrange(self._n)] elif isinstance(self._user_labels,dict): self._user_labels.update( {(i+self._n):'y%s'%i for i in xrange(self._n)} ) - seed = ClusterSeed(M, is_principal = is_principal, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) + seed = ClusterSeed(M, is_principal = is_principal, user_labels=self._user_labels, user_labels_prefix=self._user_labels_prefix, frozen=None) seed.use_c_vectors(self._use_c_vec) seed.use_fpolys(self._use_fpolys) seed.use_g_vectors(self._use_g_vec) @@ -2948,7 +2949,7 @@ def set_cluster( self, cluster, force=False ): sage: S.set_cluster(cluster2, force=True) sage: S.cluster() [x0, (x1 + 1)/x2, (x0*x2 + x1 + 1)/(x1*x2)] - + sage: S = ClusterSeed(['A',3]); S.use_fpolys(False) sage: S.set_cluster([1,1,1]) Warning: clusters not being tracked so this command is ignored. @@ -2965,7 +2966,7 @@ def set_cluster( self, cluster, force=False ): self._cluster = [ FractionField(self._R)(x) for x in cluster ][0:self._n] self._is_principal = None else: - print("Warning: clusters not being tracked so this command is ignored.") + print("Warning: clusters not being tracked so this command is ignored.") def reset_cluster( self ): r""" @@ -2992,12 +2993,12 @@ def reset_cluster( self ): sage: T.reset_cluster() sage: T.cluster() [x0, x1, x2] - + sage: S = ClusterSeed(['B',3],user_labels=[[1,2],[2,3],[3,4]],user_labels_prefix='p') sage: S.mutate([0,1]) sage: S.cluster() [(p_2_3 + 1)/p_1_2, (p_1_2*p_3_4^2 + p_2_3 + 1)/(p_1_2*p_2_3), p_3_4] - + sage: S.reset_cluster() sage: S.cluster() [p_1_2, p_2_3, p_3_4] @@ -3014,7 +3015,7 @@ def reset_cluster( self ): self._F = dict([(i,self._U(1)) for i in self._init_exch.values()]) if self._use_fpolys: self.set_cluster(self._R.gens(), force=True) - + def reset_coefficients( self ): r""" Resets the coefficients of ``self`` to the frozen variables but keeps the current cluster. @@ -3731,7 +3732,7 @@ def variable_class(self, depth=infinity, ignore_bipartite_belt=False): var_iter = self.variable_class_iter( depth=depth, ignore_bipartite_belt=ignore_bipartite_belt ) return sorted(var_iter) - def is_finite( self ): + def is_finite(self): r""" Returns True if ``self`` is of finite type. @@ -3929,6 +3930,70 @@ def greedy(self, a1, a2, method='by_recursion'): raise ValueError("Greedy elements are only currently " "defined for cluster seeds of rank two.") + def oriented_exchange_graph(self): + """ + Return the oriented exchange graph of ``self`` as a directed + graph. + + The seed must be a cluster seed for a cluster algebra of + finite type with principal coefficients (the corresponding + quiver must have mutable vertices 0,1,...,n-1). + + EXAMPLES:: + + sage: S = ClusterSeed(['A', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 5 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 0] + + sage: S = ClusterSeed(['B', 2]).principal_extension() + sage: G = S.oriented_exchange_graph(); G + Digraph on 6 vertices + sage: G.out_degree_sequence() + [2, 1, 1, 1, 1, 0] + + TESTS:: + + sage: S = ClusterSeed(['A',[2,2],1]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for finite mutation type + + sage: S = ClusterSeed(['A', 2]) + sage: S.oriented_exchange_graph() + Traceback (most recent call last): + ... + TypeError: only works for principal coefficients + """ + if not self._mutation_type.is_finite(): + raise TypeError('only works for finite mutation type') + + if not self._is_principal: + raise TypeError('only works for principal coefficients') + + covers = [] + n = self.n() + stack = [self] + known_clusters = [] + while stack: + i = stack.pop() + Vari = tuple(sorted(i.cluster())) + B = i.b_matrix() + for k in range(n): + # check if green + if all(B[i2][k] >= 0 for i2 in range(n, 2 * n)): + j = i.mutate(k, inplace=False) + Varj = tuple(sorted(j.cluster())) + covers.append((Vari, Varj)) + if not(Varj in known_clusters): + known_clusters += [Varj] + stack.append(j) + + return DiGraph(covers) + + def _bino(n, k): """ Binomial coefficient which we define as zero for negative n. @@ -4099,9 +4164,9 @@ def get_green_vertices(C): INPUT: - ``C`` -- The C matrix to check - + EXAMPLES:: - + sage: from sage.combinat.cluster_algebra_quiver.cluster_seed import get_green_vertices sage: S = ClusterSeed(['A',4]); S.mutate([1,2,3,2,0,1,2,0,3]) sage: get_green_vertices(S.c_matrix()) @@ -4219,4 +4284,3 @@ def almost_positive_root( self ): return sum( [ root[i]*Phiplus[ i+1 ] for i in range(self._n) ] ) else: raise ValueError('The cluster algebra for %s is not of finite type.'%self._repr_()) - diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py new file mode 100644 index 00000000000..40e14878ce9 --- /dev/null +++ b/src/sage/combinat/colored_permutations.py @@ -0,0 +1,1097 @@ +r""" +Colored Permutations + +.. TODO:: + + Much of the colored permutations (and element) class can be + generalized to `G \wr S_n` +""" +from sage.categories.groups import Groups +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.finite_coxeter_groups import FiniteCoxeterGroups +from sage.structure.element import MultiplicativeGroupElement +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod + +from sage.combinat.permutation import Permutations +from sage.combinat.cartesian_product import CartesianProduct +from sage.matrix.constructor import diagonal_matrix +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.all import ZZ + + +class ColoredPermutation(MultiplicativeGroupElement): + """ + A colored permutation. + """ + def __init__(self, parent, colors, perm): + """ + Initialize ``self``. + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: TestSuite(s1*s2*t).run() + """ + self._colors = tuple(colors) + self._perm = perm + MultiplicativeGroupElement.__init__(self, parent=parent) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*t + [[1, 0, 0], [3, 1, 2]] + """ + return repr([list(self._colors), self._perm]) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: latex(s1*s2*t) + [3_{1}, 1_{0}, 2_{0}] + """ + ret = "[" + ret += ", ".join("{}_{{{}}}".format(x, self._colors[i]) + for i, x in enumerate(self._perm)) + return ret + "]" + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + """ + colors = tuple(self._colors[i] + other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: ~t + [[0, 0, 3], [1, 2, 3]] + sage: all(x * ~x == C.one() for x in C.gens()) + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([-self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s1*s2 == s2*s1 + False + """ + if not isinstance(other, ColoredPermutation): + return False + return (self.parent() is other.parent() + and self._colors == other._colors + and self._perm == other._perm) + + def __ne__(self, other): + """ + Check inequality. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: s1*s2*s1 != s2*s1*s2 + False + sage: s1*s2 != s2*s1 + True + """ + return not self.__eq__(other) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: list(x) + [(1, 3), (0, 1), (0, 2)] + """ + for i, p in enumerate(self._perm): + yield (self._colors[i], p) + + def one_line_form(self): + """ + Return the one line form of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x + [[1, 0, 0], [3, 1, 2]] + sage: x.one_line_form() + [(1, 3), (0, 1), (0, 2)] + """ + return list(self) + + def colors(self): + """ + Return the colors of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.colors() + [1, 0, 0] + """ + return list(self._colors) + + def permutation(self): + """ + Return the permutation of ``self``. + + This is obtained by forgetting the colors. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t + sage: x.permutation() + [3, 1, 2] + """ + return self._perm + + def to_matrix(self): + """ + Return a matrix of ``self``. + + The colors are mapped to roots of unity. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: x = s1*s2*t*s2; x.one_line_form() + [(1, 2), (0, 1), (0, 3)] + sage: M = x.to_matrix(); M + [ 0 1 0] + [zeta4 0 0] + [ 0 0 1] + + The matrix multiplication is in the *opposite* order:: + + sage: M == s2.to_matrix()*t.to_matrix()*s2.to_matrix()*s1.to_matrix() + True + """ + Cp = CyclotomicField(self.parent()._m) + g = Cp.gen() + D = diagonal_matrix(Cp, [g ** i for i in self._colors]) + return self._perm.to_matrix() * D + + +# TODO: Parts of this should be put in the category of complex +# reflection groups +class ColoredPermutations(Parent, UniqueRepresentation): + r""" + The group of `m`-colored permutations on `\{1, 2, \ldots, n\}`. + + Let `S_n` be the symmetric group on `n` letters and `C_m` be the cyclic + group of order `m`. The `m`-colored permutation group on `n` letters + is given by `P_n^m = C_m \wr S_n`. This is also the complex reflection + group `G(m, 1, n)`. + + We define our multiplication by + + .. MATH:: + + ((s_1, \ldots s_n), \sigma) \cdot ((t_1, \ldots, t_n), \tau) + = ((s_1 t_{\sigma(1)}, \ldots, s_n t_{\sigma(n)}), \tau \sigma). + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3); C + 4-colored permutations of size 3 + sage: s1,s2,t = C.gens() + sage: (s1, s2, t) + ([[0, 0, 0], [2, 1, 3]], [[0, 0, 0], [1, 3, 2]], [[0, 0, 1], [1, 2, 3]]) + sage: s1*s2 + [[0, 0, 0], [3, 1, 2]] + sage: s1*s2*s1 == s2*s1*s2 + True + sage: t^4 == C.one() + True + sage: s2*t*s2 + [[0, 1, 0], [1, 2, 3]] + + We can also create a colored permutation by passing + either a list of tuples consisting of ``(color, element)``:: + + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + + or a list of colors and a permutation:: + + sage: C([[3,3,1], [1,3,2]]) + [[3, 3, 1], [1, 3, 2]] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: C(P.an_element()) + [[0, 0, 0], [3, 1, 2]] + + REFERENCES: + + - :wikipedia:`Generalized_symmetric_group` + - :wikipedia:`Complex_reflection_group` + """ + def __init__(self, m, n, category=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(2, 3) + sage: TestSuite(C).run() + sage: C = ColoredPermutations(1, 3) + sage: TestSuite(C).run() + """ + if m <= 0: + raise ValueError("m must be a positive integer") + self._m = m + self._n = n + self._C = IntegerModRing(self._m) + self._P = Permutations(self._n) + if category is None: + category = (Groups(), FiniteEnumeratedSets()) + Parent.__init__(self, category=category) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: ColoredPermutations(4, 3) + 4-colored permutations of size 3 + """ + return "{}-colored permutations of size {}".format(self._m, self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.one() + [[0, 0, 0], [1, 2, 3]] + """ + return self.element_class(self, [self._C.zero()] * self._n, + self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.gens() + ([[0, 0, 0], [2, 1, 3]], + [[0, 0, 0], [1, 3, 2]], + [[0, 0, 1], [1, 2, 3]]) + """ + zero = [self._C.zero()] * self._n + g = [] + for i in range(self._n - 1): + p = range(1, self._n + 1) + p[i] = i + 2 + p[i + 1] = i + 1 + g.append(self.element_class(self, zero, self._P(p))) + zero[-1] = self._C.one() + g.append(self.element_class(self, zero, self._P.identity())) + return tuple(g) + + def matrix_group(self): + """ + Return the matrix group corresponding to ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.matrix_group() + Matrix group over Cyclotomic Field of order 4 and degree 2 with 3 generators ( + [0 1 0] [1 0 0] [ 1 0 0] + [1 0 0] [0 0 1] [ 0 1 0] + [0 0 1], [0 1 0], [ 0 0 zeta4] + ) + """ + from sage.groups.matrix_gps.finitely_generated import MatrixGroup + return MatrixGroup([g.to_matrix() for g in self.gens()]) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + INPUT: + + Either a list of pairs (color, element) + or a pair of lists (colors, elements). + + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: x = C([(2,1), (3,3), (3,2)]); x + [[2, 3, 3], [1, 3, 2]] + sage: x == C([[2,3,3], [1,3,2]]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (color, element)") + c.append(self._C(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of colors and a permutation") + return self.element_class(self, [self._C(v) for v in x[0]], self._P(x[1])) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: C.has_coerce_map_from(S) + True + + sage: C = ColoredPermutations(4, 3) + sage: C.has_coerce_map_from(S) + False + sage: S = SignedPermutations(4) + sage: C.has_coerce_map_from(S) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [P._C.zero()]*P._n, x) + if self._m == 2 and isinstance(C, SignedPermutations) and C._n == self._n: + return lambda P, x: P.element_class(P, + [P._C.zero() if v == 1 else P._C.one() + for v in x._colors], + x._perm) + return super(ColoredPermutations, self)._coerce_map_from_(C) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 2) + sage: [x for x in C] + [[[0, 0], [1, 2]], + [[0, 1], [1, 2]], + [[1, 0], [1, 2]], + [[1, 1], [1, 2]], + [[0, 0], [2, 1]], + [[0, 1], [2, 1]], + [[1, 0], [2, 1]], + [[1, 1], [2, 1]]] + """ + C = CartesianProduct(*[self._C] * self._n) + for p in self._P: + for c in C: + yield self.element_class(self, c, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.cardinality() + 384 + sage: C.cardinality() == 4**3 * factorial(3) + True + """ + return self._m ** self._n * self._P.cardinality() + + def rank(self): + """ + Return the rank of ``self``. + + The rank of a complex reflection group is equal to the dimension + of the complex vector space the group acts on. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 12) + sage: C.rank() + 12 + sage: C = ColoredPermutations(7, 4) + sage: C.rank() + 4 + sage: C = ColoredPermutations(1, 4) + sage: C.rank() + 3 + """ + if self._m == 1: + return self._n - 1 + return self._n + + def degrees(self): + """ + Return the degrees of ``self``. + + The degrees of a complex reflection group are the degrees of + the fundamental invariants of the ring of polynomial invariants. + + If `m = 1`, then we are in the special case of the symmetric group + and the degrees are `(2, 3, \ldots, n, n+1)`. Otherwise the degrees + are `(m, 2m, \ldots, nm)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.degrees() + [4, 8, 12] + sage: S = ColoredPermutations(1, 3) + sage: S.degrees() + [2, 3] + + We now check that the product of the degrees is equal to the + cardinality of ``self``:: + + sage: prod(C.degrees()) == C.cardinality() + True + sage: prod(S.degrees()) == S.cardinality() + True + """ + if self._m == 1: # Special case for the usual symmetric group + return range(2, self._n + 1) + return [self._m * i for i in range(1, self._n + 1)] + + def codegrees(self): + r""" + Return the codegrees of ``self``. + + Let `G` be a complex reflection group. The codegrees + `d_1^* \leq d_2^* \leq \cdots \leq d_{\ell}^*` of `G` can be + defined by: + + .. MATH:: + + \prod_{i=1}^{\ell} (q - d_i^* - 1) + = \sum_{g \in G} \det(g) q^{\dim(V^g)}, + + where `V` is the natural complex vector space that `G` acts on + and `\ell` is the :meth:`rank`. + + If `m = 1`, then we are in the special case of the symmetric group + and the codegrees are `(n-2, n-3, \ldots 1, 0)`. Otherwise the degrees + are `((n-1)m, (n-2)m, \ldots, m, 0)`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.codegrees() + [8, 4, 0] + sage: S = ColoredPermutations(1, 3) + sage: S.codegrees() + [1, 0] + + TESTS: + + We check the polynomial identity:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(3, 2) + sage: f = prod(q - ds - 1 for ds in C.codegrees()) + sage: d = lambda x: sum(1 for e in x.to_matrix().eigenvalues() if e == 1) + sage: g = sum(det(x.to_matrix()) * q**d(x) for x in C) + sage: f == g + True + """ + if self._m == 1: # Special case for the usual symmetric group + return list(reversed(range(self._n - 1))) + return [self._m * i for i in reversed(range(self._n))] + + def number_of_reflection_hyperplanes(self): + """ + Return the number of reflection hyperplanes of ``self``. + + The number of reflection hyperplanes of a complex reflection + group is equal to the sum of the codegrees plus the rank. + + EXAMPLES:: + + sage: C = ColoredPermutations(1, 2) + sage: C.number_of_reflection_hyperplanes() + 1 + sage: C = ColoredPermutations(1, 3) + sage: C.number_of_reflection_hyperplanes() + 3 + sage: C = ColoredPermutations(4, 12) + sage: C.number_of_reflection_hyperplanes() + 276 + """ + return sum(self.codegrees()) + self.rank() + + def fixed_point_polynomial(self, q=None): + r""" + The fixed point polynomial of ``self``. + + The fixed point polynomial `f_G` of a complex reflection group `G` + is counting the dimensions of fixed points subspaces: + + .. MATH:: + + f_G(q) = \sum_{w \in W} q^{\dim V^w}. + + Furthermore, let `d_1, d_2, \ldots, d_{\ell}` be the degrees of `G`, + where `\ell` is the :meth:`rank`. Then the fixed point polynomial + is given by + + .. MATH:: + + f_G(q) = \prod_{i=1}^{\ell} (q + d_i - 1). + + INPUT: + + - ``q`` -- (default: the generator of ``ZZ['q']``) the parameter `q` + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial() + q^3 + 21*q^2 + 131*q + 231 + + sage: S = ColoredPermutations(1, 3) + sage: S.fixed_point_polynomial() + q^2 + 3*q + 2 + + TESTS: + + We check the against the degrees and codegrees:: + + sage: R. = ZZ[] + sage: C = ColoredPermutations(4, 3) + sage: C.fixed_point_polynomial(q) == prod(q + d - 1 for d in C.degrees()) + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + return prod(q + d - 1 for d in self.degrees()) + + def is_well_generated(self): + """ + Return if ``self`` is a well-generated complex reflection group. + + A complex reflection group `G` is well-generated if it is + generated by `\ell` reflections. Equivalently, `G` is well-generated + if `d_i + d_i^* = d_{\ell}` for all `1 \leq i \leq \ell`. + + EXAMPLES:: + + sage: C = ColoredPermutations(4, 3) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(2, 8) + sage: C.is_well_generated() + True + sage: C = ColoredPermutations(1, 4) + sage: C.is_well_generated() + True + """ + deg = self.degrees() + dstar = self.codegrees() + return all(deg[-1] == d + dstar[i] for i, d in enumerate(deg)) + + Element = ColoredPermutation + +##################################################################### +## Signed permutations + + +class SignedPermutation(ColoredPermutation): + """ + A signed permutation. + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: s4*s1*s2*s3*s4 + [-4, 1, 2, -3] + """ + return repr(list(self)) + + _latex_ = _repr_ + + def _mul_(self, other): + """ + Multiply ``self`` and ``other``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x * x + [3, -4, 1, -2] + + :: + + sage: s1*s2*s1 == s1*s2*s1 + True + sage: s3*s4*s3*s4 == s4*s3*s4*s3 + True + """ + colors = tuple(self._colors[i] * other._colors[val - 1] # -1 for indexing + for i, val in enumerate(self._perm)) + p = self._perm._left_to_right_multiply_on_right(other._perm) + return self.__class__(self.parent(), colors, p) + + def inverse(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: ~x + [2, 3, -4, -1] + sage: x * ~x == S.one() + True + """ + ip = ~self._perm + return self.__class__(self.parent(), + tuple([self._colors[i - 1] for i in ip]), # -1 for indexing + ip) + + __invert__ = inverse + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [a for a in x] + [-4, 1, 2, -3] + """ + for i, p in enumerate(self._perm): + yield self._colors[i] * p + + def to_matrix(self): + """ + Return a matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: M = x.to_matrix(); M + [ 0 1 0 0] + [ 0 0 1 0] + [ 0 0 0 -1] + [-1 0 0 0] + + The matrix multiplication is in the *opposite* order:: + + sage: m1,m2,m3,m4 = [g.to_matrix() for g in S.gens()] + sage: M == m4 * m3 * m2 * m1 * m4 + True + """ + return self._perm.to_matrix() * diagonal_matrix(self._colors) + + def has_left_descent(self, i): + """ + Return ``True`` if ``i`` is a left descent of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.gens() + sage: x = s4*s1*s2*s3*s4 + sage: [x.has_left_descent(i) for i in S.index_set()] + [True, False, False, True] + """ + n = self.parent()._n + if i == n: + return self._colors[-1] == -1 + if self._colors[i - 1] == -1: + return self._colors[i] == 1 or self._perm[i - 1] < self._perm[i] + return self._colors[i] == 1 and self._perm[i - 1] > self._perm[i] + + +class SignedPermutations(ColoredPermutations): + r""" + Group of signed permutations. + + The group of signed permutations is also known as the hyperoctahedral + group, the Coxeter group of type `B_n`, and the 2-colored permutation + group. Thus it can be constructed as the wreath product `S_2 \wr S_n`. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: s1,s2,s3,s4 = S.group_generators() + sage: x = s4*s1*s2*s3*s4; x + [-4, 1, 2, -3] + sage: x^4 == S.one() + True + + This is a finite Coxeter group of type `B_n`:: + + sage: S.canonical_representation() + Finite Coxeter group over Universal Cyclotomic Field with Coxeter matrix: + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + sage: S.long_element() + [-4, -3, -2, -1] + sage: S.long_element().reduced_word() + [4, 3, 4, 2, 3, 4, 1, 2, 3, 4] + + We can also go between the 2-colored permutation group:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.an_element() + [-3, 1, 2] + sage: C(S.an_element()) + [[1, 0, 0], [3, 1, 2]] + sage: S(C(S.an_element())) == S.an_element() + True + sage: S(C.an_element()) + [1, 2, 3] + + There is also the natural lift from permutations:: + + sage: P = Permutations(3) + sage: x = S(P.an_element()); x + [3, 1, 2] + sage: x.parent() + Signed permutations of 3 + + REFERENCES: + + - :wikipedia:`Hyperoctahedral_group` + """ + def __init__(self, n): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: TestSuite(S).run() + """ + ColoredPermutations.__init__(self, 2, n, FiniteCoxeterGroups()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SignedPermutations(4) + Signed permutations of 4 + """ + return "Signed permutations of {}".format(self._n) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.one() + [1, 2, 3, 4] + """ + return self.element_class(self, [ZZ.one()] * self._n, + self._P.identity()) + + def simple_reflection(self, i): + r""" + Return the ``i``-th simple reflection of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.simple_reflection(1) + [2, 1, 3, 4] + sage: S.simple_reflection(4) + [1, 2, 3, -4] + """ + if i not in self.index_set(): + raise ValueError("i must be in the index set") + if i < self._n: + p = range(1, self._n + 1) + p[i - 1] = i + 1 + p[i] = i + return self.element_class(self, [ZZ.one()] * self._n, self._P(p)) + temp = [ZZ.one()] * self._n + temp[-1] = -ZZ.one() + return self.element_class(self, temp, self._P.identity()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.gens() + ([2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3], [1, 2, 3, -4]) + """ + return tuple(self.simple_reflection(i) for i in self.index_set()) + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + TESTS:: + + sage: S = SignedPermutations(3) + sage: x = S([(+1,1), (-1,3), (-1,2)]); x + [1, -3, -2] + sage: x == S([[+1,-1,-1], [1,3,2]]) + True + sage: x == S([1, -3, -2]) + True + """ + if isinstance(x, list): + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (sign, element)") + if k[0] != 1 and k[0] != -1: + raise ValueError("the sign must be +1 or -1") + c.append(ZZ(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) == self._n: + c = [] + p = [] + one = ZZ.one() + for v in x: + if v > 0: + c.append(one) + p.append(v) + else: + c.append(-one) + p.append(-v) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of signs and a permutation") + if any(s != 1 and s != -1 for s in x[0]): + raise ValueError("the sign must be +1 or -1") + return self.element_class(self, [ZZ(v) for v in x[0]], self._P(x[1])) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(2) + sage: [x for x in S] + [[1, 2], [1, -2], [-1, 2], [-1, -2], + [2, 1], [2, -1], [-2, 1], [-2, -1]] + """ + one = ZZ.one() + C = CartesianProduct(*[[one, -one]] * self._n) + for p in self._P: + for c in C: + yield self.element_class(self, c, p) + + def _coerce_map_from_(self, C): + """ + Return a coerce map from ``C`` if it exists and ``None`` otherwise. + + EXAMPLES:: + + sage: C = ColoredPermutations(2, 3) + sage: S = SignedPermutations(3) + sage: S.has_coerce_map_from(C) + True + + sage: C = ColoredPermutations(4, 3) + sage: S.has_coerce_map_from(C) + False + + sage: P = Permutations(3) + sage: C.has_coerce_map_from(P) + True + sage: P = Permutations(4) + sage: C.has_coerce_map_from(P) + False + """ + if isinstance(C, Permutations) and C.n == self._n: + return lambda P, x: P.element_class(P, [1]*P._n, x) + if isinstance(C, ColoredPermutations) and C._n == self._n and C._m == 2: + return lambda P, x: P.element_class(P, + [1 if v == 0 else -1 + for v in x._colors], + x._perm) + return super(SignedPermutations, self)._coerce_map_from_(C) + + @cached_method + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.index_set() + (1, 2, 3, 4) + """ + return tuple(range(1, self._n + 1)) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.coxeter_matrix() + [1 3 2 2] + [3 1 3 2] + [2 3 1 4] + [2 2 4 1] + """ + from sage.combinat.root_system.cartan_type import CartanType + return CartanType(['B', self._n]).coxeter_matrix() + + def long_element(self, index_set=None): + """ + Return the longest element of ``self``, or of the + parabolic subgroup corresponding to the given ``index_set``. + + INPUT: + + - ``index_set`` -- (optional) a subset (as a list or iterable) + of the nodes of the indexing set + + EXAMPLES:: + + sage: S = SignedPermutations(4) + sage: S.long_element() + [-4, -3, -2, -1] + """ + if index_set is not None: + return super(SignedPermutations, self).long_element() + p = range(self._n, 0, -1) + return self.element_class(self, [-ZZ.one()] * self._n, self._P(p)) + + Element = SignedPermutation + +# TODO: Make this a subgroup +#class EvenSignedPermutations(SignedPermutations): +# """ +# Group of even signed permutations. +# """ +# def _repr_(self): +# """ +# Return a string representation of ``self``. +# """ +# return "Even signed permtuations of {}".format(self._n) +# +# def __iter__(self): +# """ +# Iterate over ``self``. +# """ +# for s in SignedPermutations.__iter__(self): +# total = 0 +# for pm in s._colors: +# if pm == -1: +# total += 1 +# +# if total % 2 == 0: +# yield s diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 93eaea33942..ee2be8cc2ec 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2774,9 +2774,12 @@ def bell_polynomial(n, k): .. MATH:: - B_{n,k}(x_1, x_2, \ldots, x_{n-k+1}) = \sum_{\sum{j_i}=k, \sum{i j_i} - =n} \frac{n!}{j_1!j_2!\cdots} \frac{x_1}{1!}^j_1 \frac{x_2}{2!}^j_2 - \cdots. + B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = + \sum_{\sum{j_i}=k, \sum{(i+1) j_i}=n} + \frac{n!}{j_0!j_1!\cdots j_{n-k}!} + \left(\frac{x_0}{(0+1)!}\right)^{j_0} + \left(\frac{x_1}{(1+1)!}\right)^{j_1} \cdots + \left(\frac{x_{n-k}}{(n-k+1)!}\right)^{j_{n-k}}. INPUT: @@ -2786,14 +2789,30 @@ def bell_polynomial(n, k): OUTPUT: - - a polynomial in `n-k+1` variables over `\QQ` + - a polynomial in `n-k+1` variables over `\ZZ` EXAMPLES:: sage: bell_polynomial(6,2) - 10*x_3^2 + 15*x_2*x_4 + 6*x_1*x_5 + 10*x2^2 + 15*x1*x3 + 6*x0*x4 sage: bell_polynomial(6,3) - 15*x_2^3 + 60*x_1*x_2*x_3 + 15*x_1^2*x_4 + 15*x1^3 + 60*x0*x1*x2 + 15*x0^2*x3 + + TESTS: + + Check that :trac:`18338` is fixed:: + + sage: bell_polynomial(0,0).parent() + Multivariate Polynomial Ring in x over Integer Ring + + sage: for n in (0..4): + ....: print [bell_polynomial(n,k).coefficients() for k in (0..n)] + [[1]] + [[], [1]] + [[], [1], [1]] + [[], [1], [3], [1]] + [[], [1], [3, 4], [6], [1]] + REFERENCES: @@ -2802,24 +2821,24 @@ def bell_polynomial(n, k): AUTHORS: - Blair Sutton (2009-01-26) + - Thierry Monteil (2015-09-29): the result must always be a polynomial. """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.combinat.partition import Partitions from sage.rings.arith import factorial - vars = ZZ[tuple(['x_'+str(i) for i in range(1, n-k+2)])].gens() - result = 0 + R = PolynomialRing(ZZ, 'x', n-k+1) + vars = R.gens() + result = R.zero() for p in Partitions(n, length=k): - factorial_product = 1 + factorial_product = 1 power_factorial_product = 1 for part, count in p.to_exp_dict().iteritems(): factorial_product *= factorial(count) power_factorial_product *= factorial(part)**count - - coefficient = factorial(n) / (factorial_product * power_factorial_product) + coefficient = factorial(n) // (factorial_product * power_factorial_product) result += coefficient * prod([vars[i - 1] for i in p]) - return result - def fibonacci_sequence(start, stop=None, algorithm=None): r""" Return an iterator over the Fibonacci sequence, for all fibonacci diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index a8692897ac4..523d25953de 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -165,7 +165,7 @@ def are_hyperplanes_in_projective_geometry_parameters(v, k, lmbda, return_parame return (True, (q,d)) if return_parameters else True -def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): +def ProjectiveGeometryDesign(n, d, F, algorithm=None, point_coordinates=True, check=True): """ Return a projective geometry design. @@ -188,6 +188,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): GAP's "design" package must be available in this case, and that it can be installed with the ``gap_packages`` spkg. + - ``point_coordinates`` -- ``True`` by default. Ignored and assumed to be ``False`` if + ``algorithm="gap"``. If ``True``, the ground set is indexed by coordinates in `F^{n+1}`. + Otherwise the ground set is indexed by integers, + EXAMPLES: The set of `d`-dimensional subspaces in a `n`-dimensional projective space @@ -203,6 +207,18 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): sage: PG.is_t_design(return_parameters=True) (True, (2, 85, 5, 1)) + Use coordinates:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3)) + sage: PG.blocks()[0] + [(1, 0, 0), (1, 0, 1), (1, 0, 2), (0, 0, 1)] + + Use indexing by integers:: + + sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3),point_coordinates=0) + sage: PG.blocks()[0] + [0, 1, 2, 12] + Check that the constructor using gap also works:: sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) @@ -224,7 +240,10 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): m.set_immutable() b.append(points[m]) blocks.append(b) - return BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + B = BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) + if point_coordinates: + B.relabel({i:p[0] for p,i in points.iteritems()}) + return B if algorithm == "gap": # Requires GAP's Design from sage.interfaces.gap import gap gap.load_package("design") diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index b34aae434a3..56e822814e7 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -398,7 +398,7 @@ def __contains__(self, block): True sage: ["Am", "I", "finally", "done ?"] in IS False - sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2)) + sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2), point_coordinates=False) sage: [3,8,7] in IS True sage: [3,8,9] in IS @@ -1640,6 +1640,101 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): ll = b return (True, (tt,v,k,ll)) if return_parameters else True + def is_generalized_quadrangle(self, verbose=False, parameters=False): + r""" + Test if the incidence structure is a generalized quadrangle. + + An incidence structure is a generalized quadrangle iff (see [BH12]_, + section 9.6): + + - two blocks intersect on at most one point. + + - For every point `p` not in a block `B`, there is a unique block `B'` + intersecting both `\{p\}` and `B` + + It is a *regular* generalized quadrangle if furthermore: + + - it is `s+1`-:meth:`uniform ` for some positive integer `s`. + + - it is `t+1`-:meth:`regular ` for some positive integer `t`. + + For more information, see the :wikipedia:`Generalized_quadrangle`. + + .. NOTE:: + + Some references (e.g. [PT09]_ or [GQwiki]_) only allow *regular* + generalized quadrangles. To use such a definition, see the + ``parameters`` optional argument described below, or the methods + :meth:`is_regular` and :meth:`is_uniform`. + + INPUT: + + - ``verbose`` (boolean) -- whether to print an explanation when the + instance is not a generalized quadrangle. + + - ``parameters`` (boolean; ``False``) -- if set to ``True``, the + function returns a pair ``(s,t)`` instead of ``True`` answers. In this + case, `s` and `t` are the integers defined above if they exist (each + can be set to ``False`` otherwise). + + EXAMPLE:: + + sage: h = designs.CremonaRichmondConfiguration() + sage: h.is_generalized_quadrangle() + True + + This is actually a *regular* generalized quadrangle:: + + sage: h.is_generalized_quadrangle(parameters=True) + (2, 2) + + TESTS:: + + sage: H = IncidenceStructure((2*graphs.CompleteGraph(3)).edges(labels=False)) + sage: H.is_generalized_quadrangle(verbose=True) + Some point is at distance >3 from some block. + False + + sage: G = graphs.CycleGraph(5) + sage: B = list(G.subgraph_search_iterator(graphs.PathGraph(3))) + sage: H = IncidenceStructure(B) + sage: H.is_generalized_quadrangle(verbose=True) + Two blocks intersect on >1 points. + False + + sage: hypergraphs.CompleteUniform(4,2).is_generalized_quadrangle(verbose=1) + Some point has two projections on some line. + False + """ + # The distance between a point and a line in the incidence graph is odd + # and must be <= 3. Thus, the diameter is at most 4 + g = self.incidence_graph() + if g.diameter() > 4: + if verbose: + print "Some point is at distance >3 from some block." + return False + + # There is a unique projection of a point on a line. Thus, the girth of + # g is at least 7 + girth = g.girth() + if girth == 4: + if verbose: + print "Two blocks intersect on >1 points." + return False + elif girth == 6: + if verbose: + print "Some point has two projections on some line." + return False + + if parameters: + s = self.is_uniform() + t = self.is_regular() + s = s-1 if (s is not False and s>=2) else False + t = t-1 if (t is not False and t>=2) else False + return (s,t) + else: + return True + def dual(self, algorithm=None): """ Return the dual of the incidence structure. diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index a063059f5ec..7752a564c42 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -27,17 +27,18 @@ CombinatorialFreeModuleElement) from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation +from sage.combinat.combinat import bell_number, catalan_number from sage.structure.global_options import GlobalOptions from sage.combinat.set_partition import SetPartitions, SetPartition from sage.combinat.partition import Partitions +from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra_n from sage.combinat.permutation import Permutations -from sage.combinat.combinat import (bell_number, catalan_number) from sage.sets.set import Set from sage.graphs.graph import Graph from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.flatten import flatten from sage.rings.all import ZZ -import operator BrauerDiagramOptions = GlobalOptions(name='Brauer diagram', doc=r""" @@ -1222,7 +1223,8 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): self._q = base_ring(q) self._k = k self._base_diagrams = diagrams - category = Algebras(base_ring).FiniteDimensional().WithBasis().or_subcategory(category) + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() + category = category.or_subcategory(category) CombinatorialFreeModule.__init__(self, base_ring, diagrams, category=category, prefix=prefix, bracket=False) @@ -1244,10 +1246,21 @@ def _element_constructor_(self, set_partition): True sage: D([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R,2) + sage: D(S([2,1])) + P{{-2, 1}, {-1, 2}} + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2(S([1,2])) + P{{-2, 2}, {-1, 1}} + sage: D2(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ if self.basis().keys().is_parent_of(set_partition): return self.basis()[set_partition] - + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return self._apply_module_morphism(set_partition, self._perm_to_Blst, self) sp = self._base_diagrams(set_partition) # attempt conversion if sp in self.basis().keys(): return self.basis()[sp] @@ -1272,6 +1285,27 @@ def __getitem__(self, i): return self.basis()[i] raise ValueError("{0} is not an index of a basis element".format(i)) + def _perm_to_Blst(self, w): + """ + Convert the permutation ``w`` to an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R,2) + sage: import sage.combinat.diagram_algebras as da + sage: D2 = da.DiagramAlgebra(2, x, R, 'P', da.PlanarDiagrams(2)) + sage: D2._perm_to_Blst([2,1]) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element + """ + ## 'perm' is a permutation in one-line notation + ## turns w into an expression suitable for the element constructor. + u = sorted(w) + p = [[u[i],-x] for i,x in enumerate(w)] + return self[p] + def order(self): r""" Return the order of ``self``. @@ -1625,6 +1659,14 @@ class PartitionAlgebra(DiagramAlgebra): sage: a*a 17*P{{-1}, {1}} + Symmetric group algebra elements can also be coerced into the + partition algebra:: + + sage: S = SymmetricGroupAlgebra(SR, 2) + sage: A = PartitionAlgebra(2, x, SR) + sage: S([2,1])*A([[1,-1],[2,-2]]) + P{{-2, 1}, {-1, 2}} + REFERENCES: .. [HR2005] Tom Halverson and Arun Ram, *Partition algebras*. European @@ -1679,6 +1721,31 @@ def _repr_(self): return "Partition Algebra of rank {} with parameter {} over {}".format( self._k, self._q, self.base_ring()) + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = PartitionAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Partition Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + """ + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(PartitionAlgebra, self)._coerce_map_from_(R) + class SubPartitionAlgebra(DiagramAlgebra): """ A subalgebra of the partition algebra indexed by a subset of the diagrams. @@ -1695,15 +1762,11 @@ def __init__(self, k, q, base_ring, prefix, diagrams, category=None): True """ DiagramAlgebra.__init__(self, k, q, base_ring, prefix, diagrams, category) - amb = self.ambient() - self.module_morphism(self.lift, codomain=amb, - category=self.category()).register_as_coercion() - #These methods allow for a sub algebra to be correctly identified in a partition algebra + #These methods allow for a subalgebra to be correctly identified in a partition algebra def ambient(self): r""" Return the partition algebra ``self`` is a sub-algebra of. - Generally, this method is not called directly. EXAMPLES:: @@ -1712,13 +1775,12 @@ def ambient(self): sage: BA.ambient() Partition Algebra of rank 2 with parameter x over Symbolic Ring """ - return PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + return self.lift.codomain() - def lift(self, x): + @lazy_attribute + def lift(self): r""" - Lift a diagram subalgebra element to the corresponding element - in the ambient space. This method is not intended to be called - directly. + Return the lift map from diagram subalgebra to the ambient space. EXAMPLES:: @@ -1730,20 +1792,16 @@ def lift(self, x): sage: lifted.parent() is BA.ambient() True """ - if x not in self: - raise ValueError("{0} is not in {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = 0 - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.ambient().monomial(monomial_indices[i]) - return result + amb = PartitionAlgebra(self._k, self._q, self.base_ring(), prefix=self._prefix) + phi = self.module_morphism(lambda d: amb.monomial(d), + codomain=amb, category=self.category()) + phi.register_as_coercion() + return phi def retract(self, x): r""" Retract an appropriate partition algebra element to the - corresponding element in the partition subalgebra. This method - is not intended to be called directly. + corresponding element in the partition subalgebra. EXAMPLES:: @@ -1754,14 +1812,10 @@ def retract(self, x): sage: BA.retract(E) in BA True """ - if x not in self.ambient() or reduce(operator.mul, [(i in self._indices) for i in x.support()]) == 0: + if ( x not in self.ambient() + or any(i not in self._indices for i in x.support()) ): raise ValueError("{0} cannot retract to {1}".format(x, self)) - monomial_indices = x.support() - monomial_coefficients = x.coefficients() - result = self.zero() - for i in xrange(len(monomial_coefficients)): - result += monomial_coefficients[i]*self.monomial(monomial_indices[i]) - return result + return self._from_dict(x._monomial_coefficients, remove_zeros=False) class BrauerAlgebra(SubPartitionAlgebra): r""" @@ -1789,8 +1843,8 @@ class BrauerAlgebra(SubPartitionAlgebra): EXAMPLES: - We now define the Brauer algebra of rank `2` with parameter ``x`` over - `\ZZ`:: + We now define the Brauer algebra of rank `2` with parameter ``x`` + over `\ZZ`:: sage: R. = ZZ[] sage: B = BrauerAlgebra(2, x, R) @@ -1810,6 +1864,15 @@ class BrauerAlgebra(SubPartitionAlgebra): x*B{{-2, -1}, {1, 2}} sage: b[2]^5 x^4*B{{-2, -1}, {1, 2}} + + Note, also that since the symmetric group algebra is contained in + the Brauer algebra, there is also a conversion between the two. :: + + sage: R. = ZZ[] + sage: B = BrauerAlgebra(2, x, R) + sage: S = SymmetricGroupAlgebra(R, 2) + sage: S([2,1])*B([[1,-1],[2,-2]]) + B{{-2, 1}, {-1, 2}} """ global_options = BrauerDiagramOptions @@ -1857,6 +1920,32 @@ def _repr_(self): return "Brauer Algebra of rank {} with parameter {} over {}".format( self._k, self._q, self.base_ring()) + # TODO: Make a mixin class for diagram algebras that have coercions from SGA? + def _coerce_map_from_(self, R): + """ + Return a coerce map from ``R`` if one exists and ``None`` otherwise. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S = SymmetricGroupAlgebra(R, 4) + sage: A = BrauerAlgebra(4, x, R) + sage: A._coerce_map_from_(S) + Generic morphism: + From: Symmetric group algebra of order 4 over Univariate Polynomial Ring in x over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + sage: Sp = SymmetricGroupAlgebra(QQ, 4) + sage: A._coerce_map_from_(Sp) + Generic morphism: + From: Symmetric group algebra of order 4 over Rational Field + To: Brauer Algebra of rank 4 with parameter x over Univariate Polynomial Ring in x over Rational Field + """ + if isinstance(R, SymmetricGroupAlgebra_n): + if R.n == self._k and self.base_ring().has_coerce_map_from(R.base_ring()): + return R.module_morphism(self._perm_to_Blst, codomain=self) + return None + return super(BrauerAlgebra, self)._coerce_map_from_(R) + def _element_constructor_(self, set_partition): r""" Construct an element of ``self``. @@ -1987,7 +2076,16 @@ def _element_constructor_(self, set_partition): True sage: TL([{1,2},{-1,-2}]) == b_elt True + sage: S = SymmetricGroupAlgebra(R, 2) + sage: TL(S([1,2])) + T{{-2, 2}, {-1, 1}} + sage: TL(S([2,1])) + Traceback (most recent call last): + ... + ValueError: {{-2, 1}, {-1, 2}} is not an index of a basis element """ + if isinstance(set_partition, SymmetricGroupAlgebra_n.Element): + return SubPartitionAlgebra._element_constructor_(self, set_partition) set_partition = to_Brauer_partition(set_partition, k = self.order()) return SubPartitionAlgebra._element_constructor_(self, set_partition) @@ -2146,7 +2244,7 @@ def __init__(self, k, q, base_ring, prefix): sage: TestSuite(I).run() # Not tested -- needs non-unital algebras category """ # This should be the category of non-unital fin-dim algebras with basis - category = Algebras(base_ring).FiniteDimensional().WithBasis() + category = Algebras(base_ring.category()).FiniteDimensional().WithBasis() SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, IdealDiagrams(k), category) diff --git a/src/sage/combinat/integer_list.py b/src/sage/combinat/integer_list.py index 350c83cf3e4..774ea865bad 100644 --- a/src/sage/combinat/integer_list.py +++ b/src/sage/combinat/integer_list.py @@ -126,7 +126,8 @@ class IntegerListsLex(Parent): (default: `ClonableArray`). This merely sets the attribute ``self.Element``. See the examples for details. - - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` + - ``global_options`` -- (deprecated) a + :class:`~sage.structure.global_options.GlobalOptions` object that will be assigned to the attribute ``_global_options``; for internal use only (subclasses, ...). @@ -524,6 +525,10 @@ class IntegerListsLex(Parent): in the class :class:`Partition`:: sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() + doctest:...: DeprecationWarning: the global_options argument is + deprecated since, in general, pickling is broken; + create your own class instead + See http://trac.sagemath.org/15525 for details. [[3], [2, 1], [1, 1, 1]] Note that the :class:`Partition` further assumes the existence of @@ -940,6 +945,9 @@ def __init__(self, element_constructor = self._element_constructor_nocheck self._element_constructor_is_copy_safe = True if global_options is not None: + from sage.misc.superseded import deprecation + deprecation(15525, 'the global_options argument is deprecated since, in general,' + ' pickling is broken; create your own class instead') self.global_options = global_options Parent.__init__(self, element_constructor=element_constructor, diff --git a/src/sage/combinat/kazhdan_lusztig.py b/src/sage/combinat/kazhdan_lusztig.py index 5a7dc5e62df..915adcde9f9 100644 --- a/src/sage/combinat/kazhdan_lusztig.py +++ b/src/sage/combinat/kazhdan_lusztig.py @@ -66,7 +66,7 @@ class KazhdanLusztigPolynomial(UniqueRepresentation, SageObject): sage: W = CoxeterGroup(['B', 3], implementation='coxeter3') # optional - coxeter3 sage: W.kazhdan_lusztig_polynomial([2], [3,2,3,1,2]) # optional - coxeter3 - 1 + q + q + 1 """ def __init__(self, W, q, trace=False): """ diff --git a/src/sage/combinat/ncsf_qsym/generic_basis_code.py b/src/sage/combinat/ncsf_qsym/generic_basis_code.py index e27e00e3ece..ea514d73de7 100644 --- a/src/sage/combinat/ncsf_qsym/generic_basis_code.py +++ b/src/sage/combinat/ncsf_qsym/generic_basis_code.py @@ -978,15 +978,16 @@ def degree(self): sage: S.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree sage: F = QuasiSymmetricFunctions(QQ).F() sage: F.zero().degree() Traceback (most recent call last): ... - ValueError: The zero element does not have a well-defined degree. + ValueError: the zero element does not have a well-defined degree """ return self.maximal_degree() + class AlgebraMorphism(ModuleMorphismByLinearity): # Find a better name """ A class for algebra morphism defined on a free algebra from the image of the generators diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 76eaa5f17ad..cc4d0db3927 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -4032,7 +4032,8 @@ def remove_horizontal_border_strip(self, k): sage: Partition([5,3,1]).remove_horizontal_border_strip(6).list() [] - The result is returned as an instance of :class:`IntegerListsLex`:: + The result is returned as an instance of + :class:`Partitions_with_constraints`:: sage: Partition([5,3,1]).remove_horizontal_border_strip(5) The subpartitions of [5, 3, 1] obtained by removing an horizontal border strip of length 5 @@ -4048,15 +4049,13 @@ def remove_horizontal_border_strip(self, k): sage: Partition([]).remove_horizontal_border_strip(6).list() [] """ - return IntegerListsLex(n = self.size()-k, - min_length = len(self)-1, - max_length = len(self), - floor = self[1:]+[0], - ceiling = self[:], - max_slope = 0, - element_class = Partition, - global_options = Partitions.global_options, - name = "The subpartitions of %s obtained by removing an horizontal border strip of length %s"%(self,k)) + return Partitions_with_constraints(n = self.size()-k, + min_length = len(self)-1, + max_length = len(self), + floor = self[1:]+[0], + ceiling = self[:], + max_slope = 0, + name = "The subpartitions of {} obtained by removing an horizontal border strip of length {}".format(self,k)) def k_conjugate(self, k): r""" @@ -4658,8 +4657,8 @@ class Partitions(UniqueRepresentation, Parent): Valid keywords are: ``starting``, ``ending``, ``min_part``, ``max_part``, ``max_length``, ``min_length``, ``length``, - ``max_slope``, ``min_slope``, ``inner``, ``outer``, and - ``parts_in``. They have the following meanings: + ``max_slope``, ``min_slope``, ``inner``, ``outer``, ``parts_in`` + and ``regular``. They have the following meanings: - ``starting=p`` specifies that the partitions should all be less than or equal to `p` in lex order. This argument cannot be combined @@ -4693,6 +4692,19 @@ class Partitions(UniqueRepresentation, Parent): integers. This argument cannot be combined with any other (see :trac:`15467`). + - ``regular=ell`` specifies that the partitions are `\ell`-regular, + and can only be combined with the ``max_length`` or ``max_part``, but + not both, keywords if `n` is not specified + + The ``max_*`` versions, along with ``inner`` and ``ending``, work + analogously. + + Right now, the ``parts_in``, ``starting``, ``ending``, and ``regular`` + keyword arguments are mutually exclusive, both of each other and of other + keyword arguments. If you specify, say, ``parts_in``, all other + keyword arguments will be ignored; ``starting``, ``ending``, and + ``regular`` work the same way. + EXAMPLES: If no arguments are passed, then the combinatorial class @@ -4769,6 +4781,16 @@ class Partitions(UniqueRepresentation, Parent): sage: Partitions(10, min_part=2, length=3).list() [[6, 2, 2], [5, 3, 2], [4, 4, 2], [4, 3, 3]] + Some examples using the ``regular`` keyword:: + + sage: Partitions(regular=4) + 4-Regular Partitions + sage: Partitions(regular=4, max_length=3) + 4-Regular Partitions with max length 3 + sage: Partitions(regular=4, max_part=3) + 4-Regular 3-Bounded Partitions + sage: Partitions(3, regular=4) + 4-Regular Partitions of the integer 3 Here are some further examples using various constraints:: @@ -4826,7 +4848,7 @@ class Partitions(UniqueRepresentation, Parent): sage: TestSuite(Partitions(0)).run() sage: TestSuite(Partitions(5)).run() - sage: TestSuite(Partitions(5, min_part=2)).run() # Not tested: todo - IntegerListsLex needs to pickle properly + sage: TestSuite(Partitions(5, min_part=2)).run() sage: repr( Partitions(5, min_part=2) ) 'Partitions of the integer 5 satisfying constraints min_part=2' @@ -4940,12 +4962,21 @@ def __classcall_private__(cls, n=None, **kwargs): raise ValueError("n cannot be infinite") if n is None or n is NN or n is NonNegativeIntegers(): if len(kwargs) > 0: - if len(kwargs) == 1 and 'max_part' in kwargs: - return Partitions_all_bounded(kwargs['max_part']) - else: - raise ValueError("the size must be specified with any keyword argument") - else: - return Partitions_all() + if len(kwargs) == 1: + if 'max_part' in kwargs: + return Partitions_all_bounded(kwargs['max_part']) + if 'regular' in kwargs: + return RegularPartitions_all(kwargs['regular']) + elif len(kwargs) == 2: + if 'regular' in kwargs: + if kwargs['regular'] < 2: + raise ValueError("the regularity must be at least 2") + if 'max_part' in kwargs: + return RegularPartitions_bounded(kwargs['regular'], kwargs['max_part']) + if 'max_length' in kwargs: + return RegularPartitions_truncated(kwargs['regular'], kwargs['max_length']) + raise ValueError("the size must be specified with any keyword argument") + return Partitions_all() elif isinstance(n, (int,Integer)): if len(kwargs) == 0: return Partitions_n(n) @@ -4964,11 +4995,13 @@ def __classcall_private__(cls, n=None, **kwargs): "'ending' cannot be combined with anything else.") if 'parts_in' in kwargs: - return Partitions_parts_in(n, kwargs['parts_in']) + return Partitions_parts_in(n, kwargs['parts_in']) elif 'starting' in kwargs: return Partitions_starting(n, kwargs['starting']) elif 'ending' in kwargs: return Partitions_ending(n, kwargs['ending']) + elif 'regular' in kwargs: + return RegularPartitions_n(n, kwargs['regular']) # FIXME: should inherit from IntegerListLex, and implement repr, or _name as a lazy attribute kwargs['name'] = "Partitions of the integer %s satisfying constraints %s"%(n, ", ".join( ["%s=%s"%(key, kwargs[key]) for key in sorted(kwargs.keys())] )) @@ -4995,13 +5028,10 @@ def __classcall_private__(cls, n=None, **kwargs): kwargs['min_length'] = max(len(inner), kwargs.get('min_length',0)) del kwargs['inner'] + return Partitions_with_constraints(n, **kwargs) - kwargs['element_class'] = Partition - kwargs['global_options'] = Partitions.global_options - return IntegerListsLex(n, **kwargs) - else: - raise ValueError("n must be an integer or be equal to one of "+ - "None, NN, NonNegativeIntegers()") + raise ValueError("n must be an integer or be equal to one of " + "None, NN, NonNegativeIntegers()") def __init__(self, is_infinite=False): """ @@ -6614,14 +6644,446 @@ def __setstate__(self, data): [[2, 1], [1, 1, 1]] """ n = data['n'] - self.__class__ = IntegerListsLex + self.__class__ = Partitions_with_constraints constraints = {'max_slope' : 0, - 'min_part' : 1, - 'element_class' : Partition, - 'global_options' : Partitions.global_options} + 'min_part' : 1} constraints.update(data['constraints']) self.__init__(n, **constraints) +class Partitions_with_constraints(IntegerListsLex): + """ + Partitions which satisfy a set of constraints. + + EXAMPLES:: + + sage: P = Partitions(6, inner=[1,1], max_slope=-1) + sage: list(P) + [[5, 1], [4, 2], [3, 2, 1]] + + TESTS:: + + sage: P = Partitions(6, min_part=2, max_slope=-1) + sage: TestSuite(P).run() + + Test that :trac:`15525` is fixed:: + + sage: loads(dumps(P)) == P + True + """ +# def __init__(self, n, **kwargs): +# """ +# Initialize ``self``. +# """ +# IntegerListsLex.__init__(self, n, **kwargs) + + Element = Partition + global_options = PartitionOptions + +###################### +# Regular Partitions # +###################### + +class RegularPartitions(Partitions): + r""" + Base class for `\ell`-regular partitions. + + Let `\ell` be a positive integer. A partition `\lambda` is + `\ell`-*regular* if `m_i < \ell` for all `i`, where `m_i` is the + multiplicity of `i` in `\lambda`. + + .. NOTE:: + + This is conjugate to the notion of `\ell`-*restricted* partitions, + where the difference between any two parts is at most `\ell`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``is_infinite`` -- boolean; if the subset of `\ell`-regular + partitions is infinite + """ + def __init__(self, ell, is_infinte=False): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: TestSuite(P).run() + """ + self._ell = ell + Partitions.__init__(self, is_infinte) + + def ell(self): + r""" + Return the value `\ell`. + + EXAMPLES:: + + sage: P = Partitions(regular=2) + sage: P.ell() + 2 + """ + return self._ell + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=3) + sage: [5] in P + True + sage: [] in P + True + sage: [3, 3, 2, 2] in P + True + sage: [3, 3, 3, 1] in P + False + sage: [4, 0, 0, 0, 0, 0] in P + True + sage: Partition([4,2,2,1]) in P + True + sage: Partition([4,2,2,2]) in P + False + sage: Partition([10,1]) in P + True + """ + if not Partitions.__contains__(self, x): + return False + if isinstance(x, Partition): + return max(x.to_exp(1)) < self._ell + return all(x.count(i) < self._ell for i in set(x) if i > 0) + + def _fast_iterator(self, n, max_part): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2], [3, 1, 1], [2, 2, 1]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + if n == 0: + yield [] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_all(RegularPartitions): + r""" + The class of all `\ell`-regular partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell, True) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_all + sage: RegularPartitions_all(3) + 3-Regular Partitions + """ + return "{}-Regular Partitions".format(self._ell) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [2, 1, 1]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + +class RegularPartitions_truncated(RegularPartitions): + r""" + The class of `\ell`-regular partitions with max length `k`. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``max_len`` -- integer; the maximum length + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, max_len): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: TestSuite(P).run() + """ + self._max_len = max_len + RegularPartitions.__init__(self, ell, True) + + def max_length(self): + """ + Return the maximum length of the partitions of ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_length=3) + sage: P.max_length() + 3 + """ + return self._max_len + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_length=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1, 1] in P + False + """ + return len(x) <= self._max_len and RegularPartitions.__contains__(self, x) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_truncated + sage: RegularPartitions_truncated(4, 3) + 4-Regular Partitions with max length 3 + """ + return "{}-Regular Partitions with max length {}".format(self._ell, self._max_len) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=3, max_length=2) + sage: it = P.__iter__() + sage: [it.next() for x in range(10)] + [[], [1], [2], [1, 1], [3], [2, 1], [4], [3, 1], [2, 2], [5]] + """ + n = 0 + while True: + for p in self._fast_iterator(n, n): + yield self.element_class(self, p) + n += 1 + + def _fast_iterator(self, n, max_part, depth=0): + """ + A fast (recursive) iterator which returns a list. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_length=2) + sage: list(P._fast_iterator(5, 5)) + [[5], [4, 1], [3, 2]] + sage: list(P._fast_iterator(5, 3)) + [[3, 2]] + sage: list(P._fast_iterator(5, 6)) + [[5], [4, 1], [3, 2]] + """ + if n == 0 or depth >= self._max_len: + yield [] + return + + # Special case + if depth + 1 == self._max_len: + if max_part >= n: + yield [n] + return + + if n < max_part: + max_part = n + bdry = self._ell - 1 + + for i in reversed(range(1, max_part+1)): + for p in self._fast_iterator(n-i, i, depth+1): + if p.count(i) < bdry: + yield [i] + p + +class RegularPartitions_bounded(RegularPartitions): + r""" + The class of `\ell`-regular `k`-bounded partitions. + + INPUT: + + - ``ell`` -- the integer `\ell` + - ``k`` -- integer; the value `k` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, ell, k): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=4, max_part=3) + sage: TestSuite(P).run() + """ + self.k = k + RegularPartitions.__init__(self, ell, False) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(regular=4, max_part=3) + sage: [3, 3, 3] in P + True + sage: [] in P + True + sage: [4, 2, 1] in P + False + """ + return len(x) == 0 or (x[0] <= self.k and RegularPartitions.__contains__(self, x)) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_bounded + sage: RegularPartitions_bounded(4, 3) + 4-Regular 3-Bounded Partitions + """ + return "{}-Regular {}-Bounded Partitions".format(self._ell, self.k) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(regular=2, max_part=3) + sage: list(P) + [[3, 2, 1], [3, 2], [3, 1], [3], [2, 1], [2], [1], []] + """ + k = self.k + for n in reversed(range(k*(k+1)/2 * self._ell)): + for p in self._fast_iterator(n, k): + yield self.element_class(self, p) + +class RegularPartitions_n(RegularPartitions, Partitions_n): + r""" + The class of `\ell`-regular partitions of `n`. + + INPUT: + + - ``n`` -- the integer `n` to partition + - ``ell`` -- the integer `\ell` + + .. SEEALSO:: + + :class:`~sage.combinat.partition.RegularPartitions` + """ + def __init__(self, n, ell): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: TestSuite(P).run() + """ + RegularPartitions.__init__(self, ell) + Partitions_n.__init__(self, n) + + def _repr_(self): + """ + TESTS:: + + sage: from sage.combinat.partition import RegularPartitions_n + sage: RegularPartitions_n(3, 5) + 5-Regular Partitions of the integer 3 + """ + return "{}-Regular Partitions of the integer {}".format(self._ell, self.n) + + def __contains__(self, x): + """ + TESTS:: + + sage: P = Partitions(5, regular=3) + sage: [3, 1, 1] in P + True + sage: [3, 2, 1] in P + False + """ + return RegularPartitions.__contains__(self, x) and sum(x) == self.n + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: list(P) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + """ + for p in self._fast_iterator(self.n, self.n): + yield self.element_class(self, p) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: P = Partitions(5, regular=3) + sage: P.cardinality() + 5 + sage: P = Partitions(5, regular=6) + sage: P.cardinality() + 7 + sage: P.cardinality() == Partitions(5).cardinality() + True + """ + if self._ell > self.n: + return Partitions_n.cardinality(self) + return ZZ.sum(1 for x in self) ###################### # Ordered Partitions # diff --git a/src/sage/combinat/perfect_matching.py b/src/sage/combinat/perfect_matching.py index 40852a14e1f..74f0ebe0c59 100644 --- a/src/sage/combinat/perfect_matching.py +++ b/src/sage/combinat/perfect_matching.py @@ -808,6 +808,35 @@ def to_permutation(self): from sage.combinat.permutation import Permutation return Permutation(self.value) + def to_non_crossing_set_partition(self): + r""" + Returns the noncrossing set partition (on half as many elements) + corresponding to the perfect matching if the perfect matching is + noncrossing, and otherwise gives an error. + + OUTPUT: + + The realization of ``self`` as a noncrossing set partition. + + EXAMPLES:: + + sage: PerfectMatching([[1,3], [4,2]]).to_non_crossing_set_partition() + Traceback (most recent call last): + ... + ValueError: matching must be non-crossing + sage: PerfectMatching([[1,4], [3,2]]).to_non_crossing_set_partition() + {{1, 2}} + sage: PerfectMatching([]).to_non_crossing_set_partition() + {} + """ + from sage.combinat.set_partition import SetPartition + if not self.is_non_crossing(): + raise ValueError("matching must be non-crossing") + else: + perm = self.to_permutation() + perm2 = Permutation([(perm[2*i])/2 for i in range(len(perm)/2)]) + return SetPartition(perm2.cycle_tuples()) + class PerfectMatchings(UniqueRepresentation, Parent): r""" diff --git a/src/sage/combinat/posets/elements.py b/src/sage/combinat/posets/elements.py index b27afd59980..0cf391ef992 100644 --- a/src/sage/combinat/posets/elements.py +++ b/src/sage/combinat/posets/elements.py @@ -51,6 +51,17 @@ def __init__(self, poset, element, vertex): self.element = element self.vertex = vertex + def __hash__(self): + r""" + TESTS:: + + sage: P = Poset([[1,2],[4],[3],[4],[]], facade = False) + sage: e = P(0) + sage: hash(e) + 0 + """ + return hash(self.element) + def _repr_(self): """ TESTS:: diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 58245aa7aca..bd2afa2c60c 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -114,11 +114,11 @@ :widths: 30, 70 :delim: | - :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if given iterable is a chain of the poset. - :meth:`~FinitePoset.chains` | Return all the chains of ``self``. + :meth:`~FinitePoset.is_chain_of_poset` | Return ``True`` if the given list is a chain of the poset. + :meth:`~FinitePoset.chains` | Return the chains of the poset. :meth:`~FinitePoset.antichains` | Return the antichains of the poset. - :meth:`~FinitePoset.maximal_chains` | Return all maximal chains of the poset. - :meth:`~FinitePoset.maximal_antichains` | Return all maximal antichains of the poset. + :meth:`~FinitePoset.maximal_chains` | Return the maximal chains of the poset. + :meth:`~FinitePoset.maximal_antichains` | Return the maximal antichains of the poset. :meth:`~FinitePoset.antichains_iterator` | Return an iterator over the antichains of the poset. **Drawing** @@ -1236,9 +1236,9 @@ def hasse_diagram(self, wrapped = True): sage: P = Poset((divisors(15), attrcall("divides")), facade = False) sage: H = P.hasse_diagram() sage: H.vertices() - [1, 5, 3, 15] + [1, 3, 5, 15] sage: H.edges() - [(1, 3, None), (1, 5, None), (5, 15, None), (3, 15, None)] + [(1, 3, None), (1, 5, None), (3, 15, None), (5, 15, None)] sage: H.set_latex_options(format="dot2tex") # optional - dot2tex sage: view(H, tight_page=True) # optional - dot2tex, not tested (opens external window) """ @@ -2400,77 +2400,61 @@ def is_chain(self): """ return self._hasse_diagram.is_chain() - def is_chain_of_poset(self, o, ordered=False): + def is_chain_of_poset(self, elms, ordered=False): """ - Return whether an iterable ``o`` is a chain of ``self``, - including a check for ``o`` being ordered from smallest - to largest element if the keyword ``ordered`` is set to - ``True``. + Return ``True`` if `elms` is a chain of the poset, and ``False`` otherwise. INPUT: - - ``o`` -- an iterable (e. g., list, set, or tuple) - containing some elements of ``self`` - - - ``ordered`` -- a Boolean (default: ``False``) which - decides whether the notion of a chain includes being - ordered - - OUTPUT: - - If ``ordered`` is set to ``False``, the truth value of - the following assertion is returned: The subset of ``self`` - formed by the elements of ``o`` is a chain in ``self``. + - ``elms`` -- a list or other iterable containing some elements + of the poset - If ``ordered`` is set to ``True``, the truth value of - the following assertion is returned: Every element of the - list ``o`` is (strictly!) smaller than its successor in - ``self``. (This makes no sense if ``ordered`` is a set.) + - ``ordered`` -- a Boolean. If ``True``, then return ``True`` + only if elements in `elms` are strictly increasing in the + poset; this makes no sense if `elms` is a set. If ``False`` + (the default), then elements can be repeated and be in any + order. EXAMPLES:: sage: P = Poset((divisors(12), attrcall("divides"))) sage: sorted(P.list()) [1, 2, 3, 4, 6, 12] - sage: P.is_chain_of_poset([2, 4]) - True - sage: P.is_chain_of_poset([12, 6]) + sage: P.is_chain_of_poset([12, 3]) True - sage: P.is_chain_of_poset([12, 6], ordered=True) + sage: P.is_chain_of_poset({3, 4, 12}) False - sage: P.is_chain_of_poset([6, 12], ordered=True) - True - sage: P.is_chain_of_poset(()) - True - sage: P.is_chain_of_poset((), ordered=True) - True - sage: P.is_chain_of_poset((3, 4, 12)) + sage: P.is_chain_of_poset([12, 3], ordered=True) False - sage: P.is_chain_of_poset((3, 6, 12, 1)) - True - sage: P.is_chain_of_poset((3, 6, 12, 1), ordered=True) - False - sage: P.is_chain_of_poset((3, 6, 12), ordered=True) - True sage: P.is_chain_of_poset((1, 1, 3)) True sage: P.is_chain_of_poset((1, 1, 3), ordered=True) False sage: P.is_chain_of_poset((1, 3), ordered=True) True - sage: P.is_chain_of_poset((6, 1, 1, 3)) + + TESTS:: + + sage: P = Posets.BooleanLattice(4) + sage: P.is_chain_of_poset([]) True - sage: P.is_chain_of_poset((2, 1, 1, 3)) + sage: P.is_chain_of_poset((1,3,7,15,14)) False + sage: P.is_chain_of_poset({10}) + True + sage: P.is_chain_of_poset([32]) + Traceback (most recent call last): + ... + ValueError: element (=32) not in poset """ if ordered: - sorted_o = o + sorted_o = elms return all(self.lt(a, b) for a, b in zip(sorted_o, sorted_o[1:])) else: # _element_to_vertex can be assumed to be a linear extension # of the poset according to the documentation of class # HasseDiagram. - sorted_o = sorted(o, key=self._element_to_vertex) + sorted_o = sorted(elms, key=self._element_to_vertex) return all(self.le(a, b) for a, b in zip(sorted_o, sorted_o[1:])) def is_connected(self): @@ -3309,17 +3293,22 @@ def isomorphic_subposets(self, other): def antichains(self, element_constructor = __builtin__.list): """ - Returns the antichains of the poset. + Return the antichains of the poset. + + An *antichain* of a poset is a set of elements of the + poset that are pairwise incomparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: list) + argument (default: ``list``) - OUTPUT: an enumerated set + OUTPUT: - An *antichain* of a poset is a collection of elements of the - poset that are pairwise incomparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all antichains of the poset, each of which is given as an + ``element_constructor.`` EXAMPLES:: @@ -3331,10 +3320,12 @@ def antichains(self, element_constructor = __builtin__.list): 8 sage: A[3] [1, 2] - sage: list(Posets.AntichainPoset(3).antichains()) - [[], [2], [2, 1], [2, 1, 0], [2, 0], [1], [1, 0], [0]] - sage: list(Posets.ChainPoset(3).antichains()) - [[], [0], [1], [2]] + + To get the antichains as, say, sets, one may use the + ``element_constructor`` option:: + + sage: list(Posets.ChainPoset(3).antichains(element_constructor=set)) + [set(), {0}, {1}, {2}] To get the antichains of a given size one can currently use:: @@ -3345,12 +3336,6 @@ def antichains(self, element_constructor = __builtin__.list): sage: A.subset(size = 2) # todo: not implemented - To get the antichains as, say, sets, one may use the - ``element_constructor`` option:: - - sage: list(Posets.ChainPoset(3).antichains(element_constructor = set)) - [set(), {0}, {1}, {2}] - .. NOTE:: Internally, this uses @@ -3366,7 +3351,7 @@ def antichains(self, element_constructor = __builtin__.list): On the other hand, this returns a full featured enumerated set, with containment testing, etc. - .. seealso:: :meth:`maximal_antichains` + .. seealso:: :meth:`maximal_antichains`, :meth:`chains` """ vertex_to_element = self._vertex_to_element @@ -3378,12 +3363,14 @@ def f(antichain): def antichains_iterator(self): """ - Returns an iterator over the antichains of the poset. + Return an iterator over the antichains of the poset. EXAMPLES:: - sage: Posets.PentagonPoset().antichains_iterator() + sage: it = Posets.PentagonPoset().antichains_iterator(); it + sage: it.next(), it.next() + ([], [4]) .. SEEALSO:: :meth:`antichains` """ @@ -3395,8 +3382,9 @@ def width(self): r""" Return the width of the poset (the size of its longest antichain). - It is computed through a matching in a bipartite graph. See - :wikipedia:`Dilworth's_theorem` for more information. + It is computed through a matching in a bipartite graph; see + :wikipedia:`Dilworth's_theorem` for more information. The width is + also called Dilworth number. EXAMPLES:: @@ -3489,51 +3477,47 @@ def dilworth_decomposition(self): def chains(self, element_constructor=__builtin__.list, exclude=None): """ - Return all the chains of ``self``. + Return the chains of the poset. + + A *chain* of a poset is a set of elements of the poset + that are pairwise comparable. INPUT: - ``element_constructor`` -- a function taking an iterable as - argument (default: ``list``) + argument (default: ``list``) - ``exclude`` -- elements of the poset to be excluded (default: ``None``) OUTPUT: - The enumerated set of all chains of ``self``, each of which - is given as an ``element_constructor``. - - A *chain* of a poset is a set of elements of the poset - that are pairwise comparable. + The enumerated set (of type + :class:`~sage.combinat.subsets_pairwise.PairwiseCompatibleSubsets`) + of all chains of the poset, each of which is given as an + ``element_constructor``. EXAMPLES:: - sage: A = Posets.PentagonPoset().chains(); A + sage: C = Posets.PentagonPoset().chains(); C Set of chains of Finite lattice containing 5 elements - sage: list(A) + sage: list(C) [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4], [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]] - To get the chains of a given size one can currently use:: - - sage: list(A.elements_of_depth_iterator(2)) - [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] - - For bounded posets, one can exclude the bounds as follows:: - - sage: P = Posets.DiamondPoset(5) - sage: list(P.chains(exclude=[0, 4])) - [[], [1], [2], [3]] - - Another example of exclusion of vertices:: + Exclusion of vertices, tuple (instead of list) as constructor:: sage: P = Poset({1: [2, 3], 2: [4], 3: [4, 5]}) sage: list(P.chains(element_constructor=tuple, exclude=[3])) [(), (1,), (1, 2), (1, 2, 4), (1, 4), (1, 5), (2,), (2, 4), (4,), (5,)] + To get the chains of a given size one can currently use:: + + sage: list(C.elements_of_depth_iterator(2)) + [[0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 3], [2, 4], [3, 4]] + Eventually the following syntax will be accepted:: - sage: A.subset(size = 2) # todo: not implemented + sage: C.subset(size = 2) # todo: not implemented .. SEEALSO:: :meth:`maximal_chains`, :meth:`antichains` """ @@ -4456,9 +4440,57 @@ def incomparability_graph(self): G.rename('Incomparability graph on %s vertices' % self.cardinality()) return G + def linear_extensions_graph(self): + r""" + Return the linear extensions graph of the poset. + + Vertices of the graph are linear extensions of the poset. + Two vertices are connected by an edge if the linear extensions + differ by only one adjacent transposition. + + EXAMPLES:: + + sage: P = Poset({1:[3,4],2:[4]}) + sage: G = P.linear_extensions_graph(); G + Graph on 5 vertices + sage: G.degree_sequence() + [3, 2, 2, 2, 1] + + sage: chevron = Poset({1:[2,6], 2:[3], 4:[3,5], 6:[5]}) + sage: G = chevron.linear_extensions_graph(); G + Graph on 22 vertices + sage: G.size() + 36 + + TESTS:: + + sage: Poset().linear_extensions_graph() + Graph on 1 vertex + + sage: A4 = Posets.AntichainPoset(4) + sage: G = A4.linear_extensions_graph() + sage: G.is_regular() + True + """ + from sage.graphs.graph import Graph + # Direct implementation, no optimizations + L = self.linear_extensions() + G = Graph() + G.add_vertices(L) + for i in range(len(L)): + for j in range(i): + tmp = map(lambda x,y: x != y, L[i], L[j]) + if tmp.count(True) == 2 and tmp[tmp.index(True)+1]: + G.add_edge(L[i], L[j]) + return G + def maximal_antichains(self): """ - Return all maximal antichains of the poset. + Return the maximal antichains of the poset. + + An antichain `a` of poset `P` is *maximal* if there is + no element `e \in P \setminus a` such that `a \cup \{e\}` + is an antichain. EXAMPLES:: @@ -4469,7 +4501,7 @@ def maximal_antichains(self): sage: Posets.PentagonPoset().maximal_antichains() [[0], [1, 2], [1, 3], [4]] - .. seealso:: :meth:`maximal_chains`, :meth:`antichains` + .. seealso:: :meth:`antichains`, :meth:`maximal_chains` """ # Maximal antichains are maximum cliques on incomparability graph. return self.incomparability_graph().cliques_maximal() diff --git a/src/sage/combinat/rigged_configurations/kleber_tree.py b/src/sage/combinat/rigged_configurations/kleber_tree.py index 1a059859c79..18cecea1f23 100644 --- a/src/sage/combinat/rigged_configurations/kleber_tree.py +++ b/src/sage/combinat/rigged_configurations/kleber_tree.py @@ -24,30 +24,30 @@ sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree sage: KT = KleberTree(['A', 3, 1], [[3,2], [2,1], [1,1], [1,1]]) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 3] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 2, 2] and upwards edge root [1, 0, 0] - Kleber tree node with weight [2, 1, 2] and upwards edge root [0, 0, 0] - Kleber tree node with weight [2, 0, 0] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [1, 1, 0] - Kleber tree node with weight [0, 1, 0] and upwards edge root [0, 0, 1] - Kleber tree node with weight [3, 0, 1] and upwards edge root [0, 1, 1] - Kleber tree node with weight [0, 1, 0] and upwards edge root [1, 1, 1] - Kleber tree node with weight [1, 1, 1] and upwards edge root [1, 1, 1] - Kleber tree node with weight [0, 0, 2] and upwards edge root [2, 2, 1] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 2), (1, 1, 0)), + ((0, 0, 2), (2, 2, 1)), + ((0, 1, 0), (0, 0, 1)), + ((0, 1, 0), (1, 1, 1)), + ((0, 2, 2), (1, 0, 0)), + ((1, 0, 3), (1, 1, 0)), + ((1, 1, 1), (1, 1, 1)), + ((2, 0, 0), (0, 1, 1)), + ((2, 1, 2), (0, 0, 0)), + ((3, 0, 1), (0, 1, 1))] sage: KT = KleberTree(['A', 7, 1], [[3,2], [2,1], [1,1]]) sage: KT Kleber tree of Cartan type ['A', 7, 1] and B = ((3, 2), (2, 1), (1, 1)) - sage: for x in set(KT.list()): x - Kleber tree node with weight [1, 0, 1, 0, 1, 0, 0] and upwards edge root [1, 2, 2, 1, 0, 0, 0] - Kleber tree node with weight [0, 0, 1, 0, 0, 1, 0] and upwards edge root [2, 3, 3, 2, 1, 0, 0] - Kleber tree node with weight [1, 1, 2, 0, 0, 0, 0] and upwards edge root [0, 0, 0, 0, 0, 0, 0] - Kleber tree node with weight [2, 0, 1, 1, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [1, 0, 0, 2, 0, 0, 0] and upwards edge root [0, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 3, 0, 0, 0, 0] and upwards edge root [1, 1, 0, 0, 0, 0, 0] - Kleber tree node with weight [0, 0, 0, 1, 1, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] - Kleber tree node with weight [0, 1, 1, 1, 0, 0, 0] and upwards edge root [1, 1, 1, 0, 0, 0, 0] + sage: sorted((x.weight.to_vector(), x.up_root.to_vector()) for x in KT.list()) + [((0, 0, 0, 1, 1, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((0, 0, 1, 0, 0, 1, 0), (2, 3, 3, 2, 1, 0, 0)), + ((0, 0, 3, 0, 0, 0, 0), (1, 1, 0, 0, 0, 0, 0)), + ((0, 1, 1, 1, 0, 0, 0), (1, 1, 1, 0, 0, 0, 0)), + ((1, 0, 0, 2, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0)), + ((1, 0, 1, 0, 1, 0, 0), (1, 2, 2, 1, 0, 0, 0)), + ((1, 1, 2, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0)), + ((2, 0, 1, 1, 0, 0, 0), (0, 1, 1, 0, 0, 0, 0))] """ #***************************************************************************** @@ -347,6 +347,22 @@ def multiplicity(self): return mult + def __hash__(self): + r""" + TESTS:: + + sage: from sage.combinat.rigged_configurations.kleber_tree import KleberTree + sage: RS = RootSystem(['A', 2]) + sage: WS = RS.weight_space() + sage: R = RS.root_space() + sage: KT = KleberTree(['A', 2, 1], [[1,1]]) + sage: n = KT(WS.sum_of_terms([(1,5), (2,2)]), R.zero()) + sage: hash(n) + -603608031356818252 # 64-bit + -1956156236 # 32-bit + """ + return hash(self.depth) ^ hash(self.weight) + def __cmp__(self, rhs): r""" Check whether two nodes are equal. diff --git a/src/sage/combinat/rigged_configurations/kr_tableaux.py b/src/sage/combinat/rigged_configurations/kr_tableaux.py index 1637d297003..acf1b1c9c85 100644 --- a/src/sage/combinat/rigged_configurations/kr_tableaux.py +++ b/src/sage/combinat/rigged_configurations/kr_tableaux.py @@ -308,20 +308,16 @@ def __iter__(self): EXAMPLES:: - sage: KR = crystals.KirillovReshetikhin(['A', 3, 1], 2, 1, model='KR') - sage: g = KR.__iter__() - sage: next(g) - [[1], [2]] - sage: next(g) - [[1], [3]] - sage: next(g) - [[2], [3]] + sage: KR = crystals.KirillovReshetikhin(['A', 5, 2], 2, 1, model='KR') + sage: L = [x for x in KR] + sage: len(L) + 15 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() def module_generator(self, i=None, **options): r""" diff --git a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py index a41ef5774d4..bbfe85b94a7 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py +++ b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py @@ -1805,6 +1805,125 @@ def left_box(self, return_b=False): delta = left_box + def left_column_box(self): + r""" + Return the image of ``self`` under the left column box splitting + map `\gamma`. + + Consider the map `\gamma : RC(B^{r,1} \otimes B) \to RC(B^{1,1} + \otimes B^{r-1,1} \otimes B)` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a singular string of length `1` to `\nu^{(a)}`. + + We can extend `\gamma` when the left-most factor is not a single + column by precomposing with a :meth:`left_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[3,1], [2,1]]) + sage: mg = RC.module_generators[-1] + sage: ascii_art(mg) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 0[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.left_column_box()) + 1[ ]1 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + """ + P = self.parent() + r = P.dims[0][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[0][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[0][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[0][1] > 1: + return self.left_split().left_column_box() + + B = [[1,1], [r-1,1]] + B.extend(P.dims[1:]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in self] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, 1) + i = nu._list.index(1) + nu.vacancy_numbers.insert(i, vac_num) + nu.rigging.insert(i, vac_num) + return RC(*parts) + + def right_column_box(self): + r""" + Return the image of ``self`` under the right column box splitting + map `\gamma^*`. + + Consider the map `\gamma^* : RC(B \otimes B^{r,1}) \to RC(B \otimes + B^{r-1,1} \otimes B^{1,1})` for `r > 1`, which is a natural strict + classical crystal injection. On rigged configurations, the map + `\gamma` adds a string of length `1` with rigging 0 to `\nu^{(a)}` + for all `a < r` to a classically highest weight element and extended + as a classical crystal morphism. + + We can extend `\gamma^*` when the right-most factor is not a single + column by precomposing with a :meth:`right_split()`. + + EXAMPLES:: + + sage: RC = RiggedConfigurations(['C',3,1], [[2,1], [1,1], [3,1]]) + sage: mg = RC.module_generators[7] + sage: ascii_art(mg) + 1[ ]0 0[ ][ ]0 0[ ]0 + 0[ ]0 0[ ]0 + sage: ascii_art(mg.right_column_box()) + 1[ ]0 0[ ][ ]0 0[ ]0 + 1[ ]0 0[ ]0 0[ ]0 + 0[ ]0 + """ + P = self.parent() + r = P.dims[-1][0] + if r == 1: + raise ValueError("cannot split a single box") + ct = P.cartan_type() + if ct.type() == 'D': + if P.dims[-1][0] >= ct.rank() - 2: + raise ValueError("only for non-spinor cases") + elif ct.type() == 'B' or ct.dual().type() == 'B': + if P.dims[-1][0] == ct.rank() - 1: + raise ValueError("only for non-spinor cases") + + if P.dims[-1][1] > 1: + return self.right_split().right_column_box() + + rc, e_string = self.to_highest_weight(P.cartan_type().classical().index_set()) + + B = P.dims[:-1] + ([r-1,1], [1,1]) + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(P._cartan_type, B) + parts = [x._clone() for x in rc] # Make a deep copy + for nu in parts[:r-1]: + nu._list.append(1) + for a, nu in enumerate(parts[:r-1]): + vac_num = RC._calc_vacancy_number(parts, a, -1) + nu.vacancy_numbers.append(vac_num) + nu.rigging.append(0) + return RC(*parts).f_string(reversed(e_string)) + def complement_rigging(self, reverse_factors=False): r""" Apply the complement rigging morphism `\theta` to ``self``. diff --git a/src/sage/combinat/rigged_configurations/rigged_configurations.py b/src/sage/combinat/rigged_configurations/rigged_configurations.py index 86f5c38263d..abfba3bc94b 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configurations.py +++ b/src/sage/combinat/rigged_configurations/rigged_configurations.py @@ -480,29 +480,15 @@ def __iter__(self): EXAMPLES:: sage: RC = RiggedConfigurations(['A', 3, 1], [[2,1], [1,1]]) - sage: g = RC.__iter__() - sage: next(g) - - (/) - - (/) - - (/) - - sage: next(g) # random - - 0[ ]0 - - 0[ ]0 - - (/) - + sage: L = [x for x in RC] + sage: len(L) + 24 """ index_set = self._cartan_type.classical().index_set() from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet return RecursivelyEnumeratedSet(self.module_generators, lambda x: [x.f(i) for i in index_set], - structure=None).naive_search_iterator() + structure='graded').breadth_first_search_iterator() @lazy_attribute def module_generators(self): diff --git a/src/sage/combinat/root_system/integrable_representations.py b/src/sage/combinat/root_system/integrable_representations.py index ab8842f370f..14e3386db3d 100644 --- a/src/sage/combinat/root_system/integrable_representations.py +++ b/src/sage/combinat/root_system/integrable_representations.py @@ -5,6 +5,7 @@ #***************************************************************************** # Copyright (C) 2014, 2105 Daniel Bump # Travis Scrimshaw +# Valentin Buciumas # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ @@ -42,6 +43,9 @@ class IntegrableRepresentation(UniqueRepresentation, CategoryObject): .. [KacPeterson] Kac and Peterson. *Infinite-dimensional Lie algebras, theta functions and modular forms*. Adv. in Math. 53 (1984), no. 2, 125-264. + + .. [Carter] Carter, *Lie algebras of finite and affine type*. Cambridge + University Press, 2005 If `\Lambda` is a dominant integral weight for an affine root system, there exists a unique integrable representation `V=V_\Lambda` of highest @@ -147,6 +151,31 @@ class IntegrableRepresentation(UniqueRepresentation, CategoryObject): Lambda[0] + Lambda[2] - delta: 1 5 18 55 149 372 872 1941 4141 8523 17005 33019 2*Lambda[1] - delta: 1 4 15 44 122 304 721 1612 3469 7176 14414 28124 2*Lambda[2] - 2*delta: 2 7 26 72 194 467 1084 2367 5010 10191 20198 38907 + + Examples for twisted affine types:: + + sage: Lambda = RootSystem(["A",2,2]).weight_lattice(extended=True).fundamental_weights() + sage: IntegrableRepresentation(Lambda[0]).strings() + {Lambda[0]: [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56]} + sage: Lambda = RootSystem(['G',2,1]).dual.weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[2]) + sage: V.print_strings() # long time + 6*Lambdacheck[0]: 4 28 100 320 944 2460 6064 14300 31968 69020 144676 293916 + 4*Lambdacheck[0] + Lambdacheck[2]: 4 22 84 276 800 2124 5288 12470 28116 61056 128304 261972 + 3*Lambdacheck[0] + Lambdacheck[1]: 2 16 58 192 588 1568 3952 9520 21644 47456 100906 207536 + Lambdacheck[0] + Lambdacheck[1] + Lambdacheck[2]: 1 6 26 94 294 832 2184 5388 12634 28390 61488 128976 + 2*Lambdacheck[1] - deltacheck: 2 8 32 120 354 980 2576 6244 14498 32480 69776 145528 + 2*Lambdacheck[0] + 2*Lambdacheck[2]: 2 12 48 164 492 1344 3428 8256 18960 41844 89208 184512 + 3*Lambdacheck[2] - deltacheck: 4 16 60 208 592 1584 4032 9552 21728 47776 101068 207888 + sage: Lambda = RootSystem(['A',6,2]).weight_lattice(extended=true).fundamental_weights() + sage: V = IntegrableRepresentation(Lambda[0]+2*Lambda[1]) + sage: V.print_strings() # long time + 5*Lambda[0]: 3 42 378 2508 13707 64650 272211 1045470 3721815 12425064 39254163 118191378 + 3*Lambda[0] + Lambda[2]: 1 23 234 1690 9689 47313 204247 800029 2893198 9786257 31262198 95035357 + Lambda[0] + 2*Lambda[1]: 1 14 154 1160 6920 34756 153523 612354 2248318 7702198 24875351 76341630 + Lambda[0] + Lambda[1] + Lambda[3] - 2*delta: 6 87 751 4779 25060 113971 464842 1736620 6034717 19723537 61152367 181068152 + Lambda[0] + 2*Lambda[2] - 2*delta: 3 54 499 3349 18166 84836 353092 1341250 4725259 15625727 48938396 146190544 + Lambda[0] + 2*Lambda[3] - 4*delta: 15 195 1539 9186 45804 200073 789201 2866560 9723582 31120281 94724550 275919741 """ def __init__(self, Lam): """ @@ -160,16 +189,16 @@ def __init__(self, Lam): """ CategoryObject.__init__(self, base=ZZ, category=Modules(ZZ)) - if not Lam.parent().cartan_type().is_affine() or not Lam.parent()._extended: - raise ValueError("the parent of %s must be an extended affine root lattice"%Lam) self._Lam = Lam self._P = Lam.parent() self._Q = self._P.root_system.root_lattice() + # Store some extra simple computations that appear in tight loops + self._Lam_rho = self._Lam + self._P.rho() + self._cartan_matrix = self._P.root_system.cartan_matrix() self._cartan_type = self._P.root_system.cartan_type() - if not self._cartan_type.is_untwisted_affine(): - raise NotImplementedError("integrable representations are only implemented for untwisted affine types") + self._classical_rank = self._cartan_type.classical().rank() self._index_set = self._P.index_set() self._index_set_classical = self._cartan_type.classical().index_set() @@ -186,11 +215,14 @@ def __init__(self, Lam): self._a = self._cartan_type.a() # This is not cached self._ac = self._cartan_type.dual().a() # This is not cached self._eps = {i: self._a[i] / self._ac[i] for i in self._index_set} - self._coxeter_number = sum(self._a) - self._dual_coxeter_number = sum(self._ac) E = Matrix.diagonal([self._eps[i] for i in self._index_set_classical]) self._ip = (self._cartan_type.classical().cartan_matrix()*E).inverse() + # Extra data for the twisted cases + if not self._cartan_type.is_untwisted_affine(): + self._classical_short_roots = frozenset(al for al in self._classical_roots + if self._inner_qq(al,al) == 2) + def highest_weight(self): """ Returns the highest weight of ``self``. @@ -243,6 +275,7 @@ def level(self): """ return ZZ(self._inner_pq(self._Lam, self._Q.null_root())) + @cached_method def coxeter_number(self): """ Return the Coxeter number of the Cartan type of ``self``. @@ -257,8 +290,9 @@ def coxeter_number(self): sage: V.coxeter_number() 12 """ - return self._coxeter_number + return sum(self._a) + @cached_method def dual_coxeter_number(self): r""" Return the dual Coxeter number of the Cartan type of ``self``. @@ -273,7 +307,7 @@ def dual_coxeter_number(self): sage: V.dual_coxeter_number() 9 """ - return self._dual_coxeter_number + return sum(self._ac) def _repr_(self): """ @@ -570,8 +604,8 @@ def to_dominant(self, n): def _freudenthal_roots_imaginary(self, nu): r""" - Return the set of imaginary roots `\alpha \in \Delta^+` in ``self`` - such that `\nu - \alpha \in Q^+`. + Iterate over the set of imaginary roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. INPUT: @@ -581,20 +615,24 @@ def _freudenthal_roots_imaginary(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) - sage: [V._freudenthal_roots_imaginary(V.highest_weight() - mw) + sage: [list(V._freudenthal_roots_imaginary(V.highest_weight() - mw)) ....: for mw in V.dominant_maximal_weights()] [[], [], [], [], []] """ l = self._from_weight_helper(nu) kp = min(l[i] // self._a[i] for i in self._index_set) delta = self._Q.null_root() - return [u * delta for u in range(1, kp+1)] + for u in range(1, kp+1): + yield u * delta def _freudenthal_roots_real(self, nu): r""" - Return the set of real positive roots `\alpha \in \Delta^+` in - ``self`` such that `\nu - \alpha \in Q^+`. - + Iterate over the set of real positive roots `\alpha \in \Delta^+` + in ``self`` such that `\nu - \alpha \in Q^+`. + + See [Kac]_ Proposition 6.3 for the way to compute the set of real + roots for twisted affine case. + INPUT: - ``nu`` -- an element in `Q` @@ -604,7 +642,7 @@ def _freudenthal_roots_real(self, nu): sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() sage: V = IntegrableRepresentation(Lambda[0]+Lambda[1]+Lambda[3]) sage: mw = V.dominant_maximal_weights()[0] - sage: V._freudenthal_roots_real(V.highest_weight() - mw) + sage: list(V._freudenthal_roots_real(V.highest_weight() - mw)) [alpha[1], alpha[2], alpha[3], @@ -612,14 +650,66 @@ def _freudenthal_roots_real(self, nu): alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[3]] """ - ret = [] for al in self._classical_positive_roots: - if all(x >= 0 for x in self._from_weight_helper(nu-al)): - ret.append(al) - for al in self._classical_roots: - for ir in self._freudenthal_roots_imaginary(nu-al): - ret.append(al+ir) - return ret + if min(self._from_weight_helper(nu-al)) >= 0: + yield al + + if self._cartan_type.is_untwisted_affine(): + # untwisted case + for al in self._classical_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + + elif self._cartan_type.type() == 'BC': + #case A^2_{2l} + # We have to keep track of the roots we have visted for this case + ret = set(self._classical_positive_roots) + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + ret.add(al + ir) + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + ret.add(al + 2*ir) + yield al + 2*ir + alpha = self._Q.simple_roots() + fri = list(self._freudenthal_roots_imaginary(2*nu-al)) + for ir in fri[::2]: + rt = sum( val // 2 * alpha[i] for i,val in + enumerate(self._from_weight_helper(al+ir)) ) + if rt not in ret: + ret.add(rt) + yield rt + + elif self._cartan_type.dual().type() == 'G': + # case D^3_4 in the Kac notation + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 3*ir in friset: + yield al + 3*ir + + elif self._cartan_type.dual().type() in ['B','C','F']: + #case A^2_{2l-1} or case D^2_{l+1} or case E^2_6: + for al in self._classical_roots: + if al in self._classical_short_roots: + for ir in self._freudenthal_roots_imaginary(nu-al): + yield al + ir + else: + fri = list(self._freudenthal_roots_imaginary(nu-al)) + friset = set(fri) + for ir in fri: + if 2*ir in friset: + yield al + 2*ir def _freudenthal_accum(self, nu, al): """ @@ -640,21 +730,25 @@ def _freudenthal_accum(self, nu, al): n_shift = self._from_weight_helper(al) ip_shift = self._inner_qq(al, al) - while all(val >= 0 for val in n): + while min(n) >= 0: # Change in data by adding ``al`` to our current weight ip += ip_shift for i,val in enumerate(n_shift): n[i] -= val # Compute the multiplicity - mk = self.m(tuple(n)) - ret += 2*mk*ip + ret += 2 * self.m(tuple(n)) * ip return ret def _m_freudenthal(self, n): - """ + r""" Compute the weight multiplicity using the Freudenthal multiplicity formula in ``self``. + The multiplicities of the imaginary roots for the twisted + affine case are different than those for the untwisted case. + See [Carter]_ Corollary 18.10 for general type and Corollary + 18.15 for `A^2_{2l}` + EXAMPLES:: sage: Lambda = RootSystem(['B',3,1]).weight_lattice(extended=true).fundamental_weights() @@ -670,12 +764,48 @@ def _m_freudenthal(self, n): I = self._index_set al = self._Q._from_dict({I[i]: val for i,val in enumerate(n) if val}, remove_zeros=False) - den = 2*self._inner_pq(self._Lam+self._P.rho(), al) - self._inner_qq(al, al) - num = 0 - for al in self._freudenthal_roots_real(self._Lam - mu): - num += self._freudenthal_accum(mu, al) - for al in self._freudenthal_roots_imaginary(self._Lam - mu): - num += self._classical_rank * self._freudenthal_accum(mu, al) + cr = self._classical_rank + num = sum(self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_real(self._Lam - mu)) + + if self._cartan_type.is_untwisted_affine(): + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual().type() == 'B': # A_{2n-1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.type() == 'BC': # A_{2n}^{(2)} + num += sum(cr * self._freudenthal_accum(mu, alr) + for alr in self._freudenthal_roots_imaginary(self._Lam - mu)) + + elif self._cartan_type.dual() == 'C': # D_{n+1}^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (cr - (cr - 1)*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'F': # E_6^{(2)} + val = 1 + for rt in self._freudenthal_roots_imaginary(self._Lam - mu): + # k-th element (starting from 1) is k*delta + num += (4 - 2*val) * self._freudenthal_accum(mu, rt) + val = 1 - val + + elif self._cartan_type.dual().type() == 'G': # D_4^{(3)} (or dual of G_2^{(1)}) + for k,rt in enumerate(self._freudenthal_roots_imaginary(self._Lam - mu)): + # k-th element (starting from 1) is k*delta + if (k+1) % 3 == 0: + num += 2 * self._freudenthal_accum(mu, rt) + else: + num += self._freudenthal_accum(mu, rt) + + den = 2*self._inner_pq(self._Lam_rho, al) - self._inner_qq(al, al) try: return ZZ(num / den) except TypeError: @@ -872,9 +1002,9 @@ def modular_characteristic(self, mu=None): else: n = self.from_weight(mu) k = self.level() - hd = self._dual_coxeter_number + hd = self.dual_coxeter_number() rho = self._P.rho() - m_Lambda = self._inner_pp(self._Lam+rho, self._Lam+rho) / (2*(k+hd)) \ + m_Lambda = self._inner_pp(self._Lam_rho, self._Lam_rho) / (2*(k+hd)) \ - self._inner_pp(rho, rho) / (2*hd) if n is None: return m_Lambda diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index 9cbfc8b1774..36f5bc22194 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -181,7 +181,7 @@ class type, it is also possible to compute the number of classes of that type from sage.functions.all import factorial from sage.rings.arith import moebius from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass -from sage.structure.element import Element +from sage.structure.element import Element, parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -400,6 +400,25 @@ def __repr__(self): """ return "%s"%([self._deg, self._par]) + def __hash__(self): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT3 = PrimarySimilarityClassType(2, [4, 2, 1]) + sage: hash(PT1) + 5050909583595644741 # 64-bit + 1658169157 # 32-bit + sage: hash(PT2) + 5050909583595644740 # 64-bit + 1658169156 # 32-bit + sage: hash(PT3) + 6312110366011971308 # 64-bit + 1429493484 # 32-bit + """ + return hash(self._deg) ^ hash(tuple(self._par)) + def __eq__(self, other): """ Check equality. @@ -420,9 +439,25 @@ def __eq__(self, other): sage: PT1 == PT5 False """ - if isinstance(other, PrimarySimilarityClassType): - return self.degree() == other.degree() and self.partition() == other.partition() - return False + return isinstance(other, PrimarySimilarityClassType) and \ + self.degree() == other.degree() and \ + self.partition() == other.partition() + + def __ne__(self, other): + r""" + TESTS:: + + sage: PT1 = PrimarySimilarityClassType(2, [3, 2, 1]) + sage: PT2 = PrimarySimilarityClassType(2, Partition([3, 2, 1])) + sage: PT1 != PT2 + False + sage: PT3 = PrimarySimilarityClassType(3, [3, 2, 1]) + sage: PT1 != PT3 + True + """ + return not isinstance(other, PrimarySimilarityClassType) or \ + self.degree() != other.degree() or \ + self.partition() != other.partition() def size(self): """ diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 84ac40d262d..cb1b6a308bc 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -40,7 +40,8 @@ from sage.structure.list_clone import ClonableList from sage.combinat.partition import Partition -from sage.combinat.tableau import Tableau, TableauOptions +from sage.combinat.tableau import (Tableau, TableauOptions, + StandardTableau, SemistandardTableau) from sage.combinat.skew_partition import SkewPartition, SkewPartitions from sage.combinat.integer_vector import IntegerVectors from sage.combinat.words.words import Words @@ -407,9 +408,9 @@ def conjugate(self): def to_word_by_row(self): """ Return a word obtained from a row reading of ``self``. - Specifically, this is the word obtained by concatenating the - rows from the bottommost one (in English notation) to the - topmost one. + + This is the word obtained by concatenating the rows from + the bottommost one (in English notation) to the topmost one. EXAMPLES:: @@ -434,19 +435,16 @@ def to_word_by_row(self): sage: SkewTableau([]).to_word_by_row() word: """ - word = [] - for row in self: - word = list(row) + word - - return Words("positive integers")([i for i in word if i is not None]) + word = [x for row in reversed(self) for x in row if x is not None] + return Words("positive integers")(word) def to_word_by_column(self): """ Return the word obtained from a column reading of the skew tableau. - Specifically, this is the word obtained by concatenating the - columns from the rightmost one (in English notation) to the - leftmost one. + + This is the word obtained by concatenating the columns from + the rightmost one (in English notation) to the leftmost one. EXAMPLES:: @@ -778,11 +776,11 @@ def to_chain(self, max_entry=None): def slide(self, corner=None): """ - Apply a jeu-de-taquin slide to ``self`` on the specified corner and - returns the new tableau. If no corner is given an arbitrary corner - is chosen. + Apply a jeu-de-taquin slide to ``self`` on the specified inner corner and + return the resulting tableau. If no corner is given, an arbitrary inner + corner is chosen. - See [FW]_ p12-13. + See [Fulton97]_ p12-13. EXAMPLES:: @@ -854,38 +852,74 @@ def slide(self, corner=None): return SkewTableau(new_st) - def rectify(self): + def rectify(self, algorithm=None): """ - Return a :class:`Tableau` formed by applying the jeu de taquin - process to ``self``. See page 15 of [FW]_. + Return a :class:`StandardTableau`, :class:`SemistandardTableau`, + or just :class:`Tableau` formed by applying the jeu de taquin + process to ``self``. - REFERENCES: + See page 15 of [Fulton97]_. - .. [FW] William Fulton, - *Young Tableaux*, - Cambridge University Press 1997. + INPUT: + + - ``algorithm`` -- optional: if set to ``'jdt'``, rectifies by jeu de + taquin; if set to ``'schensted'``, rectifies by Schensted insertion + of the reading word; otherwise, guesses which will be faster. EXAMPLES:: - sage: s = SkewTableau([[None,1],[2,3]]) - sage: s.rectify() + sage: S = SkewTableau([[None,1],[2,3]]) + sage: S.rectify() [[1, 3], [2]] - sage: SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]).rectify() + sage: T = SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]) + sage: T.rectify() + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='jdt') [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='schensted') + [[1, 3, 4, 6], [2, 5]] + sage: T.rectify(algorithm='spaghetti') + Traceback (most recent call last): + ... + ValueError: algorithm must be 'jdt', 'schensted', or None TESTS:: - sage: s + sage: S [[None, 1], [2, 3]] - """ - rect = copy.deepcopy(self) - inner_corners = rect.inner_shape().corners() + sage: T + [[None, None, None, 4], [None, None, 1, 6], [None, None, 5], [2, 3]] + + REFERENCES: - while len(inner_corners) > 0: - rect = rect.slide() - inner_corners = rect.inner_shape().corners() + .. [Fulton97] William Fulton, *Young Tableaux*, + Cambridge University Press 1997. + """ + mu_size = self.inner_shape().size() - return rect.to_tableau() + # Roughly, use jdt with a small inner shape, Schensted with a large one + if algorithm is None: + la = self.outer_shape() + la_size = la.size() + if mu_size ** 2 < len(la) * (la_size - mu_size): + algorithm = 'jdt' + else: + algorithm = 'schensted' + + if algorithm == 'jdt': + rect = self + for i in range(mu_size): + rect = rect.slide() + elif algorithm == 'schensted': + w = [x for row in reversed(self) for x in row if x is not None] + rect = Tableau([]).insert_word(w) + else: + raise ValueError("algorithm must be 'jdt', 'schensted', or None") + if self in StandardSkewTableaux(): + return StandardTableau(rect[:]) + if self in SemistandardSkewTableaux(): + return SemistandardTableau(rect[:]) + return Tableau(rect) def standardization(self, check=True): r""" @@ -1773,9 +1807,10 @@ def cardinality(self): def __iter__(self): """ - An iterator for all the standard skew tableaux with shape of the - skew partition ``skp``. The standard skew tableaux are ordered - lexicographically by the word obtained from their row reading. + An iterator for all the standard skew tableaux whose shape is + the skew partition ``skp``. The standard skew tableaux are + ordered lexicographically by the word obtained from their row + reading. EXAMPLES:: diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 24227877be1..2dfe0da2d39 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -2845,7 +2845,7 @@ def row_stabilizer(self): k = self.size() gens = [range(1, k+1)] for row in self: - for j in range(0, len(row)-1): + for j in range(len(row)-1): gens.append( (row[j], row[j+1]) ) return PermutationGroup( gens ) diff --git a/src/sage/combinat/words/word_char.pyx b/src/sage/combinat/words/word_char.pyx index bef4c943c05..0c55949c019 100644 --- a/src/sage/combinat/words/word_char.pyx +++ b/src/sage/combinat/words/word_char.pyx @@ -25,6 +25,8 @@ from cpython.number cimport PyIndex_Check, PyNumber_Check from cpython.sequence cimport PySequence_Check from cpython.slice cimport PySlice_Check, PySlice_GetIndicesEx +import itertools + # the maximum value of a size_t cdef size_t SIZE_T_MAX = -( 1) @@ -550,7 +552,6 @@ cdef class WordDatatype_char(WordDatatype): return w._new_c(data, new_length, None) - @cython.boundscheck(False) def has_prefix(self, other): r""" Test whether ``other`` is a prefix of ``self``. @@ -573,6 +574,22 @@ cdef class WordDatatype_char(WordDatatype): True sage: w.has_prefix(w[1:]) False + + TESTS: + + :trac:`19322`:: + + sage: W = Words([0,1,2]) + sage: w = W([0,1,0,2]) + sage: w.has_prefix(words.FibonacciWord()) + False + + sage: w.has_prefix([0,1,0,2,0]) + False + sage: w.has_prefix([0,1,0,2]) + True + sage: w.has_prefix([0,1,0]) + True """ cdef size_t i cdef WordDatatype_char w @@ -582,15 +599,16 @@ cdef class WordDatatype_char(WordDatatype): w = other if w._length > self._length: return False - return memcmp(self._data, w._data, w._length) == 0 + return memcmp(self._data, w._data, w._length * sizeof(unsigned char)) == 0 elif PySequence_Check(other): # python level - if len(other) > self._length: + from sage.combinat.words.infinite_word import InfiniteWord_class + if isinstance(other, InfiniteWord_class) or len(other) > len(self): return False for i in range(len(other)): - if other[i] != self._data[i]: + if other[i] != self[i]: return False return True @@ -638,3 +656,151 @@ cdef class WordDatatype_char(WordDatatype): return memcmp(self._data, self._data + l, l * sizeof(unsigned char)) == 0 + + def longest_common_prefix(self, other): + r""" + Return the longest common prefix of this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_prefix([0,1]) + word: 01 + sage: u = W([0,1,0,0,1]) + sage: v = W([0,1,0,2]) + sage: u.longest_common_prefix(v) + word: 010 + sage: v.longest_common_prefix(u) + word: 010 + + Using infinite words is also possible (and the return type is also a + of the same type as ``self``):: + + sage: W([0,1,0,0]).longest_common_prefix(words.FibonacciWord()) + word: 0100 + sage: type(_) + + + An example of an intensive usage:: + + sage: W = Words([0,1]) + sage: w = words.FibonacciWord() + sage: w = W(list(w[:5000])) + sage: L = [[len(w[n:].longest_common_prefix(w[n+fibonacci(i):])) + ....: for i in range(5,15)] for n in range(1,1000)] + sage: for n,l in enumerate(L): + ....: if l.count(0) > 4: print n+1,l + 375 [0, 13, 0, 34, 0, 89, 0, 233, 0, 233] + 376 [0, 12, 0, 33, 0, 88, 0, 232, 0, 232] + 608 [8, 0, 21, 0, 55, 0, 144, 0, 377, 0] + 609 [7, 0, 20, 0, 54, 0, 143, 0, 376, 0] + 985 [0, 13, 0, 34, 0, 89, 0, 233, 0, 610] + 986 [0, 12, 0, 33, 0, 88, 0, 232, 0, 609] + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_prefix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we allow to compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[i] != w._data[i]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data, i, self) + + elif PySequence_Check(other): + # Python level + # we avoid to call len(other) since it might be an infinite word + for i,a in enumerate(itertools.islice(other, self._length)): + if self._data[i] != a: + break + else: + i += 1 + + return self._new_c(self._data, i, self) + + raise TypeError("unsupported input {}".format(other)) + + def longest_common_suffix(self, other): + r""" + Return the longest common suffix between this word and ``other``. + + EXAMPLES:: + + sage: W = Words([0,1,2]) + sage: W([0,1,0,2]).longest_common_suffix([2,0,2]) + word: 02 + sage: u = W([0,1,0,0,1]) + sage: v = W([1,2,0,0,1]) + sage: u.longest_common_suffix(v) + word: 001 + sage: v.longest_common_suffix(u) + word: 001 + + TESTS:: + + sage: W = Words([0,1,2]) + sage: w = W([0,2,1,0,0,1]) + sage: w.longest_common_suffix(0) + Traceback (most recent call last): + ... + TypeError: unsupported input 0 + """ + cdef WordDatatype_char w + cdef size_t i + cdef size_t m + cdef size_t lo + + if isinstance(other, WordDatatype_char): + # C level + # (this can be much faster if we could compare larger memory + # zones) + w = other + m = min(self._length, w._length) + for i in range(m): + if self._data[self._length-i-1] != w._data[w._length-i-1]: + break + else: + if self._length <= w._length: + return self + else: + return other + + return self._new_c(self._data+self._length-i, i, self) + + elif PySequence_Check(other): + # Python level + lo = len(other) + m = min(self._length, lo) + for i in range(m): + if self._data[self._length-i-1] != other[lo-i-1]: + break + else: + if self._length == m: + return self + else: + i += 1 + + return self._new_c(self._data+self._length-i, i, self) + + raise TypeError("unsupported input {}".format(other)) + diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py index e3ce1f1a0ec..2f30ccb4535 100644 --- a/src/sage/data_structures/mutable_poset.py +++ b/src/sage/data_structures/mutable_poset.py @@ -10,12 +10,6 @@ :mod:`Posets ` in the reference manual. -.. _mutable_poset_intro: - -Introduction -============ - - .. _mutable_poset_examples: Examples @@ -49,7 +43,13 @@ We see that they elements are sorted using `\leq` which exists on the integers `\ZZ`. Since this is even a total order, we could have used a -more efficient data structure. +more efficient data structure. Alternativly, we can write +:: + + sage: MP([42, 7, 13, 3]) + poset(3, 7, 13, 42) + +to add several elements at once on construction. A less boring Example @@ -63,8 +63,9 @@ ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) -It is equipped with a `\leq`-operation which makes `a \leq b` if all -entries of `a` are at most `b`. For example, we have +It is equipped with a `\leq`-operation such that `a \leq b` if all +entries of `a` are at most the corresponding entry of `b`. For +example, we have :: @@ -75,22 +76,16 @@ (True, True, False) The last comparison gives ``False``, since the comparison of the -first component yield `2 \leq 1`. +first component checks whether `2 \leq 1`. Now, let us add such elements to a poset:: - sage: Q = MP() - sage: Q.add(T((1, 1))) - sage: Q.add(T((3, 3))) - sage: Q.add(T((4, 1))) - sage: Q.add(T((3, 2))) - sage: Q.add(T((2, 3))) - sage: Q.add(T((2, 2))) - sage: Q + sage: Q = MP([T((1, 1)), T((3, 3)), T((4, 1)), + ....: T((3, 2)), T((2, 3)), T((2, 2))]); Q poset((1, 1), (2, 2), (2, 3), (3, 2), (3, 3), (4, 1)) In the representation above, the elements are sorted topologically, -smallest first. This does not show (directly) more structural +smallest first. This does not (directly) show more structural information. We can overcome this and display a "wiring layout" by typing:: @@ -172,6 +167,17 @@ class MutablePosetShell(SageObject): A shell for the given element. + .. NOTE:: + + If the :meth:`element` of a shell is ``None``, then this + element is considered as "special" (see :meth:`is_special`). + There are two special elements, namely + + - a ``'null'`` (an element smaller than each other element; + it has no predecessors) and + - an ``'oo'`` (an element larger than each other element; + it has no successors). + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -182,6 +188,10 @@ class MutablePosetShell(SageObject): sage: s = P.shell(66) sage: type(s) + + .. SEEALSO:: + + :class:`MutablePoset` """ def __init__(self, poset, element): r""" @@ -197,8 +207,10 @@ def __init__(self, poset, element): """ self._poset_ = poset self._element_ = element + self._key_ = self.poset.get_key(element) self._predecessors_ = set() self._successors_ = set() + super(MutablePosetShell, self).__init__() @property @@ -206,6 +218,10 @@ def poset(self): r""" The poset to which this shell belongs. + .. SEEALSO:: + + :class:`MutablePoset` + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -223,6 +239,11 @@ def element(self): r""" The element contained in this shell. + .. SEEALSO:: + + :meth:`key`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -240,7 +261,14 @@ def key(self): r""" The key of the element contained in this shell. - The element is converted by the poset to the key. + The key of an element is determined by the mutable poset (the + parent) via the ``key``-function (see construction of a + :class:`MutablePoset`). + + .. SEEALSO:: + + :meth:`element`, + :class:`MutablePoset`. TESTS:: @@ -254,8 +282,20 @@ def key(self): sage: f = MutablePosetShell(Q, (1, 2)) sage: f.key 1 + + Test the caching of the key:: + + sage: def k(k): + ....: print 'key %s' % (k,) + ....: return k + sage: R = MP(key=k) + sage: h = MutablePosetShell(R, (1, 2)) + key (1, 2) + sage: h.key; h.key + (1, 2) + (1, 2) """ - return self.poset.get_key(self._element_) + return self._key_ def predecessors(self, reverse=False): @@ -264,13 +304,18 @@ def predecessors(self, reverse=False): INPUT: - - ``reverse`` -- (default: ``False``) if set, then returns + - ``reverse`` -- (default: ``False``) if set, then return successors instead. OUTPUT: A set. + .. SEEALSO:: + + :meth:`successors`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -291,13 +336,18 @@ def successors(self, reverse=False): INPUT: - - ``reverse`` -- (default: ``False``) if set, then returns + - ``reverse`` -- (default: ``False``) if set, then return predecessors instead. OUTPUT: A set. + .. SEEALSO:: + + :meth:`predecessors`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -314,7 +364,7 @@ def successors(self, reverse=False): def is_special(self): r""" - Return if this shell contains either the null-element, i.e., the + Return whether this shell contains either the null-element, i.e., the element smaller than any possible other element or the infinity-element, i.e., the element larger than any possible other element. @@ -327,6 +377,12 @@ def is_special(self): ``True`` or ``False``. + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_oo`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -341,13 +397,20 @@ def is_special(self): def is_null(self): r""" - Return if this shell contains the null-element, i.e., the element + Return whether this shell contains the null-element, i.e., the element smaller than any possible other element. OUTPUT: ``True`` or ``False``. + .. SEEALSO:: + + :meth:`is_special`, + :meth:`is_oo`, + :meth:`MutablePoset.null`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -362,13 +425,20 @@ def is_null(self): def is_oo(self): r""" - Return if this shell contains the infinity-element, i.e., the element + Return whether this shell contains the infinity-element, i.e., the element larger than any possible other element. OUTPUT: ``True`` or ``False``. + .. SEEALSO:: + + :meth:`is_null`, + :meth:`is_special`, + :meth:`MutablePoset.oo`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -381,7 +451,7 @@ def is_oo(self): return self.element is None and not self.successors() - def __repr__(self): + def _repr_(self): r""" Return the representation of this shell. @@ -413,12 +483,12 @@ def __repr__(self): sage: repr(P.oo) # indirect doctest 'oo' """ - if self.element is None: - if not self.predecessors(): - return 'null' - if not self.successors(): - return 'oo' - return repr(self.element) + if self.is_null(): + return 'null' + elif self.is_oo(): + return 'oo' + else: + return repr(self.element) def __hash__(self): @@ -456,7 +526,7 @@ def le(self, other, reverse=False): - ``other`` -- a shell. - ``reverse`` -- (default: ``False``) if set, then return - ``right <= left`` instead. + whether this shell is greater than or equal to ``other``. OUTPUT: @@ -466,10 +536,12 @@ def le(self, other, reverse=False): The comparison of the shells is based on the comparison of the keys of the elements contained in the shells, - except for the shells containing ``None``. These special - shells are interpreted as smaller or larger than all - other elements, depending on whether they have no - predecessors or no successors, respectively. + except for special shells (see :class:`MutablePosetShell`). + + .. SEEALSO:: + + :meth:`eq`, + :class:`MutablePoset`. TESTS:: @@ -523,29 +595,21 @@ def le(self, other, reverse=False): return other.le(self, reverse=False) if self.element is None: - if not self.predecessors(): + if not self._predecessors_: # null on the left return True else: # oo on the left if other.element is None: # null or oo on the right - return not other.successors() - else: - # not null, not oo on the right - return False - if other.element is None: - if not other.successors(): - # oo on the right - return True - else: - # null on the right - if self.element is None: - # null or oo on the left - return not self.predecessors() + return not other._successors_ else: # not null, not oo on the right return False + elif other.element is None: + # null/oo on the right + return not other._successors_ + return self.key <= other.key @@ -558,7 +622,7 @@ def eq(self, other): INPUT: - - ``right`` -- a shell. + - ``other`` -- a shell. OUTPUT: @@ -567,9 +631,13 @@ def eq(self, other): .. NOTE:: This method compares the keys of the elements contained - in the shells, if the elements are not both ``None``. - Otherwise, this method checks if both shells describe the - same special element. + in the (non-special) shells. In particular, + elements/shells with the same key are considered as equal. + + .. SEEALSO:: + + :meth:`le`, + :class:`MutablePoset`. TESTS:: @@ -577,6 +645,7 @@ def eq(self, other): sage: P = MP() sage: from sage.data_structures.mutable_poset import MutablePosetShell sage: e = MutablePosetShell(P, (1, 2)) + sage: f = MutablePosetShell(P, (2, 1)) sage: z = P.null sage: oo = P.oo sage: z == z @@ -585,12 +654,24 @@ def eq(self, other): True sage: e == e True + sage: e == f + False sage: z == e False sage: e == oo False sage: oo == z False + + Comparing elements in different mutable posets is possible; their + shells are equal if their elements are:: + + sage: S = MP([42]); s = S.shell(42) + sage: T = MP([42]); t = T.shell(42) + sage: s == t + True + sage: S.oo == T.oo + True """ if self.element is None and other.element is None: return self.is_null() == other.is_null() @@ -602,8 +683,8 @@ def eq(self, other): def _copy_all_linked_(self, memo, poset, mapping): r""" - Return a copy of all shells linked to this shell - (including a copy of this shell). + Return a copy of this shell. All shells linked to this shell + are copied as well. This is a helper function for :meth:`MutablePoset.copy`. @@ -612,7 +693,10 @@ def _copy_all_linked_(self, memo, poset, mapping): - ``memo`` -- a dictionary which assigns to the id of the calling shell to a copy of it. - - ``poset`` -- the poset to which the newly created shells belongs. + - ``poset`` -- the poset to which the newly created shells + belongs. Note that the elements are not inserted into + ``poset``; this is done in the calling method + :meth:`MutablePoset._copy_shells_`. - ``mapping`` -- a function which is applied on each of the elements. @@ -620,6 +704,11 @@ def _copy_all_linked_(self, memo, poset, mapping): A new shell. + .. SEEALSO:: + + :meth:`MutablePoset.copy`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -630,8 +719,16 @@ def _copy_all_linked_(self, memo, poset, mapping): sage: z.poset is Q True sage: oo = z.successors().pop() - sage: oo == P.oo + sage: oo.is_oo() True + + Note that :meth:`_copy_all_linked_` does not change the mutable + poset ``Q`` (this is done in the calling method + :meth:`MutablePoset._copy_shells_`). Thus we have + :: + + sage: oo is Q.oo + False """ try: return memo[id(self)] @@ -649,82 +746,120 @@ def _copy_all_linked_(self, memo, poset, mapping): return new - def _search_covers_(self, covers, shell, reverse=False): + def lower_covers(self, shell, reverse=False): r""" - Search for cover shells of this shell. + Return the lower covers of the specified ``shell``; + the search is started at this (``self``) shell. - This is a helper function for :meth:`covers`. + A lower cover of `x` is an element `y` of the poset + such that `y < x` and there is no element `z` of the poset + so that `y < z < x`. INPUT: - - ``covers`` -- a set which finally contains all covers. - - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. - - ``reverse`` -- (default: ``False``) if not set, then find - the lower covers, otherwise find the upper covers. + - ``reverse`` -- (default: ``False``) if set, then find + the upper covers (see also :meth:`upper_covers`) + instead of the lower covers. OUTPUT: - ``True`` or ``False``. + A set of :class:`shells `. - Note that ``False`` is returned if we do not have - ``self <= shell``. + .. NOTE:: + + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'oo'``. + Thus, only shells which are (not necessarily + direct) successors of this shell are considered. - TESTS:: + If ``reverse`` is ``True``, then the reverse direction is + taken. + + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1, 1))) - sage: P.add(T((1, 3, 1))) - sage: P.add(T((2, 1, 2))) - sage: P.add(T((4, 4, 2))) - sage: P.add(T((1, 2, 2))) - sage: P.add(T((2, 2, 2))) - sage: e = P.shell(T((2, 2, 2))); e - (2, 2, 2) - sage: covers = set() - sage: P.null._search_covers_(covers, e) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) + sage: e = P.shell(T((2, 2))); e + (2, 2) + sage: sorted(P.null.lower_covers(e), + ....: key=lambda c: repr(c.element)) + [(1, 2), (2, 1)] + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() True - sage: sorted(covers, key=lambda c: repr(c.element)) - [(1, 2, 2), (2, 1, 2)] + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + + .. SEEALSO:: + + :meth:`upper_covers`, + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. """ - if not self.le(shell, reverse) or self == shell: - return False - if not any([e._search_covers_(covers, shell, reverse) - for e in self.successors(reverse)]): - covers.add(self) - return True + if self == shell: + return set() + covers = set().union(*(e.lower_covers(shell, reverse) + for e in self.successors(reverse) + if e.le(shell, reverse))) + return covers or set([self]) - def covers(self, shell, reverse=False): + def upper_covers(self, shell, reverse=False): r""" - Return the covers of the given shell (considering only - shells which originate from this shell). + Return the upper covers of the specified ``shell``; + the search is started at this (``self``) shell. + + An upper cover of `x` is an element `y` of the poset + such that `x < y` and there is no element `z` of the poset + so that `x < z < y`. INPUT: - ``shell`` -- the shell for which to find the covering shells. + There is no restriction of ``shell`` being contained in the poset. + If ``shell`` is contained in the poset, then use the more efficient + methods :meth:`predecessors` and :meth:`successors`. - - ``reverse`` -- (default: ``False``) if not set, then find - the lower covers, otherwise find the upper covers. + - ``reverse`` -- (default: ``False``) if set, then find + the lower covers (see also :meth:`lower_covers`) + instead of the upper covers. OUTPUT: - A set of the covers. + A set of :class:`shells `. + + .. NOTE:: - Suppose ``reverse`` is ``False``. This method returns all the - lower covers of the given ``shell``, i.e., shells in the - poset, which are at most the given shell and maximal with - this property. Only shells which are (not necessarily - direct) successors of the calling shell are considered. + Suppose ``reverse`` is ``False``. This method starts at + the calling shell (``self``) and searches towards ``'null'``. + Thus, only shells which are (not necessarily + direct) predecessors of this shell are considered. - If ``reverse`` is ``True``, then the reverse direction is - taken, i.e., in the text above replace lower covers by upper - covers, maximal by minimal, and successors by predecessors. + If ``reverse`` is ``True``, then the reverse direction is + taken. EXAMPLES:: @@ -732,25 +867,39 @@ def covers(self, shell, reverse=False): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: e = P.shell(T((2, 2))); e (2, 2) - sage: sorted(P.null.covers(e), + sage: sorted(P.null.lower_covers(e), ....: key=lambda c: repr(c.element)) [(1, 2), (2, 1)] - sage: sorted(P.oo.covers(e, reverse=True), + sage: set(_) == e.predecessors() + True + sage: sorted(P.oo.upper_covers(e), + ....: key=lambda c: repr(c.element)) + [(4, 4)] + sage: set(_) == e.successors() + True + + :: + + sage: Q = MP([T((3, 2))]) + sage: f = next(Q.shells()) + sage: sorted(P.null.lower_covers(f), + ....: key=lambda c: repr(c.element)) + [(2, 2)] + sage: sorted(P.oo.upper_covers(f), ....: key=lambda c: repr(c.element)) [(4, 4)] + + .. SEEALSO:: + + :meth:`predecessors`, + :meth:`successors`, + :class:`MutablePoset`. """ - covers = set() - self._search_covers_(covers, shell, reverse) - return covers + return self.lower_covers(shell, not reverse) def _iter_depth_first_visit_(self, marked, @@ -777,12 +926,24 @@ def _iter_depth_first_visit_(self, marked, shell to ``True`` (include in iteration) or ``False`` (do not include). ``None`` is equivalent to a function returning always ``True``. Note that the iteration does not go beyond a - not shell included shell. + not included shell. OUTPUT: An iterator. + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -790,7 +951,7 @@ def _iter_depth_first_visit_(self, marked, sage: P.add(42) sage: P.add(5) sage: marked = set() - sage: list(P.oo._iter_depth_first_visit_(marked, True)) + sage: list(P.oo._iter_depth_first_visit_(marked, reverse=True)) [oo, 42, 5, null] """ if (condition is not None and @@ -811,7 +972,7 @@ def _iter_depth_first_visit_(self, marked, def iter_depth_first(self, reverse=False, key=None, condition=None): r""" - Iterates over all shells in depth first order. + Iterate over all shells in depth first order. INPUT: @@ -827,12 +988,18 @@ def iter_depth_first(self, reverse=False, key=None, condition=None): shell to ``True`` (include in iteration) or ``False`` (do not include). ``None`` is equivalent to a function returning always ``True``. Note that the iteration does not go beyond a - not shell included shell. + not included shell. OUTPUT: An iterator. + .. NOTE:: + + The depth first search starts at this (``self``) shell. Thus + only this shell and shells greater than (in case of + ``reverse=False``) this shell are visited. + ALGORITHM: See :wikipedia:`Depth-first_search`. @@ -843,13 +1010,8 @@ def iter_depth_first(self, reverse=False, key=None, condition=None): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: list(P.null.iter_depth_first(reverse=False, key=repr)) [null, (1, 1), (1, 2), (1, 3), (4, 4), oo, (2, 2), (2, 1)] sage: list(P.oo.iter_depth_first(reverse=True, key=repr)) @@ -857,6 +1019,11 @@ def iter_depth_first(self, reverse=False, key=None, condition=None): sage: list(P.null.iter_depth_first( ....: condition=lambda s: s.element[0] == 1)) [null, (1, 1), (1, 2), (1, 3)] + + .. SEEALSO:: + + :meth:`iter_topological`, + :class:`MutablePoset`. """ marked = set() return self._iter_depth_first_visit_(marked, reverse, key, condition) @@ -879,19 +1046,32 @@ def _iter_topological_visit_(self, marked, ``True`` searches towards ``'null'``. - ``key`` -- (default: ``None``) a function used for sorting - the direct successors of a shell (used in case of a + the direct predecessors of a shell (used in case of a tie). If this is ``None``, no sorting occurs. - ``condition`` -- (default: ``None``) a function mapping a shell to ``True`` (include in iteration) or ``False`` (do not include). ``None`` is equivalent to a function returning always ``True``. Note that the iteration does not go beyond a - not shell included shell. + not included shell. OUTPUT: An iterator. + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`iter_topological`, + :class:`MutablePoset`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -899,7 +1079,7 @@ def _iter_topological_visit_(self, marked, sage: P.add(42) sage: P.add(5) sage: marked = set() - sage: list(P.null._iter_topological_visit_(marked, True)) + sage: list(P.null._iter_topological_visit_(marked, reverse=True)) [oo, 42, 5, null] """ if (condition is not None and @@ -920,7 +1100,7 @@ def _iter_topological_visit_(self, marked, def iter_topological(self, reverse=False, key=None, condition=None): r""" - Iterates over all shells in topological order. + Iterate over all shells in topological order. INPUT: @@ -929,19 +1109,26 @@ def iter_topological(self, reverse=False, key=None, condition=None): ``True`` searches towards ``'null'``. - ``key`` -- (default: ``None``) a function used for sorting - the direct successors of a shell (used in case of a + the direct predeccessors of a shell (used in case of a tie). If this is ``None``, no sorting occurs. - ``condition`` -- (default: ``None``) a function mapping a shell to ``True`` (include in iteration) or ``False`` (do not include). ``None`` is equivalent to a function returning always ``True``. Note that the iteration does not go beyond a - not shell included shell. + not included shell. OUTPUT: An iterator. + .. NOTE:: + + The topological search will only find shells smaller than + (in case of ``reverse=False``) + or equal to this (``self``) shell. This is in contrast to + :meth:`iter_depth_first`. + ALGORITHM: Here a simplified version of the algorithm found in [T1976]_ @@ -963,18 +1150,13 @@ def iter_topological(self, reverse=False, key=None, condition=None): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) :: sage: for e in P.shells_topological(include_special=True, - ....: reverse=True): + ....: reverse=True): ....: print e ....: print list(e.iter_topological(reverse=True, key=repr)) oo @@ -997,7 +1179,7 @@ def iter_topological(self, reverse=False, key=None, condition=None): :: sage: for e in P.shells_topological(include_special=True, - ....: reverse=True): + ....: reverse=True): ....: print e ....: print list(e.iter_topological(reverse=False, key=repr)) oo @@ -1019,11 +1201,17 @@ def iter_topological(self, reverse=False, key=None, condition=None): :: - sage: def C(shell): - ....: return shell.element[0] == 1 sage: list(P.null.iter_topological( ....: reverse=True, condition=lambda s: s.element[0] == 1)) [(1, 3), (1, 2), (1, 1), null] + + .. SEEALSO:: + + :meth:`iter_depth_first`, + :meth:`MutablePoset.shells_topological`, + :meth:`MutablePoset.elements_topological`, + :meth:`MutablePoset.keys_topological`, + :class:`MutablePoset`. """ marked = set() return self._iter_topological_visit_(marked, reverse, key, condition) @@ -1040,15 +1228,28 @@ def merge(self, element, check=True, delete=True): - ``check`` -- (default: ``True``) if set, then the ``can_merge``-function of :class:`MutablePoset` determines - if the merge is possible. + whether the merge is possible. ``can_merge`` is ``None`` means + that this check is always passed. - - ``delete`` -- (default: ``True``) if set, then the passed - element is removed from the poset after the merge. + - ``delete`` -- (default: ``True``) if set, then ``element`` + is removed from the poset after the merge. OUTPUT: Nothing. + .. NOTE:: + + This operation depends on the parameters ``merge`` and + ``can_merge`` of the :class:`MutablePoset` this shell is + contained in. These parameters are defined when the poset + is constructed. + + .. NOTE:: + + If the ``merge`` function returns ``None``, then this shell + is removed from the poset. + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1056,24 +1257,37 @@ def merge(self, element, check=True, delete=True): ....: return (left[0], ''.join(sorted(left[1] + right[1]))) sage: def can_add(left, right): ....: return left[0] <= right[0] - sage: P = MP(key=lambda c: c[0], merge=add, can_merge=can_add) - sage: P.add((1, 'a')) - sage: P.add((3, 'b')) - sage: P.add((2, 'c')) - sage: P.add((4, 'd')) + sage: P = MP([(1, 'a'), (3, 'b'), (2, 'c'), (4, 'd')], + ....: key=lambda c: c[0], merge=add, can_merge=can_add) sage: P poset((1, 'a'), (2, 'c'), (3, 'b'), (4, 'd')) sage: P.shell(2).merge((3, 'b')) sage: P poset((1, 'a'), (2, 'bc'), (4, 'd')) + + .. SEEALSO:: + + :meth:`MutablePoset.merge`, + :class:`MutablePoset`. + + TESTS:: + + sage: MP([2], merge=operator.add, + ....: can_merge=lambda _, __: False).shell(2).merge(1) + Traceback (most recent call last): + ... + RuntimeError: Cannot merge 2 with 1. """ poset = self.poset if poset._merge_ is None: + # poset._merge_ is None means no merge (poset._merge_ simply + # returns its first input argument). return self_element = self.element - if check and poset._can_merge_ is not None and \ - not poset._can_merge_(self_element, element): - return + if check: + if not poset._can_merge_(self_element, element): + raise RuntimeError('Cannot merge %s with %s.' % + (self_element, element)) new = poset._merge_(self_element, element) if new is None: poset.discard(poset.get_key(self.element)) @@ -1088,7 +1302,11 @@ def merge(self, element, check=True, delete=True): def is_MutablePoset(P): r""" - Tests if ``P`` inherits from :class:`MutablePoset`. + Test whether ``P`` inherits from :class:`MutablePoset`. + + .. SEEALSO:: + + :class:`MutablePoset` TESTS:: @@ -1121,21 +1339,28 @@ class MutablePoset(SageObject): (default), this is the identity, i.e., keys are equal to their elements. + Two elements with the same keys are considered as equal; so only + one of these two elements can be in the poset. + + This ``key`` is not used for sorting (in contrast to + sorting-functions, e.g. ``sorted``). + - ``merge`` -- a function which merges its second argument (an element) to its first (again an element) and returns the result (as an element). If the return value is ``None``, the element is - removed out of the poset. + removed from the poset. This hook is called by :meth:`merge`. Moreover it is used during :meth:`add` when an element (more precisely its key) is already in this poset. ``merge`` is ``None`` (default) is equivalent to ``merge`` - returns its first argument. Note that it is not allowed that the + returning its first argument. Note that it is not allowed that the key of the returning element differs from the key of the first - input parameter. + input parameter. This means ``merge`` must not change the + position of the element in the poset. - - ``can_merge`` -- a function which checks if its second argument + - ``can_merge`` -- a function which checks whether its second argument can be merged to its first. This hook is called by :meth:`merge`. Moreover it is used during @@ -1143,7 +1368,7 @@ class MutablePoset(SageObject): in this poset. ``can_merge`` is ``None`` (default) is equivalent to ``can_merge`` - returns ``True`` in all cases. + returning ``True`` in all cases. OUTPUT: @@ -1175,6 +1400,10 @@ class MutablePoset(SageObject): sage: C = MP([5, 3, 11]); C poset(3, 5, 11) + + .. SEEALSO:: + + :class:`MutablePosetShell`. """ def __init__(self, data=None, key=None, merge=None, can_merge=None): r""" @@ -1219,7 +1448,10 @@ def __init__(self, data=None, key=None, merge=None, can_merge=None): self._key_ = key self._merge_ = merge - self._can_merge_ = can_merge + if can_merge is None: + self._can_merge_ = lambda _, __: True + else: + self._can_merge_ = can_merge if data is not None: try: @@ -1228,6 +1460,7 @@ def __init__(self, data=None, key=None, merge=None, can_merge=None): raise TypeError('%s is not iterable; do not know what to ' 'do with it.' % (data,)) self.union_update(it) + super(MutablePoset, self).__init__() def clear(self): @@ -1242,6 +1475,12 @@ def clear(self): Nothing. + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1277,6 +1516,10 @@ def __len__(self): An integer. + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` are not counted. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1308,6 +1551,12 @@ def null(self): null sage: z.is_null() True + + .. SEEALSO:: + + :meth:`oo`, + :meth:`MutablePosetShell.is_null`, + :meth:`MutablePosetShell.is_special`. """ return self._null_ @@ -1326,6 +1575,12 @@ def oo(self): oo sage: oo.is_oo() True + + .. SEEALSO:: + + :meth:`null`, + :meth:`MutablePosetShell.is_oo`, + :meth:`MutablePosetShell.is_special`. """ return self._oo_ @@ -1356,6 +1611,11 @@ def shell(self, key): 42 sage: type(e) + + .. SEEALSO:: + + :meth:`element`, + :meth:`get_key`. """ return self._shells_[key] @@ -1381,6 +1641,11 @@ def element(self, key): 42 sage: type(e) + + .. SEEALSO:: + + :meth:`shell`, + :meth:`get_key`. """ return self.shell(key).element @@ -1397,10 +1662,14 @@ def get_key(self, element): An object (the key of ``element``). + .. SEEALSO:: + + :meth:`element`, + :meth:`shell`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: from sage.data_structures.mutable_poset import MutablePosetShell sage: P = MP() sage: P.get_key(None) is None True @@ -1430,6 +1699,10 @@ def _copy_shells_(self, other, mapping): Nothing. + .. SEEALSO:: + + :meth:`copy` + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1437,11 +1710,8 @@ def _copy_shells_(self, other, mapping): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) sage: Q = MP() sage: Q._copy_shells_(P, lambda e: e) sage: P.repr_full() == Q.repr_full() @@ -1461,7 +1731,7 @@ def _copy_shells_(self, other, mapping): def copy(self, mapping=None): r""" - Creates a shallow copy. + Create a shallow copy. INPUT: @@ -1471,18 +1741,19 @@ def copy(self, mapping=None): A poset with the same content as ``self``. + .. SEEALSO:: + + :meth:`map`, + :meth:`mapped`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) sage: Q = copy(P) # indirect doctest sage: P.repr_full() == Q.repr_full() True @@ -1524,6 +1795,16 @@ def shells(self, include_special=False): () sage: tuple(P.shells(include_special=True)) (null, oo) + + .. SEEALSO:: + + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ if include_special: yield self.null @@ -1543,15 +1824,15 @@ def shells_topological(self, include_special=False, - ``include_special`` -- (default: ``False``) if set, then including shells containing a smallest element (`\emptyset`) and a largest element (`\infty`). - + - ``reverse`` -- (default: ``False``) -- if set, reverses the order, i.e., ``False`` gives smallest elements first, ``True`` gives largest first. - ``key`` -- (default: ``None``) a function used for sorting - the direct successors of a shell (used in case of a - tie). If this is ``None``, no sorting according to the - representation string occurs. + the direct successors of a shell (used in case of a tie). If + this is ``None``, then the successors are sorted according + to their representation strings. OUTPUT: @@ -1568,13 +1849,8 @@ def shells_topological(self, include_special=False, sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: list(P.shells_topological()) [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (4, 4)] sage: list(P.shells_topological(reverse=True)) @@ -1584,6 +1860,16 @@ def shells_topological(self, include_special=False, sage: list(P.shells_topological( ....: include_special=True, reverse=True)) [oo, (4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1), null] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ if key is None: key = repr @@ -1607,10 +1893,7 @@ def elements(self, **kwargs): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3) - sage: P.add(42) - sage: P.add(7) + sage: P = MP([3, 42, 7]) sage: [(v, type(v)) for v in sorted(P.elements())] [(3, ), (7, ), @@ -1625,6 +1908,16 @@ def elements(self, **kwargs): [3, 7, 42] returns all elements as well. + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ for shell in self.shells(**kwargs): yield shell.element @@ -1651,13 +1944,8 @@ def elements_topological(self, **kwargs): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: [(v, type(v)) for v in P.elements_topological()] [((1, 1), ), ((1, 2), ), @@ -1665,6 +1953,16 @@ def elements_topological(self, **kwargs): ((2, 1), ), ((2, 2), ), ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`keys`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ for shell in self.shells_topological(**kwargs): yield shell.element @@ -1685,10 +1983,7 @@ def keys(self, **kwargs): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP(key=lambda c: -c) - sage: P.add(3) - sage: P.add(42) - sage: P.add(7) + sage: P = MP([3, 42, 7], key=lambda c: -c) sage: [(v, type(v)) for v in sorted(P.keys())] [(-42, ), (-7, ), @@ -1704,6 +1999,16 @@ def keys(self, **kwargs): [(3, ), (7, ), (42, )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys_topological`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ for shell in self.shells(**kwargs): yield shell.key @@ -1725,13 +2030,8 @@ def keys_topological(self, **kwargs): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP(key=lambda c: c[0]) - sage: P.add((1, 1)) - sage: P.add((1, 3)) - sage: P.add((2, 1)) - sage: P.add((4, 4)) - sage: P.add((1, 2)) - sage: P.add((2, 2)) + sage: P = MP([(1, 1), (2, 1), (4, 4)], + ....: key=lambda c: c[0]) sage: [(v, type(v)) for v in P.keys_topological()] [(1, ), (2, ), @@ -1744,6 +2044,16 @@ def keys_topological(self, **kwargs): [((1, 1), ), ((2, 1), ), ((4, 4), )] + + .. SEEALSO:: + + :meth:`shells`, + :meth:`shells_topological`, + :meth:`elements`, + :meth:`elements_topological`, + :meth:`keys`, + :meth:`MutablePosetShell.iter_depth_first`, + :meth:`MutablePosetShell.iter_topological`. """ for shell in self.shells_topological(**kwargs): yield shell.key @@ -1755,12 +2065,21 @@ def repr(self, include_special=False, reverse=False): INPUT: - Nothing. + - ``include_special`` -- (default: ``False``) a boolean + indicating whether to include the special elements + ``'null'`` and ``'oo'`` or not. + + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. OUTPUT: A string. + .. SEEALSO:: + + :meth:`repr_full` + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1780,12 +2099,17 @@ def repr_full(self, reverse=False): INPUT: - Nothing. + - ``reverse`` -- (default: ``False``) a boolean. If set, then + largest elements are displayed first. OUTPUT: A string. + .. SEEALSO:: + + :meth:`repr` + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1815,12 +2139,12 @@ def repr_full(self, reverse=False): return '\n'.join(strings) - __repr__ = repr + _repr_ = repr def contains(self, key): r""" - Tests if ``key`` is encapsulated by one of the poset's elements. + Test whether ``key`` is encapsulated by one of the poset's elements. INPUT: @@ -1830,6 +2154,12 @@ def contains(self, key): ``True`` or ``False``. + .. SEEALSO:: + + :meth:`shells`, + :meth:`elements`, + :meth:`keys`. + TESTS:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -1856,7 +2186,7 @@ def add(self, element): INPUT: - ``element`` -- an object (hashable and supporting comparison - with the operator ``<=``. + with the operator ``<=``). OUTPUT: @@ -1868,12 +2198,8 @@ def add(self, element): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2))]) sage: print P.repr_full(reverse=True) poset((4, 4), (1, 3), (1, 2), (2, 1), (1, 1)) +-- oo @@ -1962,15 +2288,17 @@ def add(self, element): sage: B poset() + .. SEEALSO:: + + :meth:`discard`, + :meth:`pop`, + :meth:`remove`. + TESTS:: - sage: R = MP(key=lambda k: T(k[2:3])) - sage: R.add((1, 1, 42)) - sage: R.add((1, 3, 42)) - sage: R.add((2, 1, 7)) - sage: R.add((4, 4, 42)) - sage: R.add((1, 2, 7)) - sage: R.add((2, 2, 7)) + sage: R = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[2:3])) sage: print R.repr_full(reverse=True) poset((1, 1, 42), (2, 1, 7)) +-- oo @@ -2004,20 +2332,16 @@ def add(self, element): return new = MutablePosetShell(self, element) - smaller = self.null.covers(new, reverse=False) - larger = self.oo.covers(new, reverse=True) + new._predecessors_ = self.null.lower_covers(new) + new._successors_ = self.oo.upper_covers(new) - # In the following we first search towards oo (reverse=False) and - # then towards null (reverse=True; everything is "inverted"). - for reverse in (False, True): - sm = smaller if not reverse else larger - la = larger if not reverse else smaller - for shell in sm: - for e in shell.successors(reverse).intersection(la): - e.predecessors(reverse).remove(shell) - shell.successors(reverse).remove(e) - new.predecessors(reverse).add(shell) - shell.successors(reverse).add(new) + for s in new.predecessors(): + for l in s.successors().intersection(new.successors()): + l.predecessors().remove(s) + s.successors().remove(l) + s.successors().add(new) + for l in new.successors(): + l.predecessors().add(new) self._shells_[key] = new @@ -2040,19 +2364,25 @@ def remove(self, key, raise_key_error=True): If the element is not a member and ``raise_key_error`` is set (default), raise a ``KeyError``. + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: print P.repr_full(reverse=True) poset((4, 4), (1, 3), (2, 2), (1, 2), (2, 1), (1, 1)) +-- oo @@ -2104,15 +2434,18 @@ def remove(self, key, raise_key_error=True): | +-- successors: (1, 1) | +-- no predecessors + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`pop`. + TESTS:: - sage: Q = MP(key=lambda k: T(k[0:2])) - sage: Q.add((1, 1, 42)) - sage: Q.add((1, 3, 42)) - sage: Q.add((2, 1, 7)) - sage: Q.add((4, 4, 42)) - sage: Q.add((1, 2, 7)) - sage: Q.add((2, 2, 7)) + sage: Q = MP([(1, 1, 42), (1, 3, 42), (2, 1, 7), + ....: (4, 4, 42), (1, 2, 7), (2, 2, 7)], + ....: key=lambda k: T(k[0:2])) sage: print Q.repr_full(reverse=True) poset((4, 4, 42), (1, 3, 42), (2, 2, 7), (1, 2, 7), (2, 1, 7), (1, 1, 42)) @@ -2212,25 +2545,38 @@ def discard(self, key, raise_key_error=False): If the element is not a member and ``raise_key_error`` is set (not default), raise a ``KeyError``. + .. NOTE:: + + As with Python's ``set``, the methods :meth:`remove` + and :meth:`discard` only differ in their behavior when an + element is not contained in the poset: :meth:`remove` + raises a ``KeyError`` whereas :meth:`discard` does not + raise any exception. + + This default behavior can be overridden with the + ``raise_key_error`` parameter. + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: P.discard(T((1, 2))) sage: P.remove(T((1, 2))) Traceback (most recent call last): ... KeyError: 'Key (1, 2) is not contained in this poset.' sage: P.discard(T((1, 2))) + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`remove`, + :meth:`pop`. """ return self.remove(key, raise_key_error) @@ -2247,10 +2593,14 @@ def pop(self, **kwargs): An object. + .. NOTE:: + + The special elements ``'null'`` and ``'oo'`` cannot be popped. + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP(key=lambda c: -c) + sage: P = MP() sage: P.add(3) sage: P poset(3) @@ -2262,11 +2612,14 @@ def pop(self, **kwargs): Traceback (most recent call last): ... KeyError: 'pop from an empty poset' + + .. SEEALSO:: + + :meth:`add`, + :meth:`clear`, + :meth:`discard`, + :meth:`remove`. """ - try: - del kwargs['include_special'] - except KeyError: - pass kwargs['include_special'] = False try: @@ -2285,6 +2638,8 @@ def union(self, *other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2292,34 +2647,49 @@ def union(self, *other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. + + Due to keys and a ``merge`` function (see :class:`MutablePoset`) + this operation might not be commutative. + + .. TODO:: + + Use the already existing information in the other poset to speed + up this function. (At the moment each element of the other poset + is inserted one by one and without using this information.) EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.union(Q) poset(3, 4, 7, 8, 42) + .. SEEALSO:: + + :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + TESTS:: sage: P.union(P, Q, Q, P) poset(3, 4, 7, 8, 42) """ new = self.copy() - for o in other: - new.update(o) + new.update(*other) return new - def union_update(self, other): + def union_update(self, *other): r""" Update this poset with the union of itself and another poset. @@ -2327,6 +2697,8 @@ def union_update(self, other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2334,9 +2706,10 @@ def union_update(self, other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.union_update(B)`` and ``B.union_update(A)`` might + result in different posets. .. TODO:: @@ -2347,28 +2720,37 @@ def union_update(self, other): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.union_update(Q) sage: P poset(3, 4, 7, 8, 42) + .. SEEALSO:: + + :meth:`union`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + TESTS:: sage: Q.update(P) sage: Q poset(3, 4, 7, 8, 42) """ - try: - it = other.elements() - except AttributeError: - it = iter(other) - for element in it: - self.add(element) + for o in other: + try: + it = o.elements() + except AttributeError: + it = iter(o) + for element in it: + self.add(element) update = union_update # as in a Python set @@ -2386,6 +2768,8 @@ def difference(self, *other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2393,22 +2777,29 @@ def difference(self, *other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.difference(Q) poset(3, 7) + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + TESTS:: sage: P.difference(Q, Q) @@ -2419,12 +2810,11 @@ def difference(self, *other): poset() """ new = self.copy() - for o in other: - new.difference_update(o) + new.difference_update(*other) return new - def difference_update(self, other): + def difference_update(self, *other): r""" Remove all elements of another poset from this poset. @@ -2432,6 +2822,8 @@ def difference_update(self, other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2439,29 +2831,37 @@ def difference_update(self, other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.difference_update(Q) sage: P poset(3, 7) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. """ - try: - it = other.keys() - except AttributeError: - it = iter(other) - for key in it: - self.discard(key) + for o in other: + try: + it = o.keys() + except AttributeError: + it = iter(o) + for key in it: + self.discard(key) def intersection(self, *other): @@ -2472,6 +2872,8 @@ def intersection(self, *other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2479,34 +2881,40 @@ def intersection(self, *other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.intersection(Q) poset(42) + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. + TESTS:: sage: P.intersection(P, Q, Q, P) poset(42) """ new = self.copy() - for o in other: - new.intersection_update(o) + new.intersection_update(*other) return new - def intersection_update(self, other): + def intersection_update(self, *other): r""" Update this poset with the intersection of itself and another poset. @@ -2514,6 +2922,8 @@ def intersection_update(self, other): - ``other`` -- a poset or an iterable. In the latter case the iterated objects are seen as elements of a poset. + It is possible to specify more than one ``other`` as + variadic arguments (arbitrary argument lists). OUTPUT: @@ -2521,29 +2931,35 @@ def intersection_update(self, other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.intersection_update(B)`` and ``B.intersection_update(A)`` might + result in different posets. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.intersection_update(Q) sage: P poset(42) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. """ - try: - keys = tuple(self.keys()) - except AttributeError: - keys = tuple(iter(self)) + keys = tuple(self.keys()) for key in keys: - if key not in other: + if any(key not in o for o in other): self.discard(key) @@ -2561,21 +2977,28 @@ def symmetric_difference(self, other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.symmetric_difference(Q) poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference_update`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. """ new = self.copy() new.symmetric_difference_update(other) @@ -2597,22 +3020,32 @@ def symmetric_difference_update(self, other): .. NOTE:: - If this poset uses a ``key``-function, then all - comparisons are performed on the keys of the elements (and - not on the elements themselves). + The key of an element is used for comparison. Thus elements with + the same key are considered as equal; + ``A.symmetric_difference_update(B)`` and + ``B.symmetric_difference_update(A)`` might + result in different posets. EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.symmetric_difference_update(Q) sage: P poset(3, 4, 7, 8) + + .. SEEALSO:: + + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`is_superset`. """ T = other.difference(self) self.difference_update(other) @@ -2621,7 +3054,7 @@ def symmetric_difference_update(self, other): def is_disjoint(self, other): r""" - Return if another poset is disjoint to this poset. + Return whether another poset is disjoint to this poset. INPUT: @@ -2641,16 +3074,23 @@ def is_disjoint(self, other): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.is_disjoint(Q) False sage: P.is_disjoint(Q.difference(P)) True + + .. SEEALSO:: + + :meth:`is_subset`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. """ return all(key not in other for key in self.keys()) @@ -2663,7 +3103,7 @@ def is_disjoint(self, other): def is_subset(self, other): r""" - Return if another poset contains this poset, i.e., if this poset + Return whether another poset contains this poset, i.e., whether this poset is a subset of the other poset. INPUT: @@ -2684,11 +3124,9 @@ def is_subset(self, other): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.is_subset(Q) False @@ -2698,6 +3136,15 @@ def is_subset(self, other): True sage: P.is_subset(P.union(Q)) True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_superset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. """ return all(key in other for key in self.keys()) @@ -2710,7 +3157,7 @@ def is_subset(self, other): def is_superset(self, other): r""" - Return if this poset contains another poset, i.e., if this poset + Return whether this poset contains another poset, i.e., whether this poset is a superset of the other poset. INPUT: @@ -2731,11 +3178,9 @@ def is_superset(self, other): EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP - sage: P = MP() - sage: P.add(3); P.add(42); P.add(7); P + sage: P = MP([3, 42, 7]); P poset(3, 7, 42) - sage: Q = MP() - sage: Q.add(4); Q.add(8); Q.add(42); Q + sage: Q = MP([4, 8, 42]); Q poset(4, 8, 42) sage: P.is_superset(Q) False @@ -2745,6 +3190,15 @@ def is_superset(self, other): True sage: P.union(Q).is_superset(P) True + + .. SEEALSO:: + + :meth:`is_disjoint`, + :meth:`is_subset`, + :meth:`union`, :meth:`union_update`, + :meth:`difference`, :meth:`difference_update`, + :meth:`intersection`, :meth:`intersection_update`, + :meth:`symmetric_difference`, :meth:`symmetric_difference_update`. """ try: it = other.keys() @@ -2770,7 +3224,10 @@ def merge(self, key=None, reverse=False): element in this poset. - ``reverse`` -- (default: ``False``) specifies which - direction to go first. When ``key=None``, then this also + direction to go first: + ``False`` searches towards ``'oo'`` and + ``True`` searches towards ``'null'``. + When ``key=None``, then this also specifies which elements are merged first. OUTPUT: @@ -2778,13 +3235,26 @@ def merge(self, key=None, reverse=False): Nothing. This method tests all (not necessarily direct) successors and - predecessors of the given element if they can be merged with + predecessors of the given element whether they can be merged with the element itself. This is done by the ``can_merge``-function of :class:`MutablePoset`. If this merge is possible, then it is performed by calling :class:`MutablePoset`'s ``merge``-function and the corresponding successor/predecessor is removed from the poset. + .. NOTE:: + + ``can_merge`` is applied in the sense of the condition of + depth first iteration, i.e., once ``can_merge`` fails, + the successors/predecessors are no longer tested. + + .. NOTE:: + + The motivation for such a merge behavior comes from + asymptotic expansions: `O(n^3)` merges with, for + example, `3n^2` or `O(n)` to `O(n^3)` (as `n` tends to + `\infty`; see :wikipedia:`Big_O_notation`). + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -2797,13 +3267,9 @@ def merge(self, key=None, reverse=False): ....: ''.join(sorted(left[2] + right[2]))) sage: def can_add(left, right): ....: return key(left) >= key(right) - sage: P = MP(key=key, merge=add, can_merge=can_add) - sage: P.add((1, 1, 'a')) - sage: P.add((1, 3, 'b')) - sage: P.add((2, 1, 'c')) - sage: P.add((4, 4, 'd')) - sage: P.add((1, 2, 'e')) - sage: P.add((2, 2, 'f')) + sage: P = MP([(1, 1, 'a'), (1, 3, 'b'), (2, 1, 'c'), + ....: (4, 4, 'd'), (1, 2, 'e'), (2, 2, 'f')], + ....: key=key, merge=add, can_merge=can_add) sage: Q = copy(P) sage: Q.merge(T((1, 3))) sage: print Q.repr_full(reverse=True) @@ -2844,6 +3310,10 @@ def merge(self, key=None, reverse=False): sage: Q.merge(); Q poset((4, 4, 'abcdef')) + .. SEEALSO:: + + :meth:`MutablePosetShell.merge` + TESTS:: sage: copy(P).merge(reverse=False) == copy(P).merge(reverse=True) @@ -2860,6 +3330,16 @@ def merge(self, key=None, reverse=False): sage: R = P.mapped(lambda x: x+1) sage: R.merge(reverse=True); R poset(4) + + :: + + sage: P = MP(srange(4), + ....: merge=lambda l, r: r, can_merge=lambda l, r: l < r) + sage: P.merge() + Traceback (most recent call last): + ... + RuntimeError: Stopping merge before started; + the can_merge-function is not reflexive. """ if key is None: for shell in tuple(self.shells_topological(reverse=reverse)): @@ -2881,7 +3361,7 @@ def can_merge(other): for m in tuple(to_merge): if m.is_special(): continue - shell.merge(m.element, delete=True) + shell.merge(m.element, check=False, delete=True) def maximal_elements(self): @@ -2902,14 +3382,14 @@ def maximal_elements(self): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 1))) - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 1)), T((1, 3)), T((2, 1)), + ....: T((1, 2)), T((2, 2))]) sage: list(P.maximal_elements()) [(1, 3), (2, 2)] + + .. SEEALSO:: + + :meth:`minimal_elements` """ return iter(shell.element for shell in self.oo.predecessors() @@ -2934,14 +3414,14 @@ def minimal_elements(self): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: list(P.minimal_elements()) [(1, 2), (2, 1)] + + .. SEEALSO:: + + :meth:`maximal_elements` """ return iter(shell.element for shell in self.null.successors() @@ -2977,15 +3457,17 @@ def map(self, function, topological=False, reverse=False): sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) - sage: P.map(lambda e: str(e)) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))], + ....: key=lambda e: e[:2]) + sage: P.map(lambda e: e + (sum(e),)) sage: P - poset('(1, 2)', '(1, 3)', '(2, 1)', '(2, 2)', '(4, 4)') + poset((1, 2, 3), (1, 3, 4), (2, 1, 3), (2, 2, 4), (4, 4, 8)) + + .. SEEALSO:: + + :meth:`copy`, + :meth:`mapped`. """ shells = self.shells_topological(reverse=reverse) \ if topological else self.shells() @@ -3012,20 +3494,27 @@ def mapped(self, function): A :class:`MutablePoset`. + .. NOTE:: + + ``function`` is not allowed to change the order of the keys, + but changing the keys themselves is allowed (in contrast + to :meth:`map`). + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP sage: class T(tuple): ....: def __le__(left, right): ....: return all(l <= r for l, r in zip(left, right)) - sage: P = MP() - sage: P.add(T((1, 3))) - sage: P.add(T((2, 1))) - sage: P.add(T((4, 4))) - sage: P.add(T((1, 2))) - sage: P.add(T((2, 2))) + sage: P = MP([T((1, 3)), T((2, 1)), + ....: T((4, 4)), T((1, 2)), T((2, 2))]) sage: P.mapped(lambda e: str(e)) poset('(1, 2)', '(1, 3)', '(2, 1)', '(2, 2)', '(4, 4)') + + .. SEEALSO:: + + :meth:`copy`, + :meth:`map`. """ return self.copy(mapping=function) diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index c831644c4ca..61dfb90dab1 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -458,6 +458,15 @@ def __call__(self, query, function=None, depth=2, max_values=FINDSTAT_MAX_VALUES Traceback (most recent call last): ... ValueError: The given arguments, Permutations and 1, cannot be used for a FindStat search. + + sage: from sage.databases.findstat import FindStatCollection + sage: findstat(FindStatCollection("Permutations"), lambda pi: pi.length()) # optional -- internet,random + 0: (St000018: The [[/Permutations/Inversions|number of inversions]] of a permutation., [], 200) + 1: (St000004: The [[/Permutations/Descents-Major|major index]] of a permutation., [Mp00062: inversion-number to major-index bijection], 200) + 2: (St000067: The inversion number of the alternating sign matrix., [Mp00063: to alternating sign matrix], 152) + 3: (St000246: The [[/Permutations/Inversions|number of non-inversions]] of a permutation., [Mp00064: reverse], 200) + 4: (St000008: The major index of the composition., [Mp00062: inversion-number to major-index bijection, Mp00071: descent composition], 200) + """ try: depth = int(depth) @@ -526,7 +535,9 @@ def __call__(self, query, function=None, depth=2, max_values=FINDSTAT_MAX_VALUES else: if callable(function): - if not isinstance(query, FindStatCollection): + if isinstance(query, FindStatCollection): + collection = query + else: collection = FindStatCollection(query) first_terms = collection.first_terms(function, max_values=max_values) data = [([key], [value]) for (key, value) in first_terms] diff --git a/src/sage/dynamics/interval_exchanges/template.py b/src/sage/dynamics/interval_exchanges/template.py index ded494ef6d0..8d9ee2fd7f9 100644 --- a/src/sage/dynamics/interval_exchanges/template.py +++ b/src/sage/dynamics/interval_exchanges/template.py @@ -251,7 +251,7 @@ class Permutation(SageObject): r""" Template for all permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -790,7 +790,7 @@ class PermutationIET(Permutation): """ Template for permutation from Interval Exchange Transformation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1582,7 +1582,7 @@ class PermutationLI(Permutation): r""" Template for quadratic permutation. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1857,7 +1857,7 @@ class FlippedPermutation(Permutation): r""" Template for flipped generalized permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1916,7 +1916,7 @@ class FlippedPermutationIET(FlippedPermutation, PermutationIET): r""" Template for flipped Abelian permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1945,7 +1945,7 @@ class FlippedPermutationLI(FlippedPermutation, PermutationLI): r""" Template for flipped quadratic permutations. - .. warning:: + .. WARNING:: Internal class! Do not use directly! @@ -1981,7 +1981,7 @@ class RauzyDiagram(SageObject): r""" Template for Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! @@ -2600,27 +2600,30 @@ def __init__(self, p, top_bottom_inversion=False, symmetric=False): r""" - self._succ contains successors - self._pred contains predecessors + - ``self._succ`` contains successors - self._element_class is the class of elements of self - self._element is an instance of this class (hence contains the alphabet, - the representation mode, ...). It is used to store data about property - of permutations and also as a fast iterator. + - ``self._pred`` contains predecessors - INPUT: + - ``self._element_class`` is the class of elements of ``self`` - - ``right_induction`` - boolean or 'top' or 'bottom': consider the - right induction + - ``self._element`` is an instance of this class (hence + contains the alphabet, the representation mode, ...). It is + used to store data about property of permutations and also as + a fast iterator. - - ``left_induction`` - boolean or 'top' or 'bottom': consider the - left induction + INPUT: + + - ``right_induction`` - boolean or 'top' or 'bottom': consider the + right induction + + - ``left_induction`` - boolean or 'top' or 'bottom': consider the + left induction - - ``left_right_inversion`` - consider the left right inversion + - ``left_right_inversion`` - consider the left right inversion - - ``top_bottom_inversion`` - consider the top bottom inversion + - ``top_bottom_inversion`` - consider the top bottom inversion - - ``symmetric`` - consider the symmetric + - ``symmetric`` - consider the symmetric TESTS:: @@ -3529,7 +3532,7 @@ class FlippedRauzyDiagram(RauzyDiagram): r""" Template for flipped Rauzy diagrams. - .. warning: + .. WARNING:: Internal class! Do not use directly! diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index ac1f2e0fd00..a25b85f5976 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -565,8 +565,10 @@ def __init__(self): 0.567143290409784 """ BuiltinFunction.__init__(self, "lambert_w", nargs=2, - conversions={'mathematica':'ProductLog', - 'maple':'LambertW'}) + conversions={'mathematica': 'ProductLog', + 'maple': 'LambertW', + 'matlab': 'lambertw', + 'maxima': 'generalized_lambert_w'}) def __call__(self, *args, **kwds): r""" @@ -718,21 +720,25 @@ def _maxima_init_evaled_(self, n, z): sage: lambert_w(0, x)._maxima_() lambert_w(_SAGE_VAR_x) sage: lambert_w(1, x)._maxima_() - Traceback (most recent call last): - ... - NotImplementedError: Non-principal branch lambert_w[1](x) is not implemented in Maxima + generalized_lambert_w(1,_SAGE_VAR_x) + + TESTS:: + + sage: lambert_w(x)._maxima_()._sage_() + lambert_w(x) + sage: lambert_w(2, x)._maxima_()._sage_() + lambert_w(2, x) """ + if isinstance(z, str): + maxima_z = z + elif hasattr(z, '_maxima_init_'): + maxima_z = z._maxima_init_() + else: + maxima_z = str(z) if n == 0: - if isinstance(z,str): - maxima_z=z - elif hasattr(z,'_maxima_init_'): - maxima_z=z._maxima_init_() - else: - maxima_z=str(z) return "lambert_w(%s)" % maxima_z else: - raise NotImplementedError("Non-principal branch lambert_w[%s](%s) is not implemented in Maxima" % (n, z)) - + return "generalized_lambert_w(%s,%s)" % (n, maxima_z) def _print_(self, n, z): """ @@ -759,15 +765,17 @@ def _print_latex_(self, n, z): EXAMPLES:: sage: latex(lambert_w(1)) - \operatorname{W_0}(1) + \operatorname{W}({1}) sage: latex(lambert_w(0,x)) - \operatorname{W_0}(x) + \operatorname{W}({x}) sage: latex(lambert_w(1,x)) - \operatorname{W_{1}}(x) + \operatorname{W_{1}}({x}) + sage: latex(lambert_w(1,x+exp(x))) + \operatorname{W_{1}}({x + e^{x}}) """ if n == 0: - return r"\operatorname{W_0}(%s)" % z + return r"\operatorname{W}({%s})" % z._latex_() else: - return r"\operatorname{W_{%s}}(%s)" % (n, z) + return r"\operatorname{W_{%s}}({%s})" % (n, z._latex_()) lambert_w = Function_lambert_w() diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 936b8c434ee..68fd8ba37cc 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -207,10 +207,10 @@ is_ToricLatticeQuotient from sage.geometry.toric_plotter import ToricPlotter, label_list from sage.graphs.digraph import DiGraph -from sage.matrix.all import matrix +from sage.matrix.all import matrix, MatrixSpace from sage.misc.all import cached_method, flatten, latex from sage.misc.superseded import deprecation -from sage.modules.all import span, vector +from sage.modules.all import span, vector, VectorSpace from sage.rings.all import QQ, RR, ZZ, gcd from sage.structure.all import SageObject, parent from sage.libs.ppl import C_Polyhedron, Generator_System, Constraint_System, \ @@ -4348,6 +4348,254 @@ def lineality(self): """ return self.linear_subspace().dimension() + @cached_method + def discrete_complementarity_set(self): + r""" + Compute a discrete complementarity set of this cone. + + A discrete complementarity set of a cone is the set of all + orthogonal pairs `(x,s)` where `x` is in some fixed generating + set of the cone, and `s` is in some fixed generating set of its + dual. The generators chosen for this cone and its dual are + simply their :meth:`~IntegralRayCollection.rays`. + + OUTPUT: + + A tuple of pairs `(x,s)` such that, + + * `x` and `s` are nonzero. + * `x` and `s` are orthogonal. + * `x` is one of this cone's :meth:`~IntegralRayCollection.rays`. + * `s` is one of the :meth:`~IntegralRayCollection.rays` of this + cone's :meth:`dual`. + + REFERENCES: + + .. [Orlitzky] M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + EXAMPLES: + + Pairs of standard basis elements form a discrete complementarity + set for the nonnegative orthant:: + + sage: K = Cone([(1,0),(0,1)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(0, 1), M(1, 0))) + + If a cone consists of a single ray, then the second components + of a discrete complementarity set for that cone should generate + the orthogonal complement of the ray:: + + sage: K = Cone([(1,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0), M(0, 1)), (N(1, 0), M(0, -1))) + sage: K = Cone([(1,0,0)]) + sage: K.discrete_complementarity_set() + ((N(1, 0, 0), M(0, 1, 0)), + (N(1, 0, 0), M(0, -1, 0)), + (N(1, 0, 0), M(0, 0, 1)), + (N(1, 0, 0), M(0, 0, -1))) + + When a cone is the entire space, its dual is the trivial cone, + so the only discrete complementarity set for it is empty:: + + sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)]) + sage: K.is_full_space() + True + sage: K.discrete_complementarity_set() + () + + Likewise for trivial cones, whose duals are the entire space:: + + sage: L = ToricLattice(0) + sage: K = Cone([], ToricLattice(0)) + sage: K.discrete_complementarity_set() + () + + TESTS: + + A discrete complementarity set for the dual can be obtained by + switching components in a discrete complementarity set of the + original cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs_dual = K.dual().discrete_complementarity_set() + sage: expected = tuple( (x,s) for (s,x) in dcs_dual ) + sage: actual = K.discrete_complementarity_set() + sage: sorted(actual) == sorted(expected) + True + + The pairs in a discrete complementarity set are in fact + complementary:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=6) + sage: dcs = K.discrete_complementarity_set() + sage: sum([ x.inner_product(s).abs() for (x,s) in dcs ]) + 0 + """ + # Return an immutable tuple instead of a mutable list because + # the result will be cached. + return tuple( (x,s) for x in self + for s in self.dual() + if x.inner_product(s) == 0 ) + + def lyapunov_like_basis(self): + r""" + Compute a basis of Lyapunov-like transformations on this cone. + + A linear transformation `L` is said to be Lyapunov-like on this + cone if `L(x)` and `s` are orthogonal for every pair `(x,s)` in + its :meth:`discrete_complementarity_set`. The set of all such + transformations forms a vector space, namely the Lie algebra of + the automorphism group of this cone. + + OUTPUT: + + A list of matrices forming a basis for the space of all + Lyapunov-like transformations on this cone. + + REFERENCES: + + M. Orlitzky. The Lyapunov rank of an improper cone. + http://www.optimization-online.org/DB_HTML/2015/10/5135.html + + .. [Rudolf] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh. + Bilinear optimality constraints for the cone of positive + polynomials. Mathematical Programming, Series B, 129 (2011) 5-31. + + EXAMPLES: + + Every transformation is Lyapunov-like on the trivial cone:: + + sage: K = Cone([(0,0)]) + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + And by duality, every transformation is Lyapunov-like on the + ambient space:: + + sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)]) + sage: K.is_full_space() + True + sage: M = MatrixSpace(K.lattice().base_field(), K.lattice_dim()) + sage: M.basis() == K.lyapunov_like_basis() + True + + However, in a trivial space, there are no non-trivial linear maps, + so there can be no Lyapunov-like basis:: + + sage: L = ToricLattice(0) + sage: K = Cone([], lattice=L) + sage: K.lyapunov_like_basis() + [] + + The Lyapunov-like transformations on the nonnegative orthant are + diagonal matrices:: + + sage: K = Cone([(1,)]) + sage: K.lyapunov_like_basis() + [[1]] + + sage: K = Cone([(1,0),(0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0] [0 0] + [0 0], [0 1] + ] + + sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)]) + sage: K.lyapunov_like_basis() + [ + [1 0 0] [0 0 0] [0 0 0] + [0 0 0] [0 1 0] [0 0 0] + [0 0 0], [0 0 0], [0 0 1] + ] + + Only the identity matrix is Lyapunov-like on the pyramids + defined by the one- and infinity-norms [Rudolf]_:: + + sage: l31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)]) + sage: l31.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + sage: l3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)]) + sage: l3infty.lyapunov_like_basis() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + + TESTS: + + The vectors `L(x)` and `s` are orthogonal for every pair `(x,s)` + in the :meth:`discrete_complementarity_set` of the cone:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: dcs = K.discrete_complementarity_set() + sage: LL = K.lyapunov_like_basis() + sage: ips = [ (L*x).inner_product(s) for (x,s) in dcs + ....: for L in LL ] + sage: sum(map(abs, ips)) + 0 + + The Lyapunov-like transformations on a cone and its dual are + transposes of one another. However, there's no reason to expect + that one basis will consist of transposes of the other:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=8) + sage: LL1 = K.lyapunov_like_basis() + sage: LL2 = [L.transpose() for L in K.dual().lyapunov_like_basis()] + sage: V = VectorSpace(K.lattice().base_field(), K.lattice_dim()^2) + sage: LL1_vecs = [ V(m.list()) for m in LL1 ] + sage: LL2_vecs = [ V(m.list()) for m in LL2 ] + sage: V.span(LL1_vecs) == V.span(LL2_vecs) + True + + The space of all Lyapunov-like transformations is a Lie algebra + and should therefore be closed under the lie bracket:: + + sage: set_random_seed() + sage: K = random_cone(max_ambient_dim=4) + sage: LL = K.lyapunov_like_basis() + sage: W = VectorSpace(K.lattice().base_field(), K.lattice_dim()**2) + sage: LL_W = W.span([ W(m.list()) for m in LL ]) + sage: brackets = [ W((L1*L2 - L2*L1).list()) for L1 in LL + ....: for L2 in LL ] + sage: all([ b in LL_W for b in brackets ]) + True + """ + # Matrices are not vectors in Sage, so we have to convert them + # to vectors explicitly before we can find a basis. We need these + # two values to construct the appropriate "long vector" space. + F = self.lattice().base_field() + n = self.lattice_dim() + + # These tensor products contain a basis for the orthogonal + # complement of the Lyapunov-like transformations on this cone. + tensor_products = [ s.tensor_product(x) + for (x,s) in self.discrete_complementarity_set() ] + + # Convert those tensor products to long vectors. + W = VectorSpace(F, n**2) + perp_vectors = [ W(tp.list()) for tp in tensor_products ] + + # Now find the Lyapunov-like transformations (as long vectors). + LL_vectors = W.span(perp_vectors).complement() + + # And finally convert the long vectors back to matrices. + M = MatrixSpace(F, n, n) + return [ M(v.list()) for v in LL_vectors.basis() ] def random_cone(lattice=None, min_ambient_dim=0, max_ambient_dim=None, diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 73fd2c6414e..3c2fe11daf6 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1168,11 +1168,11 @@ def _crossratio_matrix(p0, p1, p2): # UHP r""" Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking - the elements of `p` to `0`,`1`, and `\infty'. + the elements of `p` to `0`, `1`, and `\infty`. INPUT: - - a list of three distinct elements of three distinct elements + - a list of three distinct elements of `\mathbb{CP}^1` in affine coordinates; that is, each element must be a complex number, `\infty`, or symbolic. diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index 2e7b4c8391d..032d7fcaaa9 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -23,7 +23,9 @@ sage: -2*h Hyperplane -6*x - 4*y + 10*z + 14 sage: x, y, z - (Hyperplane x + 0*y + 0*z + 0, Hyperplane 0*x + y + 0*z + 0, Hyperplane 0*x + 0*y + z + 0) + (Hyperplane x + 0*y + 0*z + 0, + Hyperplane 0*x + y + 0*z + 0, + Hyperplane 0*x + 0*y + z + 0) See :mod:`sage.geometry.hyperplane_arrangement.hyperplane` for more functionality of the individual hyperplanes. @@ -340,9 +342,11 @@ from sage.misc.cachefunc import cached_method from sage.misc.misc import uniq from sage.matrix.constructor import matrix, vector +from sage.modules.free_module import VectorSpace from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane +from copy import copy class HyperplaneArrangementElement(Element): @@ -695,7 +699,6 @@ def intersection_poset(self): """ K = self.base_ring() from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace - from sage.modules.all import VectorSpace whole_space = AffineSubspace(0, VectorSpace(K, self.dimension())) L = [[whole_space]] active = True @@ -1928,7 +1931,112 @@ def varchenko_matrix(self, names='h'): v.set_immutable() return v + @cached_method + def matroid(self): + r""" + Return the matroid associated to ``self``. + + Let `A` denote a central hyperplane arrangement and `n_H` the + normal vector of some hyperplane `H \in A`. We define a matroid + `M_A` as the linear matroid spanned by `\{ n_H | H \in A \}`. + The matroid `M_A` is such that the lattice of flats of `M` is + isomorphic to the intersection lattice of `A` + (Proposition 3.6 in [RS]_). + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z) + sage: M = A.matroid(); M + Linear matroid of rank 3 on 7 elements represented over the Rational Field + + We check the lattice of flats is isomorphic to the + intersection lattice:: + + sage: f = sum([list(M.flats(i)) for i in range(M.rank()+1)], []) + sage: PF = Poset([f, lambda x,y: x < y]) + sage: PF.is_isomorphic(A.intersection_poset()) + True + """ + if not self.is_central(): + raise ValueError("the hyperplane arrangement must be central") + norms = [p.normal() for p in self] + from sage.matroids.constructor import Matroid + return Matroid(matrix=matrix(norms).transpose()) + + @cached_method + def minimal_generated_number(self): + r""" + Return the minimum `k` such that ``self`` is `k`-generated. + + Let `A` be a central hyperplane arrangement. Let `W_k` denote + the solution space of the linear system corresponding to the + linear dependencies among the hyperplanes of `A` of length at + most `k`. We say `A` is `k`-*generated* if + `\dim W_k = \operatorname{rank} A`. + Equivalently this says all dependencies forming the Orlik-Terao + ideal are generated by at most `k` hyperplanes. + + EXAMPLES: + + We construct Example 2.2 from [Vuz93]_:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.minimal_generated_number() + 3 + sage: B.minimal_generated_number() + 4 + + REFERENCES: + + .. [Vuz93] Sergey Yuzvinksy, + *The first two obstructions to the freeness of arrangements*, + Transactions of the American Mathematical Society, + Vol. 335, **1** (1993) pp. 231--244. + """ + V = VectorSpace(self.base_ring(), self.dimension()) + W = VectorSpace(self.base_ring(), self.n_hyperplanes()) + r = self.rank() + M = self.matroid() + norms = M.representation().columns() + circuits = M.circuits() + for i in range(2, self.n_hyperplanes()): + sol = [] + for d in circuits: + if len(d) > i: + continue + d = list(d) + dep = V.linear_dependence([norms[j] for j in d]) + w = W.zero().list() + for j,k in enumerate(d): + w[k] = dep[0][j] + sol.append(w) + mat = matrix(sol) + if mat.right_kernel().dimension() == r: + return i + return self.n_hyperplanes() + + def is_formal(self): + """ + Return if ``self`` is formal. + + A hyperplane arrangement is *formal* if it is 3-generated [Vuz93]_, + where `k`-generated is defined in :meth:`minimal_generated_number`. + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, 3*x+5*z, 3*x+4*y+5*z) + sage: B = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z, x+3*z, x+2*y+3*z) + sage: A.is_formal() + True + sage: B.is_formal() + False + """ + return self.minimal_generated_number() <= 3 class HyperplaneArrangements(Parent, UniqueRepresentation): """ diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 8f0eca94c9b..d0cc12f77b6 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -839,19 +839,19 @@ def _copy_faces(self, other, reverse=False): reflexive polytopes, faces of this polytope and its polar are in inclusion reversing bijection. - .. note:: + .. NOTE:: This function does not perform any checks that this operation makes sense. INPUT: - - ``other`` - another LatticePolytope, whose facial structure will be + - ``other`` -- another LatticePolytope, whose facial structure will be copied - - ``reverse`` - (default: False) if True, the facial structure of the - other polytope will be reversed, i.e. vertices will correspond to - facets etc. + - ``reverse`` -- (default: ``False``) if ``True``, the facial + structure of the other polytope will be reversed, + i.e. vertices will correspond to facets etc. TESTS:: @@ -1101,8 +1101,8 @@ def _pullback(self, data): INPUT: - - ``data`` - rational point or matrix of points (as columns) in the - ambient space + - ``data`` -- rational point or matrix of points (as columns) in the + ambient space OUTPUT: The same point(s) in the coordinates of the affine subspace space spanned by this polytope. @@ -5752,7 +5752,7 @@ def _palp_convert_permutation(permutation): PALP specifies a permutation group element by its domain. Furthermore, it only supports permutations of up to 62 objects and labels these by - `0 \dots 9', 'a \dots z', and 'A \dots Z'. + `0 \dots 9`, `a \dots z`, and `A \dots Z`. INPUT: @@ -5760,8 +5760,7 @@ def _palp_convert_permutation(permutation): OUTPUT: - A :class:`permutation group element - `. + A :class:`permutation group element `. EXAMPLES:: diff --git a/src/sage/geometry/linear_expression.py b/src/sage/geometry/linear_expression.py index 93e7d05cf4f..ca4773e0449 100644 --- a/src/sage/geometry/linear_expression.py +++ b/src/sage/geometry/linear_expression.py @@ -351,6 +351,18 @@ def change_ring(self, base_ring): return self return P.change_ring(base_ring)(self) + def __hash__(self): + r""" + TESTS:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: hash(L([0,1])) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self._coeffs) ^ hash(self._const) + def __cmp__(self, other): """ Compare two linear expressions. @@ -379,10 +391,7 @@ def __cmp__(self, other): False """ assert type(self) is type(other) and self.parent() is other.parent() # guaranteed by framework - c = cmp(self._coeffs, other._coeffs) - if c != 0: return c - c = cmp(self._const, other._const) - return c + return cmp(self._coeffs, other._coeffs) or cmp(self._const, other._const) def evaluate(self, point): """ diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 0d3fe99bad4..6419ad6736e 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -120,6 +120,32 @@ def __init__(self, parent, Vrep, Hrep, **kwds): else: self._init_empty_polyhedron() + def __hash__(self): + r""" + TESTS:: + + sage: K. = QuadraticField(2) + sage: p = Polyhedron(vertices=[(0,1,a),(3,a,5)], + ....: rays=[(a,2,3), (0,0,1)], + ....: base_ring=K) + sage: q = Polyhedron(vertices=[(3,a,5),(0,1,a)], + ....: rays=[(0,0,1), (a,2,3)], + ....: base_ring=K) + sage: hash(p) == hash(q) + True + """ + # TODO: find something better *but* fast + return hash((self.dim(), + self.ambient_dim(), + self.n_Hrepresentation(), + self.n_Vrepresentation(), + self.n_equations(), + self.n_facets(), + self.n_inequalities(), + self.n_lines(), + self.n_rays(), + self.n_vertices())) + def _sage_input_(self, sib, coerced): """ Return Sage command to reconstruct ``self``. @@ -1909,7 +1935,7 @@ def center(self): """ Return the average of the vertices. - See also :meth:`interior_point`. + See also :meth:`representative_point`. OUTPUT: @@ -2381,7 +2407,7 @@ def Minkowski_difference(self, other): return P.element_class(P, None, [new_ieqs, new_eqns]) def __sub__(self, other): - """ + r""" Implement minus binary operation Polyhedra are not a ring with respect to dilatation and diff --git a/src/sage/geometry/polyhedron/double_description.py b/src/sage/geometry/polyhedron/double_description.py index 3bff500b362..451e4638b90 100644 --- a/src/sage/geometry/polyhedron/double_description.py +++ b/src/sage/geometry/polyhedron/double_description.py @@ -628,7 +628,7 @@ def dim(self): return self._A.ncols() def __repr__(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py index 9d4c6bd3d7d..2425070aca6 100644 --- a/src/sage/geometry/polyhedron/double_description_inhomogeneous.py +++ b/src/sage/geometry/polyhedron/double_description_inhomogeneous.py @@ -232,7 +232,7 @@ def _init_Vrep(self, inequalities, equations): return self._pivot_inequalities(A) def _split_linear_subspace(self): - """ + r""" Split the linear subspace in a generator with `x_0\not=0` and the remaining generators with `x_0=0`. @@ -504,7 +504,7 @@ def is_trivial(ray): self.equations = self._linear_subspace.matrix().rows() def _repr_(self): - """ + r""" Return a string representation. OUTPUT: diff --git a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py index 96b2bc42eeb..f3fc6f54125 100644 --- a/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py +++ b/src/sage/geometry/polyhedron/lattice_euclidean_group_element.py @@ -116,7 +116,7 @@ def __call__(self, x): return v def _repr_(self): - """ + r""" Return a string representation EXAMPLES:: diff --git a/src/sage/geometry/polyhedron/palp_database.py b/src/sage/geometry/polyhedron/palp_database.py index 98a85712530..4fb51d9312a 100644 --- a/src/sage/geometry/polyhedron/palp_database.py +++ b/src/sage/geometry/polyhedron/palp_database.py @@ -134,7 +134,7 @@ def _palp_Popen(self): return Popen(["class.x", "-b2a", "-di", self._data_basename], stdout=PIPE) def _read_vertices(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -158,7 +158,7 @@ def _read_vertices(self, stdout, rows, cols): return m def _read_vertices_transposed(self, stdout, rows, cols): - """ + r""" Read vertex data from the PALP output pipe. OUTPUT: @@ -428,9 +428,9 @@ def __init__(self, h11, h21, data_basename=None, **kwds): TESTS:: - sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge - sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d - + sage: from sage.geometry.polyhedron.palp_database import Reflexive4dHodge + sage: Reflexive4dHodge(1,101) # optional - polytopes_db_4d + """ dim = 4 if data_basename is None: diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index 8ffaddec672..6b43c095f42 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -900,18 +900,19 @@ def base_rays(self, fiber, points): ((1),) """ quo = self.base_projection(fiber) - vertices = [] + vertices = set() for p in points: - v = vector(ZZ,quo(p)) + v = quo(p).vector() if v.is_zero(): continue - d = GCD_list(v.list()) - if d>1: - for i in range(0,v.degree()): + d = GCD_list(v.list()) + if d > 1: + v = v.__copy__() + for i in range(v.degree()): v[i] /= d - v.set_immutable() - vertices.append(v) - return tuple(uniq(vertices)) + v.set_immutable() + vertices.add(v) + return tuple(sorted(vertices)) @cached_method def has_IP_property(self): diff --git a/src/sage/geometry/polyhedron/representation.py b/src/sage/geometry/polyhedron/representation.py index b61b0caa2d5..3249ceb5184 100644 --- a/src/sage/geometry/polyhedron/representation.py +++ b/src/sage/geometry/polyhedron/representation.py @@ -135,9 +135,7 @@ def __cmp__(self, other): """ if not isinstance(other, PolyhedronRepresentation): return -1 - type_cmp = cmp(type(self), type(other)) - if (type_cmp != 0): return type_cmp - return cmp(self._vector, other._vector) + return cmp(type(self), type(other)) or cmp(self._vector, other._vector) def vector(self, base_ring=None): """ diff --git a/src/sage/graphs/asteroidal_triples.pyx b/src/sage/graphs/asteroidal_triples.pyx index 56bfe7d2ce6..fbe72d233ca 100644 --- a/src/sage/graphs/asteroidal_triples.pyx +++ b/src/sage/graphs/asteroidal_triples.pyx @@ -240,7 +240,7 @@ cdef list is_asteroidal_triple_free_C(int n, # We now search for an unseen vertex v = bitset_first_in_complement(seen) - while v!=-1: + while v != -1: # and add it to the queue waiting_list[0] = v waiting_beginning = 0 diff --git a/src/sage/graphs/base/dense_graph.pyx b/src/sage/graphs/base/dense_graph.pyx index f1abc3d4128..29bf399164d 100644 --- a/src/sage/graphs/base/dense_graph.pyx +++ b/src/sage/graphs/base/dense_graph.pyx @@ -102,18 +102,16 @@ from ``CGraph`` (for explanation, refer to the documentation there):: It also contains the following variables:: - cdef int radix_div_shift - cdef int radix_mod_mask cdef int num_longs cdef unsigned long *edges The array ``edges`` is a series of bits which are turned on or off, and due to this, dense graphs only support graphs without edge labels and with no multiple -edges. The ints ``radix_div_shift`` and ``radix_mod_mask`` are simply for doing -efficient division by powers of two, and ``num_longs`` stores the length of the -``edges`` array. Recall that this length reflects the number of available -vertices, not the number of "actual" vertices. For more details about this, -refer to the documentation for ``CGraph``. +edges. ``num_longs`` stores the length of the ``edges`` array. Recall that this +length reflects the number of available vertices, not the number of "actual" +vertices. For more details about this, refer to the documentation for +``CGraph``. + """ #******************************************************************************* @@ -125,6 +123,11 @@ refer to the documentation for ``CGraph``. include 'sage/data_structures/bitset.pxi' +from libc.string cimport memcpy + +cdef int radix = sizeof(unsigned long) * 8 # number of bits per 'unsigned long' +cdef int radix_mod_mask = radix - 1 # (assumes that radis is a power of 2) + cdef class DenseGraph(CGraph): """ Compiled dense graphs. @@ -151,7 +154,6 @@ cdef class DenseGraph(CGraph): for use in pickling. """ - def __cinit__(self, int nverts, int extra_vertices = 10, verts = None, arcs = None): """ Allocation and initialization happen in one place. @@ -167,35 +169,24 @@ cdef class DenseGraph(CGraph): """ if nverts == 0 and extra_vertices == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') - cdef int radix = sizeof(unsigned long) << 3 - self.radix_mod_mask = radix - 1 - cdef int i = 0 - while ((1)<> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - self.edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) - self.in_degrees = sage_malloc(total_verts * sizeof(int)) - self.out_degrees = sage_malloc(total_verts * sizeof(int)) + self.edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) + self.in_degrees = sage_calloc(total_verts, sizeof(int)) + self.out_degrees = sage_calloc(total_verts, sizeof(int)) if not self.edges or not self.in_degrees or not self.out_degrees: - if self.edges: sage_free(self.edges) - if self.in_degrees: sage_free(self.in_degrees) - if self.out_degrees: sage_free(self.out_degrees) + sage_free(self.edges) + sage_free(self.in_degrees) + sage_free(self.out_degrees) raise MemoryError - for i from 0 <= i < self.num_longs * total_verts: - self.edges[i] = 0 - for i from 0 <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + bitset_init(self.active_vertices, total_verts) bitset_set_first_n(self.active_vertices, self.num_verts) @@ -261,6 +252,7 @@ cdef class DenseGraph(CGraph): cdef int i, j if total_verts == 0: raise RuntimeError('Dense graphs must allocate space for vertices!') + cdef bitset_t bits cdef int min_verts, min_longs, old_longs = self.num_longs if total_verts < self.active_vertices.size: @@ -276,42 +268,27 @@ cdef class DenseGraph(CGraph): min_verts = self.active_vertices.size min_longs = self.num_longs - i = total_verts >> self.radix_div_shift - if total_verts & self.radix_mod_mask: - i += 1 - self.num_longs = i - if min_longs == -1: min_longs = self.num_longs + # self.num_longs = "ceil(total_verts/radix)" + self.num_longs = total_verts / radix + (0 != (total_verts & radix_mod_mask)) - cdef unsigned long *new_edges = sage_malloc(total_verts * self.num_longs * sizeof(unsigned long)) + if min_longs == -1: + min_longs = self.num_longs + # Resize of self.edges + cdef unsigned long *new_edges = sage_calloc(total_verts * self.num_longs, sizeof(unsigned long)) for i from 0 <= i < min_verts: - for j from 0 <= j < min_longs: - new_edges[i*self.num_longs + j] = self.edges[i*old_longs + j] - for j from min_longs <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 - for i from min_verts <= i < total_verts: - for j from 0 <= j < self.num_longs: - new_edges[i*self.num_longs + j] = 0 + memcpy(new_edges+i*self.num_longs, self.edges+i*old_longs, min_longs*sizeof(unsigned long)) + sage_free(self.edges) self.edges = new_edges - self.in_degrees = sage_realloc(self.in_degrees, total_verts * sizeof(int)) + self.in_degrees = sage_realloc(self.in_degrees , total_verts * sizeof(int)) self.out_degrees = sage_realloc(self.out_degrees, total_verts * sizeof(int)) - cdef int first_limb - cdef unsigned long zero_gate - if total_verts > self.active_vertices.size: - first_limb = (self.active_vertices.size >> self.radix_div_shift) - zero_gate = (1) << (self.active_vertices.size & self.radix_mod_mask) - zero_gate -= 1 - for i from 0 <= i < total_verts: - self.edges[first_limb] &= zero_gate - for j from first_limb < j < self.num_longs: - self.edges[j] = 0 - - for i from self.active_vertices.size <= i < total_verts: - self.in_degrees[i] = 0 - self.out_degrees[i] = 0 + for i in range(self.active_vertices.size, total_verts): + self.in_degrees[i] = 0 + self.out_degrees[i] = 0 + bitset_realloc(self.active_vertices, total_verts) ################################### @@ -326,8 +303,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if not self.edges[place] & word: self.in_degrees[v] += 1 self.out_degrees[u] += 1 @@ -373,9 +350,9 @@ cdef class DenseGraph(CGraph): 1 -- True """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) - return (self.edges[place] & word) >> (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) + return (self.edges[place] & word) >> (v & radix_mod_mask) cpdef bint has_arc(self, int u, int v) except -1: """ @@ -409,8 +386,8 @@ cdef class DenseGraph(CGraph): u, v -- non-negative integers, must be in self """ - cdef int place = (u * self.num_longs) + (v >> self.radix_div_shift) - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = (u * self.num_longs) + (v / radix) + cdef unsigned long word = (1) << (v & radix_mod_mask) if self.edges[place] & word: self.in_degrees[v] -= 1 self.out_degrees[u] -= 1 @@ -444,6 +421,43 @@ cdef class DenseGraph(CGraph): self.check_vertex(v) self.del_arc_unsafe(u,v) + def complement(self): + r""" + Replaces the graph with its complement + + .. NOTE:: + + Assumes that the graph has no loop. + + EXAMPLE:: + + sage: from sage.graphs.base.dense_graph import DenseGraph + sage: G = DenseGraph(5) + sage: G.add_arc(0,1) + sage: G.has_arc(0,1) + True + sage: G.complement() + sage: G.has_arc(0,1) + False + """ + cdef int num_arcs_old = self.num_arcs + + # The following cast assumes that mp_limb_t is an unsigned long. + # (this assumption is already made in bitset.pxi) + cdef unsigned long * active_vertices_bitset + active_vertices_bitset = self.active_vertices.bits + + cdef int i,j + for i in range(self.active_vertices.size): + if bitset_in(self.active_vertices,i): + self.add_arc_unsafe(i,i) + for j in range(self.num_longs): # the actual job + self.edges[i*self.num_longs+j] ^= active_vertices_bitset[j] + self.in_degrees[i] = self.num_verts-self.in_degrees[i] + self.out_degrees[i] = self.num_verts-self.out_degrees[i] + + self.num_arcs = self.num_verts*(self.num_verts-1) - num_arcs_old + ################################### # Neighbor functions ################################### @@ -527,8 +541,8 @@ cdef class DenseGraph(CGraph): there were more """ - cdef int place = v >> self.radix_div_shift - cdef unsigned long word = (1) << (v & self.radix_mod_mask) + cdef int place = v / radix + cdef unsigned long word = (1) << (v & radix_mod_mask) cdef int i, num_nbrs = 0 for i from 0 <= i < self.active_vertices.size: if self.edges[place + i*self.num_longs] & word: diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 3abc113977f..d7e8f81a587 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -349,7 +349,7 @@ class DiGraph(GenericGraph): [ 0 1 -1] [ -1 0 -1/2] [ 1 1/2 0] - sage: G = DiGraph(M,sparse=True); G + sage: G = DiGraph(M,sparse=True,weighted=True); G Digraph on 3 vertices sage: G.weighted() True @@ -676,104 +676,20 @@ def __init__(self, data=None, pos=None, loops=None, format=None, # At this point, format has been set. We build the graph if format == 'dig6': - if weighted is None: weighted = False - if not isinstance(data, str): - raise ValueError('If input format is dig6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_dig6(s, n) - expected = n**2 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + if weighted is None: self._weighted = False self.allow_loops(True if loops else False,check=False) self.allow_multiple_edges(True if multiedges else False,check=False) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(n): - if m[k] == '1': - self._backend.add_edge(i, j, None, True) - k += 1 - elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False + from graph_input import from_dig6 + from_dig6(self, data) - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + elif format == 'adjacency_matrix': + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(True if loops else False,check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - positions = [] - for c in data.columns(): - NZ = c.nonzero_positions() - if len(NZ) != 2: - msg += "There must be two nonzero entries (-1 & 1) per column." - raise ValueError(msg) - L = sorted(set(c.list())) - if L != [-1,0,1]: - msg += "Each column represents an edge: -1 goes to 1." - raise ValueError(msg) - if c[NZ[0]] == -1: - positions.append(tuple(NZ)) - else: - positions.append((NZ[1],NZ[0])) - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = ( len(set(positions)) < total ) - self.allow_loops(True if loops else False,check=False) - self.allow_multiple_edges(multiedges,check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) + from graph_input import from_oriented_incidence_matrix + from_oriented_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'DiGraph': if loops is None: loops = data.allows_loops() elif not loops and data.has_loops(): @@ -801,66 +717,14 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_vertices(data[0]) self.add_edges((u,v) for u in data[0] for v in data[0] if f(u,v)) elif format == 'dict_of_dicts': - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set(data.keys()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: loops = False - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if v not in verts: verts.add(v) - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: - multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multidigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - self.add_vertices(verts) + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if multiedges: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,labels in Nu.iteritems() for l in labels) - else: - self.add_edges((u,v,l) for u,Nu in data.iteritems() for v,l in Nu.iteritems()) elif format == 'dict_of_lists': - # convert to a dict of lists if not already one - if not all(isinstance(data[u], list) for u in data): - data = {u: list(v) for u,v in data.iteritems()} - - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The digraph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False - - if weighted is None: weighted = False + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if not multiedges and any(len(set(neighb)) != len(neighb) for neighb in data.itervalues()): - if multiedges is False: - uv = next((u,v) for u,neighb in data.iteritems() for v in neighb if neighb.count(v) > 1) - raise ValueError("Non-multidigraph got several edges (%s,%s)"%(u,v)) - multiedges = True - if multiedges is None: - multiedges = False - self.allow_multiple_edges(multiedges,check=False) - self.allow_loops(loops,check=False) - verts = set().union(data.keys(),*data.values()) - self.add_vertices(verts) - self.add_edges((u,v) for u,Nu in data.iteritems() for v in Nu) elif format == 'NX': # adjust for empty dicts instead of None in NetworkX default edge labels if convert_empty_dict_labels_to_None is None: @@ -943,7 +807,9 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if data_structure == "static_sparse": from sage.graphs.base.static_sparse_backend import StaticSparseBackend - ib = StaticSparseBackend(self, loops = loops, multiedges = multiedges) + ib = StaticSparseBackend(self, + loops = self.allows_loops(), + multiedges = self.allows_multiple_edges()) self._backend = ib self._immutable = True diff --git a/src/sage/graphs/generators/classical_geometries.py b/src/sage/graphs/generators/classical_geometries.py index cae8a1e9e30..33ebf67da74 100644 --- a/src/sage/graphs/generators/classical_geometries.py +++ b/src/sage/graphs/generators/classical_geometries.py @@ -21,6 +21,8 @@ from math import sin, cos, pi from sage.graphs.graph import Graph from sage.graphs import graph +from sage.rings.arith import is_prime_power +from sage.rings.finite_rings.constructor import FiniteField def SymplecticPolarGraph(d, q, algorithm=None): r""" @@ -87,7 +89,6 @@ def SymplecticPolarGraph(d, q, algorithm=None): G = _polar_graph(d, q, libgap.SymplecticGroup(d, q)) elif algorithm == None: # faster for small (q<4) fields - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.schemes.projective.projective_space import ProjectiveSpace from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix @@ -187,7 +188,6 @@ def AffineOrthogonalPolarGraph(d,q,sign="+"): s = 0 from sage.interfaces.gap import gap - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap @@ -287,7 +287,6 @@ def _orthogonal_polar_graph(m, q, sign="+", point_type=[0]): """ from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.matrix.constructor import Matrix from sage.libs.gap.libgap import libgap @@ -491,7 +490,6 @@ def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): """ from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') @@ -635,14 +633,12 @@ def UnitaryPolarGraph(m, q, algorithm="gap"): elif algorithm == None: # slow on large examples from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector - from __builtin__ import sum as psum Fq = FiniteField(q**2, 'a') PG = map(vector, ProjectiveSpace(m - 1, Fq)) map(lambda x: x.set_immutable(), PG) def P(x,y): - return psum(map(lambda j: x[j]*y[m-1-j]**q, xrange(m)))==0 + return sum(map(lambda j: x[j]*y[m-1-j]**q, xrange(m)))==0 V = filter(lambda x: P(x,x), PG) G = Graph([V,lambda x,y: # bottleneck is here, of course: @@ -697,7 +693,6 @@ def NonisotropicUnitaryPolarGraph(m, q): Disc. Math. 13(1975), pp 357--381. http://dx.doi.org/10.1016/0012-365X(75)90057-6 """ - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') @@ -743,7 +738,7 @@ def UnitaryDualPolarGraph(m, q): EXAMPLES: - The point graph of a generalized quadrangle of order (8,4):: + The point graph of a generalized quadrangle (see [GQwiki]_, [PT09]_) of order (8,4):: sage: G = graphs.UnitaryDualPolarGraph(5,2); G # long time Unitary Dual Polar Graph DU(5, 2); GQ(8, 4): Graph on 297 vertices @@ -881,12 +876,10 @@ def TaylorTwographDescendantSRG(q, clique_partition=None): ... ValueError: q must be an odd prime power """ - from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0 or p==2: raise ValueError('q must be an odd prime power') from sage.schemes.projective.projective_space import ProjectiveSpace - from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.rings.finite_rings.integer_mod import mod from __builtin__ import sum @@ -941,3 +934,171 @@ def TaylorTwographSRG(q): G.seidel_switching(sum(l[:(q**2+1)/2],[])) G.name("Taylor two-graph SRG") return G + +def AhrensSzekeresGeneralizedQuadrangleGraph(q, dual=False): + r""" + Return the collinearity graph of the generalized quadrangle `AS(q)`, or of its dual + + Let `q` be an odd prime power. `AS(q)` is a generalized quadrangle [GQwiki]_ of + order `(q-1,q+1)`, see 3.1.5 in [PT09]_. Its points are elements + of `F_q^3`, and lines are sets of size `q` of the form + + * `\{ (\sigma, a, b) \mid \sigma\in F_q \}` + * `\{ (a, \sigma, b) \mid \sigma\in F_q \}` + * `\{ (c \sigma^2 - b \sigma + a, -2 c \sigma + b, \sigma) \mid \sigma\in F_q \}`, + + where `a`, `b`, `c` are arbitrary elements of `F_q`. + + INPUT: + + - ``q`` -- a power of an odd prime number + + - ``dual`` -- if ``False`` (default), return the collinearity graph of `AS(q)`. + Otherwise return the collinearity graph of the dual `AS(q)`. + + EXAMPLES:: + + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5); g + AS(5); GQ(4, 6): Graph on 125 vertices + sage: g.is_strongly_regular(parameters=True) + (125, 28, 3, 7) + sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5,dual=True); g + AS(5)*; GQ(6, 4): Graph on 175 vertices + sage: g.is_strongly_regular(parameters=True) + (175, 30, 5, 5) + + REFERENCE: + + .. [GQwiki] `Generalized quadrangle + `__ + + .. [PT09] S. Payne, J. A. Thas. + Finite generalized quadrangles. + European Mathematical Society, + 2nd edition, 2009. + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + p, k = is_prime_power(q,get_data=True) + if k==0 or p==2: + raise ValueError('q must be an odd prime power') + F = FiniteField(q, 'a') + L = [] + for a in F: + for b in F: + L.append(tuple(map(lambda s: (s, a, b), F))) + L.append(tuple(map(lambda s: (a, s, b), F))) + for c in F: + L.append(tuple(map(lambda s: (c*s**2 - b*s + a, -2*c*s + b, s), F))) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('AS('+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('AS('+str(q)+'); GQ'+str((q-1,q+1))) + return G + +def T2starGeneralizedQuadrangleGraph(q, dual=False, hyperoval=None, field=None, check_hyperoval=True): + r""" + Return the collinearity graph of the generalized quadrangle `T_2^*(q)`, or of its dual + + Let `q=2^k` and `\Theta=PG(3,q)`. `T_2^*(q)` is a generalized quadrangle [GQwiki]_ + of order `(q-1,q+1)`, see 3.1.3 in [PT09]_. Fix a plane `\Pi \subset \Theta` and a + `hyperoval `__ + `O \subset \Pi`. The points of `T_2^*(q):=T_2^*(O)` are the points of `\Theta` + outside `\Pi`, and the lines are the lines of `\Theta` outside `\Pi` + that meet `\Pi` in a point of `O`. + + INPUT: + + - ``q`` -- a power of two + + - ``dual`` -- if ``False`` (default), return the graph of `T_2^*(O)`. + Otherwise return the graph of the dual `T_2^*(O)`. + + - ``hyperoval`` -- a hyperoval (i.e. a complete 2-arc; a set of points in the plane + meeting every line in 0 or 2 points) in the plane of points with 0th coordinate + 0 in `PG(3,q)` over the field ``field``. Each point of ``hyperoval`` must be a length 4 + vector over ``field`` with 1st non-0 coordinate equal to 1. By default, ``hyperoval`` and + ``field`` are not specified, and constructed on the fly. In particular, ``hyperoval`` + we build is the classical one, i.e. a conic with the point of intersection of its + tangent lines. + + - ``field`` -- an instance of a finite field of order `q`, must be provided + if ``hyperoval`` is provided. + + - ``check_hyperoval`` -- (default: ``True``) if ``True``, + check ``hyperoval`` for correctness. + + + EXAMPLES: + + using the built-in construction:: + + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4,dual=True); g + T2*(O,4)*; GQ(5, 3): Graph on 96 vertices + sage: g.is_strongly_regular(parameters=True) + (96, 20, 4, 4) + + supplying your own hyperoval:: + + sage: F=GF(4,'b') + sage: O=[vector(F,(0,0,0,1)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: g=graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: F=GF(4,'b') # repeating a point... + sage: O=[vector(F,(0,1,0,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval size + sage: O=[vector(F,(0,1,1,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) + sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) + Traceback (most recent call last): + ... + RuntimeError: incorrect hyperoval + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from sage.combinat.designs.block_design import ProjectiveGeometryDesign as PG + from sage.modules.free_module_element import free_module_element as vector + + p, k = is_prime_power(q,get_data=True) + if k==0 or p!=2: + raise ValueError('q must be a power of 2') + if field is None: + F = FiniteField(q, 'a') + else: + F = field + + Theta = PG(3, 1, F, point_coordinates=1) + Pi = set(filter(lambda x: x[0]==F.zero(), Theta.ground_set())) + if hyperoval is None: + O = filter(lambda x: x[1]+x[2]*x[3]==0 or (x[1]==1 and x[2]==0 and x[3]==0), Pi) + O = set(O) + else: + map(lambda x: x.set_immutable(), hyperoval) + O = set(hyperoval) + if check_hyperoval: + if len(O) != q+2: + raise RuntimeError("incorrect hyperoval size") + for L in Theta.blocks(): + if set(L).issubset(Pi): + if not len(O.intersection(L)) in [0,2]: + raise RuntimeError("incorrect hyperoval") + L = map(lambda z: filter(lambda y: not y in O, z), + filter(lambda x: len(O.intersection(x)) == 1, Theta.blocks())) + if dual: + G = IncidenceStructure(L).intersection_graph() + G.name('T2*(O,'+str(q)+')*; GQ'+str((q+1,q-1))) + else: + G = IncidenceStructure(L).dual().intersection_graph() + G.name('T2*(O,'+str(q)+'); GQ'+str((q-1,q+1))) + return G diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 05efc1dc10b..303383ef39f 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -46,7 +46,7 @@ def JohnsonGraph(n, k): sage: g.is_vertex_transitive() True - The complement of the Johnson graph `J(n,2)` is isomorphic to the Knesser + The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser Graph `K(n,2)`. In paritcular the complement of `J(5,2)` is isomorphic to the Petersen graph. :: diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 84938f5c7eb..0c02a3aa719 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -14566,10 +14566,10 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, [4, 17, 16, 12, 13, 9] sage: D.shortest_path(4, 9, algorithm='BFS') [4, 3, 2, 1, 8, 9] - sage: D.shortest_path(4, 9, algorithm='Dijkstra_NetworkX') - [4, 3, 2, 1, 8, 9] - sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid_NetworkX') - [4, 3, 2, 1, 8, 9] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_NetworkX') + [4, 3, 2, 1, 8] + sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') + [4, 3, 2, 1, 8] sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') [4, 3, 19, 0, 10, 9] sage: D.shortest_path(5, 5) @@ -16291,8 +16291,7 @@ def add_path(self, vertices): vert1 = v def complement(self): - """ - Returns the complement of the (di)graph. + """Returns the complement of the (di)graph. The complement of a graph has the same vertices, but exactly those edges that are not in the original graph. This is not well defined @@ -16320,7 +16319,10 @@ def complement(self): sage: G.complement() Traceback (most recent call last): ... - TypeError: complement not well defined for (di)graphs with multiple edges + ValueError: This method is not known to work on graphs with + multiedges. Perhaps this method can be updated to handle them, but + in the meantime if you want to use it please disallow multiedges + using allow_multiple_edges(). TESTS: @@ -16338,17 +16340,14 @@ def complement(self): Graph on 10 vertices """ - if self.has_multiple_edges(): - raise TypeError('complement not well defined for (di)graphs with multiple edges') self._scream_if_not_simple() - G = copy(self) - G.delete_edges(G.edges()) + + G = self.copy(data_structure='dense') + G._backend.c_graph()[0].complement() + if self.name(): G.name("complement({})".format(self.name())) - for u in self: - for v in self: - if not self.has_edge(u,v): - G.add_edge(u,v) + if getattr(self, '_immutable', False): return G.copy(immutable=True) return G diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 56282c0c86e..04ad91b9587 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1,146 +1,11 @@ +# -*- coding: utf-8 -*- r""" Undirected graphs This module implements functions and operations involving undirected graphs. -**Graph basic operations:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.write_to_eps` | Writes a plot of the graph to ``filename`` in ``eps`` format. - :meth:`~Graph.to_undirected` | Since the graph is already undirected, simply returns a copy of itself. - :meth:`~Graph.to_directed` | Returns a directed version of the graph. - :meth:`~Graph.sparse6_string` | Returns the sparse6 representation of the graph as an ASCII string. - :meth:`~Graph.graph6_string` | Returns the graph6 representation of the graph as an ASCII string. - :meth:`~Graph.bipartite_sets` | Returns `(X,Y)` where X and Y are the nodes in each bipartite set of graph. - :meth:`~Graph.bipartite_color` | Returns a dictionary with vertices as the keys and the color class as the values. - :meth:`~Graph.is_directed` | If ``self`` is undirected, returns False. - :meth:`~Graph.join` | Returns the join of ``self`` and ``other``. - - -**Distances:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.centrality_degree` | Returns the degree centrality - - -**Graph properties:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.is_asteroidal_triple_free` | Tests whether the current graph is asteroidal triple free. - :meth:`~Graph.is_prime` | Tests whether the current graph is prime. - :meth:`~Graph.is_split` | Returns ``True`` if the graph is a Split graph, ``False`` otherwise. - :meth:`~Graph.is_triangle_free` | Returns whether ``self`` is triangle-free. - :meth:`~Graph.is_bipartite` | Returns True if graph G is bipartite, False if not. - :meth:`~Graph.is_line_graph` | Tests wether the graph is a line graph. - :meth:`~Graph.is_odd_hole_free` | Tests whether ``self`` contains an induced odd hole. - :meth:`~Graph.is_even_hole_free` | Tests whether ``self`` contains an induced even hole. - :meth:`~Graph.is_cartesian_product` | Tests whether ``self`` is a cartesian product of graphs. - :meth:`~Graph.is_long_hole_free` | Tests whether ``self`` contains an induced cycle of length at least 5. - :meth:`~Graph.is_long_antihole_free` | Tests whether ``self`` contains an induced anticycle of length at least 5. - :meth:`~Graph.is_weakly_chordal` | Tests whether ``self`` is weakly chordal. - :meth:`~Graph.is_strongly_regular` | Tests whether ``self`` is strongly regular. - :meth:`~Graph.is_distance_regular` | Tests whether ``self`` is distance-regular. - :meth:`~Graph.is_tree` | Return True if the graph is a tree. - :meth:`~Graph.is_forest` | Return True if the graph is a forest, i.e. a disjoint union of trees. - :meth:`~Graph.is_overfull` | Tests whether the current graph is overfull. - :meth:`~Graph.odd_girth` | Returns the odd girth of ``self``. - :meth:`~Graph.is_edge_transitive` | Returns true if ``self`` is edge-transitive. - :meth:`~Graph.is_arc_transitive` | Returns true if ``self`` is arc-transitive. - :meth:`~Graph.is_half_transitive` | Returns true if ``self`` is a half-transitive graph. - :meth:`~Graph.is_semi_symmetric` | Returns true if ``self`` is a semi-symmetric graph. - -**Connectivity, orientations, trees:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.gomory_hu_tree` | Returns a Gomory-Hu tree of ``self``. - :meth:`~Graph.minimum_outdegree_orientation` | Returns an orientation of ``self`` with the smallest possible maximum outdegree - :meth:`~Graph.bounded_outdegree_orientation` | Computes an orientation of ``self`` such that every vertex `v` has out-degree less than `b(v)` - :meth:`~Graph.strong_orientation` | Returns a strongly connected orientation of the current graph. - :meth:`~Graph.degree_constrained_subgraph` | Returns a degree-constrained subgraph. - :meth:`~Graph.bridges` | Returns the list of all bridges. - :meth:`~Graph.spanning_trees` | Returns the list of all spanning trees. - :meth:`~Graph.random_spanning_tree` | Returns a random spanning tree. - -**Clique-related methods:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.clique_complex` | Returns the clique complex of ``self`` - :meth:`~Graph.cliques_containing_vertex` | Returns the cliques containing each vertex - :meth:`~Graph.cliques_vertex_clique_number` | Returns a dictionary of sizes of the largest maximal cliques containing each vertex - :meth:`~Graph.cliques_get_clique_bipartite` | Returns a bipartite graph constructed such that maximal cliques are the right vertices and the left vertices are retained from the given graph - :meth:`~Graph.cliques_get_max_clique_graph` | Returns a graph constructed with maximal cliques as vertices, and edges between maximal cliques sharing vertices. - :meth:`~Graph.cliques_number_of` | Returns a dictionary of the number of maximal cliques containing each vertex, keyed by vertex. - :meth:`~Graph.clique_number` | Returns the order of the largest clique of the graph. - :meth:`~Graph.clique_maximum` | Returns the vertex set of a maximal order complete subgraph. - :meth:`~Graph.cliques_maximum` | Returns the list of all maximum cliques - :meth:`~Graph.cliques_maximal` | Returns the list of all maximal cliques - :meth:`~Graph.clique_polynomial` | Returns the clique polynomial - -**Algorithmically hard stuff:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.vertex_cover` | Returns a minimum vertex cover. - :meth:`~Graph.independent_set` | Returns a maximum independent set. - :meth:`~Graph.topological_minor` | Returns a topological `H`-minor of ``self`` if one exists. - :meth:`~Graph.convexity_properties` | Returns a ``ConvexityProperties`` object corresponding to ``self``. - :meth:`~Graph.matching_polynomial` | Computes the matching polynomial. - :meth:`~Graph.rank_decomposition` | Returns an rank-decomposition of ``self`` achieving optiml rank-width. - :meth:`~Graph.minor` | Returns the vertices of a minor isomorphic to `H`. - :meth:`~Graph.independent_set_of_representatives` | Returns an independent set of representatives. - :meth:`~Graph.coloring` | Returns the first (optimal) proper vertex-coloring found. - :meth:`~Graph.has_homomorphism_to` | Checks whether there is a morphism between two graphs. - :meth:`~Graph.chromatic_number` | Returns the minimal number of colors needed to color the vertices. - :meth:`~Graph.chromatic_polynomial` | Returns the chromatic polynomial. - :meth:`~Graph.chromatic_symmetric_function` | Return the chromatic symmetric function. - :meth:`~Graph.tutte_polynomial` | Returns the Tutte polynomial. - :meth:`~Graph.is_perfect` | Tests whether the graph is perfect. - :meth:`~Graph.treewidth` | Computes the tree-width and provides a decomposition. - - -**Leftovers:** - -.. csv-table:: - :class: contentstable - :widths: 30, 70 - :delim: | - - :meth:`~Graph.cores` | Returns the core number for each vertex in an ordered list. - :meth:`~Graph.matching` | Returns a maximum weighted matching of the graph - :meth:`~Graph.fractional_chromatic_index` | Computes the fractional chromatic index. - :meth:`~Graph.lovasz_theta` | Returns the Lovasz number (a.k.a theta). - :meth:`~Graph.kirchhoff_symanzik_polynomial` | Returns the Kirchhoff-Symanzik polynomial. - :meth:`~Graph.modular_decomposition` | Returns the modular decomposition. - :meth:`~Graph.maximum_average_degree` | Returns the Maximum Average Degree (MAD). - :meth:`~Graph.two_factor_petersen` | Returns a decomposition of the graph into 2-factors. - :meth:`~Graph.ihara_zeta_function_inverse` | Returns the inverse of the zeta function. - :meth:`~Graph.seidel_switching` | Returns Seidel switching w.r.t. a subset of vertices. - :meth:`~Graph.seidel_adjacency_matrix` | Returns the Seidel adjacency matrix of ``self``. - :meth:`~Graph.twograph` | Returns :class:`two-graph ` of ``self``. +{INDEX_OF_METHODS} AUTHORS: @@ -552,7 +417,7 @@ from sage.graphs.digraph import DiGraph from sage.graphs.independent_sets import IndependentSets from sage.combinat.combinatorial_map import combinatorial_map - +from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index class Graph(GenericGraph): r""" @@ -898,9 +763,7 @@ class Graph(GenericGraph): sage: Graph(Matrix([[1],[1],[1]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(Matrix([[1],[1],[0]])) Graph on 3 vertices @@ -918,9 +781,7 @@ class Graph(GenericGraph): sage: Graph(M) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1] in column 2 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1] in column 2 Check that :trac:`9714` is fixed:: @@ -1140,9 +1001,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, sage: Graph(matrix([[1,1],[1,1],[1,0]])) Traceback (most recent call last): ... - ValueError: Non-symmetric or non-square matrix assumed to be an - incidence matrix: There must be one or two nonzero entries per - column. Got entries [1, 1, 1] in column 0 + ValueError: There must be one or two nonzero entries per column in an incidence matrix. Got entries [1, 1, 1] in column 0 sage: Graph(matrix([[3,1,1],[0,1,1]])) Traceback (most recent call last): ... @@ -1150,7 +1009,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, to 2, but column 0 does not """ GenericGraph.__init__(self) - msg = '' + from sage.structure.element import is_Matrix if sparse is False: @@ -1201,7 +1060,6 @@ def __init__(self, data=None, pos=None, loops=None, format=None, format = 'adjacency_matrix' else: format = 'incidence_matrix' - msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: " if format is None and isinstance(data, Graph): format = 'Graph' from sage.graphs.all import DiGraph @@ -1267,189 +1125,32 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if weighted is None: weighted = False self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) - if not isinstance(data, str): - raise ValueError('If input format is graph6, then data must be a string.') - n = data.find('\n') - if n == -1: - n = len(data) - ss = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(ss) - m = generic_graph_pyx.binary_string_from_graph6(s, n) - expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 - if len(m) > expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) - elif len(m) < expected: - raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) - self.add_vertices(range(n)) - k = 0 - for i in xrange(n): - for j in xrange(i): - if m[k] == '1': - self._backend.add_edge(i, j, None, False) - k += 1 + from graph_input import from_graph6 + from_graph6(self, data) elif format == 'sparse6': if weighted is None: weighted = False self.allow_loops(False if loops is False else True, check=False) self.allow_multiple_edges(False if multiedges is False else True, check=False) - from math import ceil, floor - from sage.misc.functional import log - n = data.find('\n') - if n == -1: - n = len(data) - s = data[:n] - n, s = generic_graph_pyx.length_and_string_from_graph6(s[1:]) - if n == 0: - edges = [] - else: - k = int(ceil(log(n,2))) - ords = [ord(i) for i in s] - if any(o > 126 or o < 63 for o in ords): - raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) - bits = ''.join([generic_graph_pyx.int_to_binary_string(o-63).zfill(6) for o in ords]) - b = [] - x = [] - for i in xrange(int(floor(len(bits)/(k+1)))): - b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) - x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) - v = 0 - edges = [] - for i in xrange(len(b)): - if b[i] == 1: - v += 1 - if x[i] > v: - v = x[i] - else: - if v < n: - edges.append((x[i],v)) - self.add_vertices(range(n)) - self.add_edges(edges) + from graph_input import from_sparse6 + from_sparse6(self, data) + elif format == 'adjacency_matrix': - assert is_Matrix(data) - # note: the adjacency matrix might be weighted and hence not - # necessarily consists of integers - if not weighted and data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - if weighted is False: - raise ValueError("Non-weighted graph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True + from graph_input import from_adjacency_matrix + from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if not weighted and any(e < 0 for e in entries): - if weighted is False: - raise ValueError("Non-weighted digraph's"+ - " adjacency matrix must have only nonnegative"+ - " integer entries") - weighted = True - if multiedges is None: multiedges = False - if weighted is None: - weighted = False - - if multiedges is None: - multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) - - if not loops and any(data[i,i] for i in xrange(data.nrows())): - if loops is False: - raise ValueError("Non-looped digraph's adjacency"+ - " matrix must have zeroes on the diagonal.") - loops = True - if loops is None: - loops = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - e = [] - if weighted: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j,data[i][j])) - elif multiedges: - for i,j in data.nonzero_positions(): - if i <= j: - e += [(i,j)]*int(data[i][j]) - else: - for i,j in data.nonzero_positions(): - if i <= j: - e.append((i,j)) - self.add_edges(e) elif format == 'incidence_matrix': - assert is_Matrix(data) - - oriented = any(data[pos] < 0 for pos in data.nonzero_positions(copy=False)) - - positions = [] - for i in range(data.ncols()): - NZ = data.nonzero_positions_in_column(i) - if len(NZ) == 1: - if oriented: - raise ValueError("Column {} of the (oriented) incidence " - "matrix contains only one nonzero value".format(i)) - elif data[NZ[0],i] != 2: - raise ValueError("Each column of a non-oriented incidence " - "matrix must sum to 2, but column {} does not".format(i)) - if loops is None: - loops = True - positions.append((NZ[0],NZ[0])) - elif len(NZ) != 2 or \ - (oriented and not ((data[NZ[0],i] == +1 and data[NZ[1],i] == -1) or \ - (data[NZ[0],i] == -1 and data[NZ[1],i] == +1))) or \ - (not oriented and (data[NZ[0],i] != 1 or data[NZ[1],i] != 1)): - msg += "There must be one or two nonzero entries per column. " - msg += "Got entries {} in column {}".format([data[j,i] for j in NZ], i) - raise ValueError(msg) - else: - positions.append(tuple(NZ)) + from graph_input import from_incidence_matrix + from_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) - if weighted is None: weighted = False - if multiedges is None: - total = len(positions) - multiedges = (len(set(positions)) < total ) - self.allow_loops(False if loops is None else loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(range(data.nrows())) - self.add_edges(positions) elif format == 'seidel_adjacency_matrix': - assert is_Matrix(data) - if data.base_ring() != ZZ: - try: - data = data.change_ring(ZZ) - except TypeError: - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have only 0,1,-1 integer entries") - - if data.is_sparse(): - entries = set(data[i,j] for i,j in data.nonzero_positions()) - else: - entries = set(data.list()) - - if any(e < -1 or e > 1 for e in entries): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have only 0,1,-1 integer entries") - if any(i==j for i,j in data.nonzero_positions()): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " have 0s on the main diagonal") - if not data.is_symmetric(): - raise ValueError("Graph's Seidel adjacency matrix must"+ - " be symmetric") multiedges = False weighted = False loops = False self.allow_loops(False) self.allow_multiple_edges(False) - self.add_vertices(range(data.nrows())) - e = [] - for i,j in data.nonzero_positions(): - if i <= j and data[i,j] < 0: - e.append((i,j)) - self.add_edges(e) + from graph_input import from_seidel_adjacency_matrix + from_seidel_adjacency_matrix(self, data) elif format == 'Graph': if loops is None: loops = data.allows_loops() if multiedges is None: multiedges = data.allows_multiple_edges() @@ -1508,90 +1209,14 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_edges(e for e in combinations(verts,2) if f(*e)) self.add_edges((v,v) for v in verts if f(v,v)) elif format == 'dict_of_dicts': - # adjust for empty dicts instead of None in NetworkX default edge labels - if convert_empty_dict_labels_to_None is None: - convert_empty_dict_labels_to_None = (format == 'NX') - - if not all(isinstance(data[u], dict) for u in data): - raise ValueError("Input dict must be a consistent format.") - - if not loops and any(u in neighb for u,neighb in data.iteritems()): - if loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - loops = True - if loops is None: - loops = False + from graph_input import from_dict_of_dicts + from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, + convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) - if weighted is None: weighted = False - for u in data: - for v in data[u]: - if hash(u) > hash(v): - if v in data and u in data[v]: - if data[u][v] != data[v][u]: - raise ValueError("Dict does not agree on edge (%s,%s)"%(u,v)) - continue - if multiedges is not False and not isinstance(data[u][v], list): - if multiedges is None: multiedges = False - if multiedges: - raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") - if multiedges is None and len(data) > 0: - multiedges = True - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - verts = set().union(data.keys(), *data.values()) - self.add_vertices(verts) - if convert_empty_dict_labels_to_None: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v] if data[u][v] != {} else None,False) - else: - for u in data: - for v in data[u]: - if hash(u) <= hash(v) or v not in data or u not in data[v]: - if multiedges: - for l in data[u][v]: - self._backend.add_edge(u,v,l,False) - else: - self._backend.add_edge(u,v,data[u][v],False) elif format == 'dict_of_lists': - if not all(isinstance(data[u], list) for u in data): - raise ValueError("Input dict must be a consistent format.") - - verts = set().union(data.keys(),*data.values()) - if loops is None or loops is False: - for u in data: - if u in data[u]: - if loops is None: - loops = True - elif loops is False: - u = next(u for u,neighb in data.iteritems() if u in neighb) - raise ValueError("The graph was built with loops=False but input data has a loop at {}.".format(u)) - break - if loops is None: - loops = False - if weighted is None: weighted = False - for u in data: - if len(set(data[u])) != len(data[u]): - if multiedges is False: - v = next((v for v in data[u] if data[u].count(v) > 1)) - raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) - if multiedges is None: - multiedges = True - if multiedges is None: multiedges = False - self.allow_loops(loops, check=False) - self.allow_multiple_edges(multiedges, check=False) - self.add_vertices(verts) - for u in data: - for v in data[u]: - if (multiedges or hash(u) <= hash(v) or - v not in data or u not in data[v]): - self._backend.add_edge(u,v,None,False) + from graph_input import from_dict_of_lists + from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted) + elif format == 'int': self.allow_loops(loops if loops else False, check=False) self.allow_multiple_edges(multiedges if multiedges else False, check=False) @@ -1652,7 +1277,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, raise ValueError("Unknown input format '{}'".format(format)) if weighted is None: weighted = False - self._weighted = weighted + self._weighted = getattr(self,'_weighted',weighted) self._pos = pos @@ -1668,6 +1293,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self._immutable = True ### Formats + + @doc_index("Basic methods") def graph6_string(self): """ Returns the graph6 representation of the graph as an ASCII string. @@ -1695,6 +1322,7 @@ def graph6_string(self): else: return generic_graph_pyx.small_integer_to_graph6(n) + generic_graph_pyx.binary_string_to_graph6(self._bit_vector()) + @doc_index("Basic methods") def sparse6_string(self): r""" Returns the sparse6 representation of the graph as an ASCII string. @@ -1783,6 +1411,7 @@ def sparse6_string(self): ### Attributes @combinatorial_map(name="partition of connected components") + @doc_index("Deprecated") def to_partition(self): """ Return the partition of connected components of ``self``. @@ -1803,6 +1432,7 @@ def to_partition(self): from sage.combinat.partition import Partition return Partition(sorted([len(y) for y in self.connected_components()], reverse=True)) + @doc_index("Basic methods") def is_directed(self): """ Since graph is undirected, returns False. @@ -1814,6 +1444,7 @@ def is_directed(self): """ return False + @doc_index("Connectivity, orientations, trees") def bridges(self): r""" Returns a list of the bridges (or cut edges). @@ -1839,6 +1470,7 @@ def bridges(self): bridges.extend(gs.edge_boundary(scc)) return bridges + @doc_index("Connectivity, orientations, trees") def spanning_trees(self): """ Returns a list of all spanning trees. @@ -1868,6 +1500,19 @@ def spanning_trees(self): - :meth:`~sage.graphs.graph.Graph.random_spanning_tree` -- returns a random spanning tree. + TESTS: + + Works with looped graphs:: + + sage: g = Graph({i:[i,(i+1)%6] for i in range(6)}) + sage: g.spanning_trees() + [Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices, + Graph on 6 vertices] + REFERENCES: .. [RT75] Read, R. C. and Tarjan, R. E. @@ -1886,7 +1531,7 @@ def _recursive_spanning_trees(G,forest): return [forest.copy()] else: # Pick an edge e from G-forest - for e in G.edges(): + for e in G.edge_iterator(labels=False): if not forest.has_edge(e): break @@ -1919,11 +1564,12 @@ def _recursive_spanning_trees(G,forest): forest = Graph([]) forest.add_vertices(self.vertices()) forest.add_edges(self.bridges()) - return _recursive_spanning_trees(self, forest) + return _recursive_spanning_trees(Graph(self,immutable=False,loops=False), forest) else: return [] ### Properties + @doc_index("Graph properties") def is_tree(self, certificate=False, output='vertex'): """ Tests if the graph is a tree @@ -2054,6 +1700,7 @@ def vertices_to_edges(x): else: return self.num_verts() == self.num_edges() + 1 + @doc_index("Graph properties") def is_forest(self, certificate=False, output='vertex'): """ Tests if the graph is a forest, i.e. a disjoint union of trees. @@ -2105,6 +1752,7 @@ def is_forest(self, certificate=False, output='vertex'): if not isit: return (False, cycle) + @doc_index("Graph properties") def is_overfull(self): r""" Tests whether the current graph is overfull. @@ -2204,6 +1852,7 @@ def is_overfull(self): return (self.order() % 2 == 1) and ( 2 * self.size() > max(self.degree()) * (self.order() - 1)) + @doc_index("Graph properties") def is_even_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced even hole. @@ -2312,6 +1961,7 @@ def is_even_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_odd_hole_free(self, certificate = False): r""" Tests whether ``self`` contains an induced odd hole. @@ -2391,6 +2041,7 @@ def is_odd_hole_free(self, certificate = False): return True + @doc_index("Graph properties") def is_bipartite(self, certificate = False): """ Returns ``True`` if graph `G` is bipartite, ``False`` if not. @@ -2471,6 +2122,7 @@ def is_bipartite(self, certificate = False): else: return True + @doc_index("Graph properties") def is_triangle_free(self, algorithm='bitset'): r""" Returns whether ``self`` is triangle-free @@ -2562,6 +2214,7 @@ def is_triangle_free(self, algorithm='bitset'): else: raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + @doc_index("Graph properties") def is_split(self): r""" Returns ``True`` if the graph is a Split graph, ``False`` otherwise. @@ -2635,6 +2288,7 @@ def is_split(self): return left == right + @doc_index("Algorithmically hard stuff") def treewidth(self,k=None,certificate=False): r""" Computes the tree-width of `G` (and provides a decomposition) @@ -2849,6 +2503,7 @@ def rec(cut,cc): return G + @doc_index("Algorithmically hard stuff") def is_perfect(self, certificate = False): r""" Tests whether the graph is perfect. @@ -2977,6 +2632,7 @@ def is_perfect(self, certificate = False): return self_complement.is_odd_hole_free(certificate = certificate) + @doc_index("Graph properties") def odd_girth(self): r""" Returns the odd girth of self. @@ -3044,6 +2700,7 @@ def odd_girth(self): return Infinity + @doc_index("Graph properties") def is_edge_transitive(self): """ Returns true if self is an edge transitive graph. @@ -3090,6 +2747,7 @@ def is_edge_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnSets);") == self.size() + @doc_index("Graph properties") def is_arc_transitive(self): """ Returns true if self is an arc-transitive graph @@ -3132,6 +2790,7 @@ def is_arc_transitive(self): return gap("OrbitLength("+str(A._gap_())+",Set(" + str(e) + "),OnTuples);") == 2*self.size() + @doc_index("Graph properties") def is_half_transitive(self): """ Returns true if self is a half-transitive graph. @@ -3171,6 +2830,7 @@ def is_half_transitive(self): self.is_vertex_transitive() and not self.is_arc_transitive()) + @doc_index("Graph properties") def is_semi_symmetric(self): """ Returns true if self is semi-symmetric. @@ -3215,6 +2875,7 @@ def is_semi_symmetric(self): self.is_edge_transitive() and not self.is_vertex_transitive()) + @doc_index("Connectivity, orientations, trees") def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): r""" Returns a degree-constrained subgraph. @@ -3313,15 +2974,15 @@ def degree_constrained_subgraph(self, bounds=None, solver=None, verbose=0): ### Orientations + @doc_index("Connectivity, orientations, trees") def strong_orientation(self): r""" - Returns a strongly connected orientation of the current graph. See - also the :wikipedia:`Strongly_connected_component`. + Returns a strongly connected orientation of the current graph. - An orientation of an undirected graph is a digraph obtained by - giving an unique direction to each of its edges. An orientation - is said to be strong if there is a directed path between each - pair of vertices. + An orientation of an undirected graph is a digraph obtained by giving an + unique direction to each of its edges. An orientation is said to be + strong if there is a directed path between each pair of vertices. See + also the :wikipedia:`Strongly_connected_component`. If the graph is 2-edge-connected, a strongly connected orientation can be found in linear time. If the given graph is not 2-connected, @@ -3419,6 +3080,7 @@ def strong_orientation(self): return d + @doc_index("Connectivity, orientations, trees") def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0): r""" Returns an orientation of ``self`` with the smallest possible maximum @@ -3527,6 +3189,7 @@ def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verb return O + @doc_index("Connectivity, orientations, trees") def bounded_outdegree_orientation(self, bound): r""" Computes an orientation of ``self`` such that every vertex `v` @@ -3684,6 +3347,7 @@ def bounded_outdegree_orientation(self, bound): ### Coloring + @doc_index("Basic methods") def bipartite_color(self): """ Returns a dictionary with vertices as the keys and the color class @@ -3705,6 +3369,7 @@ def bipartite_color(self): else: raise RuntimeError("Graph is not bipartite.") + @doc_index("Basic methods") def bipartite_sets(self): """ Returns `(X,Y)` where `X` and `Y` are the nodes in each bipartite set of @@ -3731,6 +3396,7 @@ def bipartite_sets(self): return left, right + @doc_index("Algorithmically hard stuff") def chromatic_number(self, algorithm="DLX", verbose = 0): r""" Returns the minimal number of colors needed to color the vertices @@ -3830,6 +3496,7 @@ def chromatic_number(self, algorithm="DLX", verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX', 'MILP' or 'CP'.") + @doc_index("Algorithmically hard stuff") def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): r""" Returns the first (optimal) proper vertex-coloring found. @@ -3904,6 +3571,7 @@ def coloring(self, algorithm="DLX", hex_colors=False, verbose = 0): else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.") + @doc_index("Algorithmically hard stuff") def chromatic_symmetric_function(self, R=None): r""" Return the chromatic symmetric function of ``self``. @@ -3977,6 +3645,7 @@ def chromatic_symmetric_function(self, R=None): ret += (-1)**len(F) * p[la] return ret + @doc_index("Leftovers") def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0): r""" Returns a maximum weighted matching of the graph @@ -4117,6 +3786,7 @@ def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, else: raise ValueError('algorithm must be set to either "Edmonds" or "LP"') + @doc_index("Algorithmically hard stuff") def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): r""" Checks whether there is a homomorphism between two graphs. @@ -4224,6 +3894,7 @@ def has_homomorphism_to(self, H, core = False, solver = None, verbose = 0): except MIPSolverException: return False + @doc_index("Leftovers") def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, verbose = 0): r""" Computes the fractional chromatic index of ``self`` @@ -4344,6 +4015,7 @@ def fractional_chromatic_index(self, solver = None, verbose_constraints = 0, ver # Accomplished ! return obj + @doc_index("Leftovers") def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): r""" Returns the Maximum Average Degree (MAD) of the current graph. @@ -4449,6 +4121,7 @@ def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): else: return g_mad + @doc_index("Algorithmically hard stuff") def independent_set_of_representatives(self, family, solver=None, verbose=0): r""" Returns an independent set of representatives. @@ -4577,6 +4250,7 @@ def independent_set_of_representatives(self, family, solver=None, verbose=0): return repr + @doc_index("Algorithmically hard stuff") def minor(self, H, solver=None, verbose=0): r""" Returns the vertices of a minor isomorphic to `H` in the current graph. @@ -4742,6 +4416,7 @@ def minor(self, H, solver=None, verbose=0): ### Convexity + @doc_index("Algorithmically hard stuff") def convexity_properties(self): r""" Returns a ``ConvexityProperties`` object corresponding to ``self``. @@ -4783,6 +4458,7 @@ def convexity_properties(self): return ConvexityProperties(self) # Centrality + @doc_index("Distances") def centrality_degree(self, v=None): r""" Returns the degree centrality of a vertex. @@ -4830,6 +4506,7 @@ def centrality_degree(self, v=None): ### Constructors + @doc_index("Basic methods") def to_directed(self, implementation='c_graph', data_structure=None, sparse=None): """ @@ -4900,6 +4577,7 @@ def to_directed(self, implementation='c_graph', data_structure=None, return D + @doc_index("Basic methods") def to_undirected(self): """ Since the graph is already undirected, simply returns a copy of @@ -4912,6 +4590,7 @@ def to_undirected(self): """ return self.copy() + @doc_index("Basic methods") def join(self, other, verbose_relabel=None, labels="pairs"): """ Returns the join of ``self`` and ``other``. @@ -4980,7 +4659,7 @@ def join(self, other, verbose_relabel=None, labels="pairs"): G.name('%s join %s'%(self.name(), other.name())) return G - + @doc_index("Leftovers") def seidel_adjacency_matrix(self, vertices=None): r""" Returns the Seidel adjacency matrix of ``self``. @@ -5012,6 +4691,7 @@ def seidel_adjacency_matrix(self, vertices=None): self.complement().adjacency_matrix(sparse=False, \ vertices=vertices) + @doc_index("Leftovers") def seidel_switching(self, s, inplace=True): r""" Returns the Seidel switching of ``self`` w.r.t. subset of vertices ``s``. @@ -5054,6 +4734,7 @@ def seidel_switching(self, s, inplace=True): if not inplace: return G + @doc_index("Leftovers") def twograph(self): r""" Returns the two-graph of ``self`` @@ -5112,6 +4793,7 @@ def twograph(self): ### Visualization + @doc_index("Basic methods") def write_to_eps(self, filename, **options): r""" Writes a plot of the graph to ``filename`` in ``eps`` format. @@ -5144,6 +4826,7 @@ def write_to_eps(self, filename, **options): f.write( print_graph_eps(self.vertices(), self.edge_iterator(), pos) ) f.close() + @doc_index("Algorithmically hard stuff") def topological_minor(self, H, vertices = False, paths = False, solver=None, verbose=0): r""" Returns a topological `H`-minor from ``self`` if one exists. @@ -5375,6 +5058,7 @@ def topological_minor(self, H, vertices = False, paths = False, solver=None, ver ### Cliques + @doc_index("Clique-related methods") def cliques_maximal(self, algorithm = "native"): """ Returns the list of all maximal cliques, with each clique represented @@ -5450,6 +5134,7 @@ def cliques_maximal(self, algorithm = "native"): else: raise ValueError("Algorithm must be equal to 'native' or to 'NetworkX'.") + @doc_index("Clique-related methods") def clique_maximum(self, algorithm="Cliquer"): """ Returns the vertex set of a maximal order complete subgraph. @@ -5520,6 +5205,7 @@ def clique_maximum(self, algorithm="Cliquer"): else: raise NotImplementedError("Only 'MILP', 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def clique_number(self, algorithm="Cliquer", cliques=None): r""" Returns the order of the largest clique of the graph (the clique @@ -5610,6 +5296,7 @@ def clique_number(self, algorithm="Cliquer", cliques=None): else: raise NotImplementedError("Only 'networkx' 'MILP' 'Cliquer' and 'mcqd' are supported.") + @doc_index("Clique-related methods") def cliques_number_of(self, vertices=None, cliques=None): """ Returns a dictionary of the number of maximal cliques containing each @@ -5660,6 +5347,7 @@ def cliques_number_of(self, vertices=None, cliques=None): import networkx return networkx.number_of_cliques(self.networkx_graph(copy=False), vertices, cliques) + @doc_index("Clique-related methods") def cliques_get_max_clique_graph(self, name=''): """ Returns a graph constructed with maximal cliques as vertices, and @@ -5691,6 +5379,7 @@ def cliques_get_max_clique_graph(self, name=''): import networkx return Graph(networkx.make_max_clique_graph(self.networkx_graph(copy=False), name=name, create_using=networkx.MultiGraph())) + @doc_index("Clique-related methods") def cliques_get_clique_bipartite(self, **kwds): """ Returns a bipartite graph constructed such that maximal cliques are the @@ -5718,6 +5407,7 @@ def cliques_get_clique_bipartite(self, **kwds): import networkx return BipartiteGraph(networkx.make_clique_bipartite(self.networkx_graph(copy=False), **kwds)) + @doc_index("Algorithmically hard stuff") def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" Returns a maximum independent set. @@ -5807,6 +5497,7 @@ def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_r return [u for u in self.vertices() if not u in my_cover] + @doc_index("Algorithmically hard stuff") def vertex_cover(self, algorithm = "Cliquer", value_only = False, reduction_rules = True, solver = None, verbosity = 0): r""" @@ -6091,6 +5782,7 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, cover_g.sort() return cover_g + @doc_index("Clique-related methods") def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, cliques=None): """ @@ -6162,6 +5854,7 @@ def cliques_vertex_clique_number(self, algorithm="cliquer", vertices=None, else: raise NotImplementedError("Only 'networkx' and 'cliquer' are supported.") + @doc_index("Clique-related methods") def cliques_containing_vertex(self, vertices=None, cliques=None): """ Returns the cliques containing each vertex, represented as a dictionary @@ -6212,6 +5905,7 @@ def cliques_containing_vertex(self, vertices=None, cliques=None): import networkx return networkx.cliques_containing_node(self.networkx_graph(copy=False),vertices, cliques) + @doc_index("Clique-related methods") def clique_complex(self): """ Returns the clique complex of self. This is the largest simplicial complex on @@ -6243,6 +5937,7 @@ def clique_complex(self): C._graph = self return C + @doc_index("Clique-related methods") def clique_polynomial(self, t = None): """ Returns the clique polynomial of self. @@ -6274,6 +5969,7 @@ def clique_polynomial(self, t = None): ### Miscellaneous + @doc_index("Leftovers") def cores(self, k = None, with_labels=False): """ Returns the core number for each vertex in an ordered list. @@ -6431,6 +6127,7 @@ def cores(self, k = None, with_labels=False): else: return core.values() + @doc_index("Leftovers") def modular_decomposition(self): r""" Returns the modular decomposition of the current graph. @@ -6573,6 +6270,7 @@ def modular_decomposition(self): return relabel(D) + @doc_index("Graph properties") def is_prime(self): r""" Tests whether the current graph is prime. @@ -6700,6 +6398,7 @@ def _gomory_hu_tree(self, vertices, method="FF"): return g + @doc_index("Connectivity, orientations, trees") def gomory_hu_tree(self, method="FF"): r""" Returns a Gomory-Hu tree of self. @@ -6796,6 +6495,7 @@ def gomory_hu_tree(self, method="FF"): g.set_pos(dict(self.get_pos())) return g + @doc_index("Leftovers") def two_factor_petersen(self): r""" Returns a decomposition of the graph into 2-factors. @@ -6865,6 +6565,7 @@ def two_factor_petersen(self): return classes_b + @doc_index("Leftovers") def kirchhoff_symanzik_polynomial(self, name='t'): """ Return the Kirchhoff-Symanzik polynomial of a graph. @@ -6964,6 +6665,7 @@ def kirchhoff_symanzik_polynomial(self, name='t'): D = matrix.diagonal(PolynomialRing(ZZ, name, self.size()).gens()) return (circuit_mtrx.transpose() * D * circuit_mtrx).determinant() + @doc_index("Leftovers") def ihara_zeta_function_inverse(self): """ Compute the inverse of the Ihara zeta function of the graph. @@ -7041,43 +6743,63 @@ def ihara_zeta_function_inverse(self): import types import sage.graphs.weakly_chordal -Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) -Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) -Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) +Graph.is_long_hole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_hole_free, None, Graph) +Graph.is_long_antihole_free = types.MethodType(sage.graphs.weakly_chordal.is_long_antihole_free, None, Graph) +Graph.is_weakly_chordal = types.MethodType(sage.graphs.weakly_chordal.is_weakly_chordal, None, Graph) import sage.graphs.asteroidal_triples Graph.is_asteroidal_triple_free = types.MethodType(sage.graphs.asteroidal_triples.is_asteroidal_triple_free, None, Graph) import sage.graphs.chrompoly -Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) +Graph.chromatic_polynomial = types.MethodType(sage.graphs.chrompoly.chromatic_polynomial, None, Graph) import sage.graphs.graph_decompositions.rankwidth -Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) +Graph.rank_decomposition = types.MethodType(sage.graphs.graph_decompositions.rankwidth.rank_decomposition, None, Graph) import sage.graphs.matchpoly -Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) +Graph.matching_polynomial = types.MethodType(sage.graphs.matchpoly.matching_polynomial, None, Graph) import sage.graphs.cliquer -Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) +Graph.cliques_maximum = types.MethodType(sage.graphs.cliquer.all_max_clique, None, Graph) import sage.graphs.spanning_tree -Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) +Graph.random_spanning_tree = types.MethodType(sage.graphs.spanning_tree.random_spanning_tree, None, Graph) import sage.graphs.graph_decompositions.graph_products -Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) +Graph.is_cartesian_product = types.MethodType(sage.graphs.graph_decompositions.graph_products.is_cartesian_product, None, Graph) import sage.graphs.distances_all_pairs -Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) +Graph.is_distance_regular = types.MethodType(sage.graphs.distances_all_pairs.is_distance_regular, None, Graph) import sage.graphs.base.static_dense_graph -Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) +Graph.is_strongly_regular = types.MethodType(sage.graphs.base.static_dense_graph.is_strongly_regular, None, Graph) # From Python modules import sage.graphs.line_graph -Graph.is_line_graph = sage.graphs.line_graph.is_line_graph +Graph.is_line_graph = sage.graphs.line_graph.is_line_graph from sage.graphs.tutte_polynomial import tutte_polynomial -Graph.tutte_polynomial = tutte_polynomial +Graph.tutte_polynomial = tutte_polynomial from sage.graphs.lovasz_theta import lovasz_theta -Graph.lovasz_theta = lovasz_theta +Graph.lovasz_theta = lovasz_theta + +_additional_categories = { + Graph.is_long_hole_free : "Graph properties", + Graph.is_long_antihole_free : "Graph properties", + Graph.is_weakly_chordal : "Graph properties", + Graph.is_asteroidal_triple_free : "Graph properties", + Graph.chromatic_polynomial : "Algorithmically hard stuff", + Graph.rank_decomposition : "Algorithmically hard stuff", + Graph.matching_polynomial : "Algorithmically hard stuff", + Graph.cliques_maximum : "Clique-related methods", + Graph.random_spanning_tree : "Connectivity, orientations, trees", + Graph.is_cartesian_product : "Graph properties", + Graph.is_distance_regular : "Graph properties", + Graph.is_strongly_regular : "Graph properties", + Graph.is_line_graph : "Graph properties", + Graph.tutte_polynomial : "Algorithmically hard stuff", + Graph.lovasz_theta : "Leftovers", + } + +__doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index a28d0c3aa75..3db5bb3f4da 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -235,6 +235,7 @@ def __append_to_doc(methods): __append_to_doc( ["AffineOrthogonalPolarGraph", + "AhrensSzekeresGeneralizedQuadrangleGraph", "NonisotropicOrthogonalPolarGraph", "NonisotropicUnitaryPolarGraph", "OrthogonalPolarGraph", @@ -242,6 +243,7 @@ def __append_to_doc(methods): "SymplecticPolarGraph", "TaylorTwographDescendantSRG", "TaylorTwographSRG", + "T2starGeneralizedQuadrangleGraph", "UnitaryDualPolarGraph", "UnitaryPolarGraph"]) @@ -1997,6 +1999,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None ########################################################################### import sage.graphs.generators.classical_geometries AffineOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.AffineOrthogonalPolarGraph) + AhrensSzekeresGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.AhrensSzekeresGeneralizedQuadrangleGraph) NonisotropicOrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph) NonisotropicUnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.NonisotropicUnitaryPolarGraph) OrthogonalPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.OrthogonalPolarGraph) @@ -2006,6 +2009,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None TaylorTwographDescendantSRG = \ staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographDescendantSRG) TaylorTwographSRG = staticmethod(sage.graphs.generators.classical_geometries.TaylorTwographSRG) + T2starGeneralizedQuadrangleGraph = staticmethod(sage.graphs.generators.classical_geometries.T2starGeneralizedQuadrangleGraph) UnitaryDualPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryDualPolarGraph) UnitaryPolarGraph = staticmethod(sage.graphs.generators.classical_geometries.UnitaryPolarGraph) diff --git a/src/sage/graphs/graph_input.py b/src/sage/graphs/graph_input.py new file mode 100644 index 00000000000..3692458a517 --- /dev/null +++ b/src/sage/graphs/graph_input.py @@ -0,0 +1,527 @@ +r""" +Functions for reading/building graphs/digraphs. + +This module gathers functions needed to build a graph from any other data. + +.. NOTE:: + + This is an **internal** module of Sage. All features implemented here are + made available to end-users through the constructors of :class:`Graph` and + :class:`DiGraph`. + +Note that because they are called by the constructors of :class:`Graph` and +:class:`DiGraph`, most of these functions modify a graph inplace. + +{INDEX_OF_FUNCTIONS} + +Functions +--------- + +""" + +def from_graph6(G, g6_string): + r""" + Fill ``G`` with the data of a graph6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a graph6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_graph6 + sage: g = Graph() + sage: from_graph6(g, 'IheA@GUAo') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_graph6 + + if not isinstance(g6_string, str): + raise ValueError('If input format is graph6, then g6_string must be a string.') + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + ss = g6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_graph6(s, n) + expected = n*(n-1)/2 + (6 - n*(n-1)/2)%6 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(i): + if m[k] == '1': + G._backend.add_edge(i, j, None, False) + k += 1 + +def from_sparse6(G, g6_string): + r""" + Fill ``G`` with the data of a sparse6 string. + + INPUT: + + - ``G`` -- a graph + + - ``g6_string`` -- a sparse6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_sparse6 + sage: g = Graph() + sage: from_sparse6(g, ':I`ES@obGkqegW~') + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, int_to_binary_string + from math import ceil, floor + from sage.misc.functional import log + n = g6_string.find('\n') + if n == -1: + n = len(g6_string) + s = g6_string[:n] + n, s = length_and_string_from_graph6(s[1:]) + if n == 0: + edges = [] + else: + k = int(ceil(log(n,2))) + ords = [ord(i) for i in s] + if any(o > 126 or o < 63 for o in ords): + raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)])) + bits = ''.join([int_to_binary_string(o-63).zfill(6) for o in ords]) + b = [] + x = [] + for i in xrange(int(floor(len(bits)/(k+1)))): + b.append(int(bits[(k+1)*i:(k+1)*i+1],2)) + x.append(int(bits[(k+1)*i+1:(k+1)*i+k+1],2)) + v = 0 + edges = [] + for i in xrange(len(b)): + if b[i] == 1: + v += 1 + if x[i] > v: + v = x[i] + else: + if v < n: + edges.append((x[i],v)) + G.add_vertices(range(n)) + G.add_edges(edges) + +def from_dig6(G, dig6_string): + r""" + Fill ``G`` with the data of a dig6 string. + + INPUT: + + - ``G`` -- a graph + + - ``dig6_string`` -- a dig6 string + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dig6 + sage: g = DiGraph() + sage: from_dig6(g, digraphs.Circuit(10).dig6_string()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from generic_graph_pyx import length_and_string_from_graph6, binary_string_from_dig6 + if not isinstance(dig6_string, str): + raise ValueError('If input format is dig6, then dig6_string must be a string.') + n = dig6_string.find('\n') + if n == -1: + n = len(dig6_string) + ss = dig6_string[:n] + n, s = length_and_string_from_graph6(ss) + m = binary_string_from_dig6(s, n) + expected = n**2 + if len(m) > expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too long."%(ss,n)) + elif len(m) < expected: + raise RuntimeError("The string (%s) seems corrupt: for n = %d, the string is too short."%(ss,n)) + G.add_vertices(range(n)) + k = 0 + for i in xrange(n): + for j in xrange(n): + if m[k] == '1': + G._backend.add_edge(i, j, None, True) + k += 1 + +def from_seidel_adjacency_matrix(G, M): + r""" + Fill ``G`` with the data of a Seidel adjacency matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a Seidel adjacency matrix + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_seidel_adjacency_matrix + sage: g = Graph() + sage: from_seidel_adjacency_matrix(g, graphs.PetersenGraph().seidel_adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + + if M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if any(e < -1 or e > 1 for e in entries): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have only 0,1,-1 integer entries") + if any(i==j for i,j in M.nonzero_positions()): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " have 0s on the main diagonal") + if not M.is_symmetric(): + raise ValueError("Graph's Seidel adjacency matrix must"+ + " be symmetric") + G.add_vertices(range(M.nrows())) + e = [] + for i,j in M.nonzero_positions(): + if i <= j and M[i,j] < 0: + e.append((i,j)) + G.add_edges(e) + +def from_adjacency_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an adjacency matrix. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``M`` -- an adjacency matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_adjacency_matrix + sage: g = Graph() + sage: from_adjacency_matrix(g, graphs.PetersenGraph().adjacency_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + from sage.rings.integer_ring import ZZ + assert is_Matrix(M) + # note: the adjacency matrix might be weighted and hence not + # necessarily consists of integers + if not weighted and M.base_ring() != ZZ: + try: + M = M.change_ring(ZZ) + except TypeError: + if weighted is False: + raise ValueError("Non-weighted graph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + + if M.is_sparse(): + entries = set(M[i,j] for i,j in M.nonzero_positions()) + else: + entries = set(M.list()) + + if not weighted and any(e < 0 for e in entries): + if weighted is False: + raise ValueError("Non-weighted digraph's"+ + " adjacency matrix must have only nonnegative"+ + " integer entries") + weighted = True + if multiedges is None: multiedges = False + if weighted is None: + weighted = False + + if multiedges is None: + multiedges = ((not weighted) and any(e != 0 and e != 1 for e in entries)) + + if not loops and any(M[i,i] for i in xrange(M.nrows())): + if loops is False: + raise ValueError("Non-looped digraph's adjacency"+ + " matrix must have zeroes on the diagonal.") + loops = True + if loops is None: + loops = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + e = [] + if G.is_directed(): + pairs = M.nonzero_positions() + else: + pairs = ((i,j) for i,j in M.nonzero_positions() if i<=j) + if weighted: + for i,j in pairs: + e.append((i,j,M[i][j])) + elif multiedges: + for i,j in pairs: + e += [(i,j)]*int(M[i][j]) + else: + for i,j in pairs: + e.append((i,j)) + G.add_edges(e) + G._weighted = weighted + +def from_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an incidence matrix. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_incidence_matrix + sage: g = Graph() + sage: from_incidence_matrix(g, graphs.PetersenGraph().incidence_matrix()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + oriented = any(M[pos] < 0 for pos in M.nonzero_positions(copy=False)) + + positions = [] + for i in range(M.ncols()): + NZ = M.nonzero_positions_in_column(i) + if len(NZ) == 1: + if oriented: + raise ValueError("Column {} of the (oriented) incidence " + "matrix contains only one nonzero value".format(i)) + elif M[NZ[0],i] != 2: + raise ValueError("Each column of a non-oriented incidence " + "matrix must sum to 2, but column {} does not".format(i)) + if loops is None: + loops = True + positions.append((NZ[0],NZ[0])) + elif len(NZ) != 2 or \ + (oriented and not ((M[NZ[0],i] == +1 and M[NZ[1],i] == -1) or \ + (M[NZ[0],i] == -1 and M[NZ[1],i] == +1))) or \ + (not oriented and (M[NZ[0],i] != 1 or M[NZ[1],i] != 1)): + msg = "There must be one or two nonzero entries per column in an incidence matrix. " + msg += "Got entries {} in column {}".format([M[j,i] for j in NZ], i) + raise ValueError(msg) + else: + positions.append(tuple(NZ)) + + if weighted is None: G._weighted = False + if multiedges is None: + total = len(positions) + multiedges = (len(set(positions)) < total ) + G.allow_loops(False if loops is None else loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_oriented_incidence_matrix(G, M, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of an *oriented* incidence matrix. + + An oriented incidence matrix is the incidence matrix of a directed graph, in + which each non-loop edge corresponds to a `+1` and a `-1`, indicating its + source and destination. + + INPUT: + + - ``G`` -- a :class:`DiGraph` + + - ``M`` -- an incidence matrix + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_oriented_incidence_matrix + sage: g = DiGraph() + sage: from_oriented_incidence_matrix(g, digraphs.Circuit(10).incidence_matrix()) + sage: g.is_isomorphic(digraphs.Circuit(10)) + True + """ + from sage.matrix.matrix import is_Matrix + assert is_Matrix(M) + + positions = [] + for c in M.columns(): + NZ = c.nonzero_positions() + if len(NZ) != 2: + raise ValueError("There must be two nonzero entries (-1 & 1) per column.") + L = sorted(set(c.list())) + if L != [-1,0,1]: + msg += "Each column represents an edge: -1 goes to 1." + raise ValueError(msg) + if c[NZ[0]] == -1: + positions.append(tuple(NZ)) + else: + positions.append((NZ[1],NZ[0])) + if weighted is None: weighted = False + if multiedges is None: + total = len(positions) + multiedges = ( len(set(positions)) < total ) + G.allow_loops(True if loops else False,check=False) + G.allow_multiple_edges(multiedges,check=False) + G.add_vertices(range(M.nrows())) + G.add_edges(positions) + +def from_dict_of_dicts(G, M, loops=False, multiedges=False, weighted=False, convert_empty_dict_labels_to_None=False): + r""" + Fill ``G`` with the data of a dictionary of dictionaries. + + INPUT: + + - ``G`` -- a graph + + - ``M`` -- a dictionary of dictionaries. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + - ``convert_empty_dict_labels_to_None`` (boolean) -- whether to adjust for + empty dicts instead of None in NetworkX default edge labels. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_dicts + sage: g = Graph() + sage: from_dict_of_dicts(g, graphs.PetersenGraph().to_dictionary(edge_labels=True)) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + if not all(isinstance(M[u], dict) for u in M): + raise ValueError("Input dict must be a consistent format.") + + if not loops and any(u in neighb for u,neighb in M.iteritems()): + if loops is False: + u = next(u for u,neighb in M.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input M has a loop at {}.".format(u)) + loops = True + if loops is None: + loops = False + + if weighted is None: G._weighted = False + for u in M: + for v in M[u]: + if multiedges is not False and not isinstance(M[u][v], list): + if multiedges is None: multiedges = False + if multiedges: + raise ValueError("Dict of dicts for multigraph must be in the format {v : {u : list}}") + if multiedges is None and len(M) > 0: + multiedges = True + + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + verts = set().union(M.keys(), *M.values()) + G.add_vertices(verts) + if convert_empty_dict_labels_to_None: + relabel = lambda x : x if x!={} else None + else: + relabel = lambda x : x + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in M: + for v in M[u]: + if v_to_id[u] <= v_to_id[v] or v not in M or u not in M[v] or u == v: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),False) + elif multiedges: + for u in M: + for v in M[u]: + for l in M[u][v]: + G._backend.add_edge(u,v,relabel(l),is_directed) + else: + for u in M: + for v in M[u]: + G._backend.add_edge(u,v,relabel(M[u][v]),is_directed) + +def from_dict_of_lists(G, D, loops=False, multiedges=False, weighted=False): + r""" + Fill ``G`` with the data of a dictionary of lists. + + INPUT: + + - ``G`` -- a :class:`Graph` or :class:`DiGraph`. + + - ``D`` -- a dictionary of lists. + + - ``loops``, ``multiedges``, ``weighted`` (booleans) -- whether to consider + the graph as having loops, multiple edges, or weights. Set to ``False`` by default. + + EXAMPLE:: + + sage: from sage.graphs.graph_input import from_dict_of_lists + sage: g = Graph() + sage: from_dict_of_lists(g, graphs.PetersenGraph().to_dictionary()) + sage: g.is_isomorphic(graphs.PetersenGraph()) + True + """ + verts = set().union(D.keys(),*D.values()) + if loops is None or loops is False: + for u in D: + if u in D[u]: + if loops is None: + loops = True + elif loops is False: + u = next(u for u,neighb in D.iteritems() if u in neighb) + raise ValueError("The graph was built with loops=False but input D has a loop at {}.".format(u)) + break + if loops is None: + loops = False + if weighted is None: G._weighted = False + for u in D: + if len(set(D[u])) != len(D[u]): + if multiedges is False: + v = next((v for v in D[u] if D[u].count(v) > 1)) + raise ValueError("Non-multigraph got several edges (%s,%s)"%(u,v)) + if multiedges is None: + multiedges = True + if multiedges is None: multiedges = False + G.allow_loops(loops, check=False) + G.allow_multiple_edges(multiedges, check=False) + G.add_vertices(verts) + + is_directed = G.is_directed() + if not is_directed and multiedges: + v_to_id = {v:i for i,v in enumerate(verts)} + for u in D: + for v in D[u]: + if (v_to_id[u] <= v_to_id[v] or + v not in D or u not in D[v] or u == v): + G._backend.add_edge(u,v,None,False) + else: + for u in D: + for v in D[u]: + G._backend.add_edge(u,v,None,is_directed) + +from sage.misc.rest_index_of_methods import gen_rest_table_index +import sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(sys.modules[__name__])) diff --git a/src/sage/graphs/hyperbolicity.pyx b/src/sage/graphs/hyperbolicity.pyx index bf8a8a2c8b4..07d3b4703f4 100644 --- a/src/sage/graphs/hyperbolicity.pyx +++ b/src/sage/graphs/hyperbolicity.pyx @@ -208,7 +208,7 @@ def _my_subgraph(G, vertices, relabel=False, return_map=False): etc.). If ``relabel`` is ``True``, the vertices of the new graph are relabeled - with integers in the range '0\cdots |vertices|-1'. The relabeling map is + with integers in the range '0\cdots \mid vertices \mid -1'. The relabeling map is returned if ``return_map`` is also ``True``. TESTS: diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index 0deb4cbafdd..7dfb9473d99 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -159,4 +159,23 @@ def nauty(self, number_of_sets, number_of_vertices, yield tuple( tuple( x for x in G.neighbors(v)) for v in range(number_of_vertices, total)) + def CompleteUniform(self, n, k): + r""" + Return the complete `k`-uniform hypergraph on `n` points. + + INPUT: + + - ``k,n`` -- nonnegative integers with `k\leq n` + + EXAMPLE:: + + sage: h = hypergraphs.CompleteUniform(5,2); h + Incidence structure with 5 points and 10 blocks + sage: len(h.packing()) + 2 + """ + from sage.combinat.designs.incidence_structures import IncidenceStructure + from itertools import combinations + return IncidenceStructure(list(combinations(range(n),k))) + hypergraphs = HypergraphGenerators() diff --git a/src/sage/graphs/independent_sets.pyx b/src/sage/graphs/independent_sets.pyx index cbb99409f55..e91e0609dba 100644 --- a/src/sage/graphs/independent_sets.pyx +++ b/src/sage/graphs/independent_sets.pyx @@ -338,7 +338,7 @@ cdef class IndependentSets: - ``S`` -- a set of vertices to be tested. - TESTS:: + TESTS: All independent sets of PetersenGraph are... independent sets:: diff --git a/src/sage/graphs/schnyder.py b/src/sage/graphs/schnyder.py index c4b4b8398fc..6af206e9581 100644 --- a/src/sage/graphs/schnyder.py +++ b/src/sage/graphs/schnyder.py @@ -6,12 +6,14 @@ Walter Schnyder's Algorithm. AUTHORS: - -- Jonathan Bober, Emily Kirkman (2008-02-09): initial version + +- Jonathan Bober, Emily Kirkman (2008-02-09) -- initial version REFERENCE: - [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. - Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, - San Francisco (1994), pp. 138-147. + +.. [1] Schnyder, Walter. Embedding Planar Graphs on the Grid. + Proc. 1st Annual ACM-SIAM Symposium on Discrete Algorithms, + San Francisco (1994), pp. 138-147. """ #***************************************************************************** # Copyright (C) 2008 Jonathan Bober and Emily Kirkman @@ -42,11 +44,13 @@ def _triangulate(g, comb_emb): method will work on one of these attempts.) INPUT: - g -- the graph to triangulate - comb_emb -- a planar combinatorial embedding of g - RETURNS: - A list of edges that are added to the graph (in place) + - g -- the graph to triangulate + - ``comb_emb`` -- a planar combinatorial embedding of g + + OUTPUT: + + A list of edges that are added to the graph (in place) EXAMPLES:: @@ -125,27 +129,33 @@ def _triangulate(g, comb_emb): return edges_added def _normal_label(g, comb_emb, external_face): - """ - Helper function to schnyder method for computing coordinates in the plane to - plot a planar graph with no edge crossings. + r""" + Helper function to schnyder method for computing coordinates in + the plane to plot a planar graph with no edge crossings. - Constructs a normal labelling of a triangular graph g, given the planar - combinatorial embedding of g and a designated external face. Returns labels - dictionary. The normal label is constructed by first contracting the graph - down to its external face, then expanding the graph back to the original while - simultaneously adding angle labels. + Constructs a normal labelling of a triangular graph g, given the + planar combinatorial embedding of g and a designated external + face. Returns labels dictionary. The normal label is constructed + by first contracting the graph down to its external face, then + expanding the graph back to the original while simultaneously + adding angle labels. INPUT: - g -- the graph to find the normal labeling of (g must be triangulated) - comb_emb -- a planar combinatorial embedding of g - external_face -- the list of three edges in the external face of g - RETURNS: - x -- tuple with entries - x[0] = dict of dicts of normal labeling for each vertex of g and each - adjacent neighbors u,v (u < v) of vertex: - { vertex : { (u,v): angel_label } } - x[1] = (v1,v2,v3) tuple of the three vertices of the external face. + - g -- the graph to find the normal labeling of (g must be triangulated) + - ``comb_emb`` -- a planar combinatorial embedding of g + - ``external_face`` -- the list of three edges in the external face of g + + OUTPUT: + + x -- tuple with entries + + x[0] = dict of dicts of normal labeling for each vertex of g and each + adjacent neighbors u,v (u < v) of vertex: + + { vertex : { (u,v): angel_label } } + + x[1] = (v1,v2,v3) tuple of the three vertices of the external face. EXAMPLES:: @@ -160,7 +170,6 @@ def _normal_label(g, comb_emb, external_face): sage: _realizer(g, tn) ({0: []}, (0, 1, 2)) - """ contracted = [] contractible = [] @@ -329,20 +338,27 @@ def _realizer(g, x, example=False): give a path to each of the three external vertices. INPUT: - g -- the graph to compute the realizer of - x -- tuple with entries - x[0] = dict of dicts representing a normal labeling of g. For - each vertex of g and each adjacent neighbors u,v (u < v) of - vertex: { vertex : { (u,v): angle_label } } - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) - - RETURNS: - x -- tuple with entries - x[0] = dict of lists of TreeNodes: - { root_vertex : [ list of all TreeNodes under root_vertex ] } - x[0] = (v1,v2,v3) tuple of the three external vertices (also the - roots of each tree) + + - g -- the graph to compute the realizer of + - x -- tuple with entries + + x[0] = dict of dicts representing a normal labeling of g. For + each vertex of g and each adjacent neighbors u,v (u < v) of + vertex: { vertex : { (u,v): angle_label } } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + + OUTPUT: + + - x -- tuple with entries + + x[0] = dict of lists of TreeNodes: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1,v2,v3) tuple of the three external vertices (also the + roots of each tree) EXAMPLES:: @@ -409,22 +425,26 @@ def _realizer(g, x, example=False): return tree_nodes, (v1,v2,v3) def _compute_coordinates(g, x): - """ + r""" Given a triangulated graph g with a dict of trees given by the realizer and tuple of the external vertices, we compute the coordinates of a planar geometric embedding in the grid. - The coordinates will be set to the _pos attribute of g. + The coordinates will be set to the ``_pos`` attribute of g. INPUT: - g -- the graph to compute the coordinates of - x -- tuple with entries - x[0] = dict of tree nodes for the three trees with each external - vertex as root - { root_vertex : [ list of all TreeNodes under root_vertex ] } - - x[1] = (v1, v2, v3) tuple of the three external vertices (also - the roots of each tree) + + - g -- the graph to compute the coordinates of + - x -- tuple with entries + + x[0] = dict of tree nodes for the three trees with each external + vertex as root: + + { root_vertex : [ list of all TreeNodes under root_vertex ] } + + x[1] = (v1, v2, v3) tuple of the three external vertices (also + the roots of each tree) + EXAMPLES:: sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates @@ -505,16 +525,17 @@ def _compute_coordinates(g, x): class TreeNode(): """ - A class to represent each node in the trees used by _realizer() and - _compute_coordinates() when finding a planar geometric embedding in + A class to represent each node in the trees used by :func:`_realizer` and + :func:`_compute_coordinates` when finding a planar geometric embedding in the grid. Each tree node is doubly linked to its parent and children. INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLES:: @@ -532,14 +553,14 @@ class TreeNode(): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ def __init__(self, parent = None, children = None, label = None): """ INPUT: - parent -- the parent TreeNode of self - children -- a list of TreeNode children of self - label -- the associated realizer vertex label + + - ``parent`` -- the parent TreeNode of ``self`` + - ``children`` -- a list of TreeNode children of ``self`` + - ``label`` -- the associated realizer vertex label EXAMPLE:: @@ -557,7 +578,6 @@ def __init__(self, parent = None, children = None, label = None): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if children is None: children = [] @@ -566,10 +586,10 @@ def __init__(self, parent = None, children = None, label = None): self.label = label self.number_of_descendants = 1 - def compute_number_of_descendants(self): """ Computes the number of descendants of self and all descendants. + For each TreeNode, sets result as attribute self.number_of_descendants EXAMPLES:: @@ -599,6 +619,7 @@ def compute_number_of_descendants(self): def compute_depth_of_self_and_children(self): """ Computes the depth of self and all descendants. + For each TreeNode, sets result as attribute self.depth EXAMPLES:: @@ -617,7 +638,6 @@ def compute_depth_of_self_and_children(self): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if self.parent is None: self.depth = 1 @@ -646,7 +666,6 @@ def append_child(self, child): sage: tn.compute_depth_of_self_and_children() sage: tn3.depth 2 - """ if child in self.children: return diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index cfefb513375..553bd53afed 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -46,7 +46,7 @@ from sage.graphs.generators.smallgraphs import HigmanSimsGraph from sage.graphs.generators.smallgraphs import LocalMcLaughlinGraph from sage.graphs.generators.smallgraphs import SuzukiGraph from sage.graphs.graph import Graph -from libc.math cimport sqrt +from libc.math cimport sqrt, floor from sage.matrix.constructor import Matrix from sage.rings.finite_rings.constructor import FiniteField as GF from sage.coding.linear_code import LinearCode @@ -858,6 +858,83 @@ def is_unitary_dual_polar(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import UnitaryDualPolarGraph return (UnitaryDualPolarGraph, 5, p**t) +@cached_function +def is_GQqmqp(int v,int k,int l,int mu): + r""" + Test whether some `GQ(q-1,q+1)` or `GQ(q+1,q-1)`-graph is `(v,k,\lambda,\mu)`-srg. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_GQqmqp + sage: t = is_GQqmqp(27,10,1,5); t + (, 3, False) + sage: g = t[0](*t[1:]); g + AS(3); GQ(2, 4): Graph on 27 vertices + sage: t = is_GQqmqp(45,12,3,3); t + (, 3, True) + sage: g = t[0](*t[1:]); g + AS(3)*; GQ(4, 2): Graph on 45 vertices + sage: g.is_strongly_regular(parameters=True) + (45, 12, 3, 3) + sage: t = is_GQqmqp(16,6,2,2); t + (, 2, True) + sage: g = t[0](*t[1:]); g + T2*(O,2)*; GQ(3, 1): Graph on 16 vertices + sage: g.is_strongly_regular(parameters=True) + (16, 6, 2, 2) + sage: t = is_GQqmqp(64,18,2,6); t + (, 4, False) + sage: g = t[0](*t[1:]); g + T2*(O,4); GQ(3, 5): Graph on 64 vertices + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + TESTS:: + + sage: (S,T)=(127,129) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, False) + sage: (S,T)=(129,127) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 128, True) + sage: (S,T)=(124,126) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, False) + sage: (S,T)=(126,124) + sage: t = is_GQqmqp((S+1)*(S*T+1), S*(T+1), S-1, T+1); t + (, 125, True) + sage: t = is_GQqmqp(5,5,5,5); t + """ + # do we have GQ(s,t)? we must have mu=t+1, s=l+1, + # v=(s+1)(st+1), k=s(t+1) + S=l+1 + T=mu-1 + q = (S+T)//2 + p, w = is_prime_power(q, get_data=True) + if (v == (S+1)*(S*T+1) and + k == S*(T+1) and + q == p**w and + (S+T)/2 == q): + if p % 2 == 0: + from sage.graphs.generators.classical_geometries\ + import T2starGeneralizedQuadrangleGraph as F + else: + from sage.graphs.generators.classical_geometries\ + import AhrensSzekeresGeneralizedQuadrangleGraph as F + if (S,T) == (q-1, q+1): + return (F, q, False) + elif (S,T) == (q+1, q-1): + return (F, q, True) + @cached_function def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): r""" @@ -966,6 +1043,72 @@ def is_taylor_twograph_srg(int v,int k,int l,int mu): return (TaylorTwographSRG, q) return +def is_switch_OA_srg(int v, int k, int l, int mu): + r""" + Test whether some *switch* `OA(k,n)+*` is `(v,k,\lambda,\mu)`-strongly regular. + + The "switch* `OA(k,n)+*` graphs appear on `Andries Brouwer's database + `__ and are built by + adding an isolated vertex to a + :meth:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`, + and a :meth:`Seidel switching ` a set of disjoint + `n`-cocliques. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: graphs.strongly_regular_graph(170, 78, 35, 36) # indirect doctest + Graph on 170 vertices + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import is_switch_OA_srg + sage: t = is_switch_OA_srg(5,5,5,5); t + sage: t = is_switch_OA_srg(170, 78, 35, 36); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (170, 78, 35, 36) + sage: t = is_switch_OA_srg(290, 136, 63, 64); + sage: t[0](*t[1:]).is_strongly_regular(parameters=True) + (290, 136, 63, 64) + sage: is_switch_OA_srg(626, 300, 143, 144) + (.switch_OA_srg at ..., 12, 25) + sage: is_switch_OA_srg(842, 406, 195, 196) + (.switch_OA_srg at ..., 14, 29) + + """ + from sage.combinat.designs.orthogonal_arrays import orthogonal_array + + cdef int n_2_p_1 = v + cdef int n = floor(sqrt(n_2_p_1-1)) + + if n*n != n_2_p_1-1: # is it a square? + return None + + cdef int c = k/n + if (k % n or + l != c*c-1 or + k != 1+(c-1)*(c+1)+(n-c)*(n-c-1) or + not orthogonal_array(c+1,n,existence=True,resolvable=True)): + return None + + def switch_OA_srg(c,n): + from itertools import izip + OA = map(tuple,orthogonal_array(c+1,n,resolvable=True)) + g = Graph([OA,lambda x,y: any(xx==yy for xx,yy in izip(x,y))],loops=False) + g.add_vertex(0) + g.seidel_switching(OA[:c*n]) + return g + + return (switch_OA_srg,c,n) + cdef eigenvalues(int v,int k,int l,int mu): r""" Return the eigenvalues of a (v,k,l,mu)-strongly regular graph. @@ -1218,6 +1361,59 @@ def SRG_176_105_68_54(): H = IncidenceStructure([x for x in W if 22 not in x]) return H.intersection_graph(3) +def SRG_210_99_48_45(): + r""" + Return a strongly regular graph with parameters `(210, 99, 48, 45)` + + This graph is from Example 4.2 in [KPRWZ10]_. One considers the action of + the symmetric group `S_7` on the 210 digraphs isomorphic to the + disjoint union of `K_1` and the circulant 6-vertex digraph + ``digraphs.Circulant(6,[1,4])``. It has 16 orbitals; the package [COCO]_ + found a megring of them, explicitly described in [KPRWZ10]_, resulting in + this graph. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_210_99_48_45 + sage: g=SRG_210_99_48_45() + sage: g.is_strongly_regular(parameters=True) + (210, 99, 48, 45) + + REFERENCES: + + .. [KPRWZ10] M. H. Klin, C. Pech, S. Reichard, A. Woldar, M. Zvi-Av, + Examples of computer experimentation in algebraic combinatorics, + ARS MATHEMATICA CONTEMPORANEA 3 (2010) 237–258 + http://amc-journal.eu/index.php/amc/article/viewFile/119/118 + + .. [COCO] I. A. Faradjev and M. H. Klin, + Computer package for computations with coherent configurations, + Proc. ISSAC-91, ACM Press, Bonn, 1991, pages 219–223; + code, by I.A.Faradjev (with contributions by A.E.Brouwer, D.V.Pasechnik) + https://github.com/dimpase/coco + + """ + from sage.libs.gap.libgap import libgap + from sage.combinat.permutation import Permutation + def ekg(g0): # return arcs of the Cayley digraph of on {g,g^4} + g = Permutation(g0) + return libgap.Set(map(lambda x: (x,g(x)), range(1,8))\ + + map(lambda x: (x,g(g(g(g(x))))), range(1,8))) + + kd=map(ekg, + [(7, 1, 2, 3, 4, 5), (7, 1, 3, 4, 5, 6), + (7, 3, 4, 5, 6, 2), (7, 1, 4, 3, 5, 6), + (7, 3, 1, 4, 5, 6), (7, 2, 4, 3, 5, 6), + (7, 3, 2, 4, 5, 1), (7, 2, 4, 3, 5, 1)]) + s=libgap.SymmetricGroup(7) + O=s.Orbit(kd[0],libgap.OnSetsTuples) + sa=s.Action(O,libgap.OnSetsTuples) + G=Graph() + for g in kd[1:]: + G.add_edges(libgap.Orbit(sa,[libgap.Position(O,kd[0]),\ + libgap.Position(O,g)],libgap.OnSets)) + return G + def SRG_243_110_37_60(): r""" Return a `(243, 110, 37, 60)`-strongly regular graph. @@ -1522,6 +1718,102 @@ def SRG_560_208_72_80(): h.relabel() return h +def strongly_regular_from_two_intersection_set(M): + r""" + Return a strongly regular graph from a 2-intersection set. + + A set of points in the projective geometry `PG(k,q)` is said to be a + 2-intersection set if it intersects every hyperplane in either `h_1` or + `h_2` points, where `h_1,h_2\in \\NN`. + + From a 2-intersection set `S` can be defined a strongly-regular graph in the + following way: + + - Place the points of `S` on a hyperplane `H` in `PG(k+1,q)` + + - Define the graph `G` on all points of `PG(k+1,q)\backslash H` + + - Make two points of `V(G)=PG(k+1,q)\backslash H` adjacent if the line going + through them intersects `S` + + For more information, see e.g. [CDB13]_ where this explanation has been + taken from. + + INPUT: + + - `M` -- a `|S| \times k` matrix with entries in `F_q` representing the points of + the 2-intersection set. We assume that the first non-zero entry of each row is + equal to `1`, that is, they give points in homogeneous coordinates. + + The implementation does not check that `S` is actually a 2-intersection set. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import strongly_regular_from_two_intersection_set + sage: S=Matrix([(0,0,1),(0,1,0)] + map(lambda x: (1,x^2,x), GF(4,'b'))) + sage: g=strongly_regular_from_two_intersection_set(S) + sage: g.is_strongly_regular(parameters=True) + (64, 18, 2, 6) + + REFERENCES: + + .. [CDB13] I. Cardinali and B. De Bruyn, + Spin-embeddings, two-intersection sets and two-weight codes, + Ars Comb. 109 (2013): 309-319. + https://biblio.ugent.be/publication/4241842/file/4241845.pdf + """ + from itertools import product, izip + K = M.base_ring() + k = M.ncols() + g = Graph() + + M = [list(p) for p in M] + + # For every point in F_q^{k+1} not on the hyperplane of M + for u in [tuple(x) for x in product(K,repeat=k)]: + # For every v point of M + for v in M: + # u is adjacent with all vertices on a uv line. + g.add_edges([[u,tuple([u[i]+qq*v[i] for i in range(k)])] \ + for qq in K if not qq==K.zero()]) + g.relabel() + return g + + +def SRG_729_336_153_156(): + r""" + Return a `(729, 336, 153, 156)`-strongly regular graph. + + This graph is built from a 2-intersection code shared by L. Disset in his + thesis [Disset00]_ and available at + http://www.mat.uc.cl/~ldissett/newgraphs/. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_336_153_156 + sage: G = SRG_729_336_153_156() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 336, 153, 156) + + REFERENCES: + + .. [Disset00] L. Dissett, + Combinatorial and computational aspects of finite geometries, + 2000, + https://tspace.library.utoronto.ca/bitstream/1807/14575/1/NQ49844.pdf + """ + L = [ + "101212212122202012010102120101112012121001201012120220122112001121201201201201010020012201001201201201202120121122012021201221021110200212121011211002012220000122201201", + "011100122001200111220011220020011222001200022000220012220122011220011101122012012001222010122200012011120112220112000120120012002012201122001220012122000201212001211211", + "000011111000011111112000001112000000111122222000001111112222000001111122222000111222222001111122222000001111112222000001112222000111122222000001111222000011122000011122", + "000000000111111111111000000000111111111111111222222222222222000000000000000111111111111222222222222000000000000000111111111111222222222222000000000000111111111222222222", + "000000000000000000000111111111111111111111111111111111111111000000000000000000000000000000000000000111111111111111111111111111111111111111222222222222222222222222222222", + "000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + ] + + L = Matrix(GF(3),map(list,L)).transpose() + return strongly_regular_from_two_intersection_set(L) + def SRG_729_532_391_380(): r""" Return a `(729, 532, 391, 380)`-strongly regular graph. @@ -2228,20 +2520,8 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint ( 27, 16, 10, 8): [SchlaefliGraph], ( 36, 14, 4, 6): [Graph,('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ'+ 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygSGkZ`OyHETdK[?lWStCapVgKK')], - ( 40, 12, 2, 4): [Graph,('g}iS[A@_S@OA_BWQIGaPCQE@CcQGcQECXAgaOdS@a'+ - 'CWEEAOIBH_HW?scb?f@GMBGGhIPGaQoh?q_bD_pGPq_WI`T_DBU?R_dECsSARGgogBO'+ - '{_IPBKZ?DI@Wgt_E?MPo{_?')], - ( 45, 12, 3, 3): [Graph,('l~}CKMF_C?oB_FPCGaICQOaH@DQAHQ@Ch?aJHAQ@G'+ - 'P_CQAIGcAJGO`IcGOY`@IGaGHGaKSCDI?gGDgGcE_@OQAg@PCSO_hOa`GIDADAD@XCI'+ - 'ASDKB?oKOo@_SHCc?SGcGd@A`B?bOOHGQH?ROQOW`?XOPa@C_hcGo`CGJK')], ( 50, 7, 0, 1): [HoffmanSingletonGraph], ( 56, 10, 0, 2): [SimsGewirtzGraph], - ( 64, 18, 2, 6): [Graph,('~?@?~aK[A@_[?O@_B_?O?K?B_?A??K??YQQPHGcQQ'+ - 'CaPIOHAX?POhAPIC`GcgSAHDE?PCiC@BCcDADIG_QCocS@AST?OOceGG@QGcKcdCbCB'+ - 'gIEHAScIDDOy?DAWaEg@IQO?maHPOhAW_dBCX?s@HOpKD@@GpOpHO?bCbHGOaGgpWQQ'+ - '?PDDDw@A_CSRIS_P?GeGpg`@?EOcaJGccbDC_dLAc_pHOe@`ocEGgo@sRo?WRAbAcPc'+ - '?iCiHEKBO_hOiOWpOSGSTBQCUAW_DDIWOqHBO?gghw_?`kOAXH?\\Ds@@@CpIDKOpc@'+ - 'OCoeIS_YOgGATGaqAhKGA?cqDOwQKGc?')], ( 77, 16, 0, 4): [M22Graph], ( 81, 50, 31, 30): [SRG_81_50_31_30], (100, 22, 0, 6): [HigmanSimsGraph], @@ -2258,6 +2538,7 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (176, 49, 12, 14): [SRG_176_49_12_14], (176, 105, 68, 54): [SRG_176_105_68_54], (196, 91, 42, 42): [SRG_196_91_42_42], + (210, 99, 48, 45): [SRG_210_99_48_45], (220, 84, 38, 28): [SRG_220_84_38_28], (231, 30, 9, 3): [CameronGraph], (243, 110, 37, 60): [SRG_243_110_37_60], @@ -2275,6 +2556,7 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (560, 208, 72, 80): [SRG_560_208_72_80], (625, 416, 279,272): [SRG_625_416_279_272], (625, 364, 213,210): [SRG_625_364_213_210], + (729, 336, 153,156): [SRG_729_336_153_156], (729, 616, 523,506): [SRG_729_616_523_506], (729, 420, 243,240): [SRG_729_420_243_240], (729, 560, 433,420): [SRG_729_560_433_420], @@ -2296,11 +2578,11 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint is_steiner, is_affine_polar, is_orthogonal_polar, is_NOodd, is_NOperp_F5, is_NO_F2, is_NO_F3, is_NU, - is_unitary_polar, - is_unitary_dual_polar, + is_unitary_polar, is_unitary_dual_polar, is_GQqmqp, is_RSHCD, is_twograph_descendant_of_srg, - is_taylor_twograph_srg] + is_taylor_twograph_srg, + is_switch_OA_srg] # Going through all test functions, for the set of parameters and its # complement. diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index c49325d699b..d1d61c93cc0 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -93,7 +93,6 @@ class AbelianGroupElement(AbelianGroupElementBase): sage: a*b in F True """ - def as_permutation(self): r""" Return the element of the permutation group G (isomorphic to the diff --git a/src/sage/groups/abelian_gps/element_base.py b/src/sage/groups/abelian_gps/element_base.py index ab2cff59587..1cf9c4a5a62 100644 --- a/src/sage/groups/abelian_gps/element_base.py +++ b/src/sage/groups/abelian_gps/element_base.py @@ -73,6 +73,16 @@ def __init__(self, parent, exponents): if len(self._exponents) != n: raise IndexError('argument length (= %s) must be %s.'%(len(exponents), n)) + def __hash__(self): + r""" + TESTS:: + + sage: F = AbelianGroup(3,[7,8,9]) + sage: hash(F.an_element()) # random + 1024 + """ + return hash(self.parent()) ^ hash(self._exponents) + def exponents(self): """ The exponents of the generators defining the group element. diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 51fa899443a..3bf8aef61e6 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -401,7 +401,7 @@ def __init__(self, cover, rels, gens): Additive abelian group isomorphic to Z/3 """ AdditiveAbelianGroup_class.__init__(self, cover, rels) - self._orig_gens = [self(x) for x in gens] + self._orig_gens = tuple(self(x) for x in gens) def gens(self): r""" @@ -416,7 +416,7 @@ def gens(self): sage: G.smith_form_gens() ((1, 2),) """ - return tuple(self._orig_gens) + return self._orig_gens def identity(self): r""" diff --git a/src/sage/groups/conjugacy_classes.py b/src/sage/groups/conjugacy_classes.py index 3553af8e8a0..d528090a295 100644 --- a/src/sage/groups/conjugacy_classes.py +++ b/src/sage/groups/conjugacy_classes.py @@ -180,7 +180,7 @@ def __iter__(self): sage: a = G(a) sage: C = ConjugacyClass(G, a) sage: it = iter(C) - sage: [next(it) for _ in range(5)] + sage: [next(it) for _ in range(5)] # random (nothing guarantees enumeration order) [ [1 1] [ 2 1] [ 0 1] [ 3 1] [ 3 4] [0 1], [-1 0], [-1 2], [-4 -1], [-1 -1] diff --git a/src/sage/groups/finitely_presented_named.py b/src/sage/groups/finitely_presented_named.py index 31214fc9751..88fd6df7420 100644 --- a/src/sage/groups/finitely_presented_named.py +++ b/src/sage/groups/finitely_presented_named.py @@ -130,14 +130,14 @@ def FinitelyGeneratedAbelianPresentation(int_list): sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) - Finitely presented group < a, b, c | a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) - Finitely presented group < a, b, c | a^2, b^2, a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > + Finitely presented group < a, b, c | a^2, b^2, a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > Outputs are reduced to minimal generators and relations:: @@ -193,7 +193,7 @@ def FinitelyGeneratedAbelianPresentation(int_list): ret_rls = [F([i+1])**invariants[i] for i in range(len(invariants)) if invariants[i]!=0] # Build commutator relations - gen_pairs = list(Set(F.gens()).subsets(2)) + gen_pairs = [[F.gen(i),F.gen(j)] for i in range(F.ngens()-1) for j in range(i+1,F.ngens())] ret_rls = ret_rls + [x[0]**(-1)*x[1]**(-1)*x[0]*x[1] for x in gen_pairs] return FinitelyPresentedGroup(F, tuple(ret_rls)) diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 0bdef462d65..45ef7a1c10c 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -227,6 +227,17 @@ def __init__(self, parent, x): x = AbstractWordTietzeWord(l, parent._gap_gens()) ElementLibGAP.__init__(self, parent, x) + def __hash__(self): + r""" + TESTS:: + + sage: G. = FreeGroup() + sage: hash(a*b*b*~a) + -485698212495963022 # 64-bit + -1876767630 # 32-bit + """ + return hash(self.Tietze()) + def _latex_(self): """ Return a LaTeX representation diff --git a/src/sage/groups/matrix_gps/group_element.py b/src/sage/groups/matrix_gps/group_element.py index 5c0ae88326b..434c7ce8027 100644 --- a/src/sage/groups/matrix_gps/group_element.py +++ b/src/sage/groups/matrix_gps/group_element.py @@ -117,12 +117,23 @@ class MatrixGroupElement_base(MultiplicativeGroupElement): EXAMPLES:: sage: F = GF(3); MS = MatrixSpace(F,2,2) - sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] + sage: gens = [MS([[1,0],[0,1]]),MS([[1,1],[0,1]])] sage: G = MatrixGroup(gens) sage: g = G.random_element() sage: type(g) """ + def __hash__(self): + r""" + TESTS:: + + sage: MS = MatrixSpace(GF(3), 2) + sage: G = MatrixGroup([MS([1,1,0,1]), MS([1,0,1,1])]) + sage: g = G.an_element() + sage: hash(g) + 0 + """ + return hash(self.matrix()) def _repr_(self): """ diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index c8c188552f6..534340fbc16 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -775,7 +775,7 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): sage: l=designs.ProjectiveGeometryDesign(2,1,GF(4,name='a')) sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) - sage: SimplicialComplex(from_characteristic_function=(f, range(21))) + sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) Simplicial complex with 21 vertices and 168 facets TESTS: diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py index 24fd9087940..d4d45b60d9b 100644 --- a/src/sage/interfaces/chomp.py +++ b/src/sage/interfaces/chomp.py @@ -126,10 +126,10 @@ def __call__(self, program, complex, subcomplex=None, **kwds): EXAMPLES:: - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP - {0: 0, 1: Z x Z, 2: Z} + sage: from sage.interfaces.chomp import CHomP + sage: T = cubical_complexes.Torus() + sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP + {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index df08d363fe9..c599690cfc3 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -94,9 +94,8 @@ sage: a = octave.eval(t + ';') # optional - octave, < 1/100th of a second sage: a = octave(t) # optional - octave -Note that actually reading a back out takes forever. This *must* -be fixed ASAP - see -http://trac.sagemath.org/sage_trac/ticket/940/. +Note that actually reading ``a`` back out takes forever. This *must* +be fixed as soon as possible, see :trac:`940`. Tutorial -------- @@ -286,7 +285,7 @@ def quit(self, verbose=False): # to signals. if not self._expect is None: if verbose: - print "Exiting spawned %s process."%self + print "Exiting spawned %s process." % self return def _start(self): @@ -308,9 +307,38 @@ def _start(self): # set random seed self.set_seed(self._seed) + def _equality_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + sage: octave('1 == 1') # optional - octave + 1 + """ + return '==' + + def _true_symbol(self): + """ + EXAMPLES:: + + sage: octave('1 == 1') # optional - octave + 1 + """ + return '1' + + def _false_symbol(self): + """ + EXAMPLES:: + + sage: octave('0 == 1') # optional - octave + 0 + """ + return '0' + def set(self, var, value): """ - Set the variable var to the given value. + Set the variable ``var`` to the given ``value``. EXAMPLES:: @@ -325,7 +353,7 @@ def set(self, var, value): def get(self, var): """ - Get the value of the variable var. + Get the value of the variable ``var``. EXAMPLES:: @@ -344,9 +372,9 @@ def clear(self, var): EXAMPLES:: sage: octave.set('x', '2') # optional - octave - sage: octave.clear('x') # optional - octave - sage: octave.get('x') # optional - octave - "error: `x' undefined near line ... column 1" + sage: octave.clear('x') # optional - octave + sage: octave.get('x') # optional - octave + "error: 'x' undefined near line ... column 1" """ self.eval('clear %s'%var) @@ -387,18 +415,16 @@ def version(self): return octave_version() def solve_linear_system(self, A, b): - """ + r""" Use octave to compute a solution x to A\*x = b, as a list. INPUT: + - ``A`` -- mxn matrix A with entries in `\QQ` or `\RR` - - ``A`` - mxn matrix A with entries in QQ or RR + - ``b`` -- m-vector b entries in `\QQ` or `\RR` (resp) - - ``b`` - m-vector b entries in QQ or RR (resp) - - - OUTPUT: An list x (if it exists) which solves M\*x = b + OUTPUT: A list x (if it exists) which solves M\*x = b EXAMPLES:: @@ -507,33 +533,211 @@ def _object_class(self): return OctaveElement +octave_functions = set() + +def to_complex(octave_string, R): + r""" + Helper function to convert octave complex number + + TESTS:: + + sage: from sage.interfaces.octave import to_complex + sage: to_complex('(0,1)', CDF) + 1.0*I + sage: to_complex('(1.3231,-0.2)', CDF) + 1.3231 - 0.2*I + """ + real, imag = octave_string.strip('() ').split(',') + return R(float(real), float(imag)) + class OctaveElement(ExpectElement): - def _matrix_(self, R): + def _get_sage_ring(self): + r""" + TESTS:: + + sage: octave('1')._get_sage_ring() # optional - octave + Real Double Field + sage: octave('I')._get_sage_ring() # optional - octave + Complex Double Field + sage: octave('[]')._get_sage_ring() # optional - octave + Real Double Field + """ + if self.isinteger(): + import sage.rings.integer_ring + return sage.rings.integer_ring.ZZ + elif self.isreal(): + import sage.rings.real_double + return sage.rings.real_double.RDF + elif self.iscomplex(): + import sage.rings.complex_double + return sage.rings.complex_double.CDF + else: + raise TypeError("no Sage ring associated to this element.") + + def __nonzero__(self): + r""" + Test whether this element is nonzero. + + EXAMPLES:: + + sage: bool(octave('0')) # optional - octave + False + sage: bool(octave('[]')) # optional - octave + False + sage: bool(octave('[0,0]')) # optional - octave + False + sage: bool(octave('[0,0,0;0,0,0]')) # optional - octave + False + + sage: bool(octave('0.1')) # optional - octave + True + sage: bool(octave('[0,1,0]')) # optional - octave + True + sage: bool(octave('[0,0,-0.1;0,0,0]')) # optional - octave + True + """ + return str(self) != ' [](0x0)' and any(x != '0' for x in str(self).split()) + + def _matrix_(self, R=None): r""" Return Sage matrix from this octave element. EXAMPLES:: + sage: A = octave('[1,2;3,4.5]') # optional - octave + sage: matrix(A) # optional - octave + [1.0 2.0] + [3.0 4.5] + sage: _.base_ring() # optional - octave + Real Double Field + + sage: A = octave('[I,1;-1,0]') # optional - octave + sage: matrix(A) # optional - octave + [1.0*I 1.0] + [ -1.0 0.0] + sage: _.base_ring() # optional - octave + Complex Double Field + sage: A = octave('[1,2;3,4]') # optional - octave sage: matrix(ZZ, A) # optional - octave [1 2] [3 4] - sage: A = octave('[1,2;3,4.5]') # optional - octave - sage: matrix(RR, A) # optional - octave - [1.00000000000000 2.00000000000000] - [3.00000000000000 4.50000000000000] """ + oc = self.parent() + if not self.ismatrix(): + raise TypeError('not an octave matrix') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = [u.strip().split(' ') for u in s.split('\n')] + nrows = len(w) + ncols = len(w[0]) + + if self.iscomplex(): + w = [[to_complex(x,R) for x in row] for row in w] + from sage.matrix.all import MatrixSpace - s = str(self).strip() - v = s.split('\n ') - nrows = len(v) - if nrows == 0: - return MatrixSpace(R,0,0)(0) - ncols = len(v[0].split()) - M = MatrixSpace(R, nrows, ncols) - v = sum([[x for x in w.split()] for w in v], []) - return M(v) + return MatrixSpace(R, nrows, ncols)(w) + + def _vector_(self, R=None): + r""" + Return Sage vector from this octave element. + + EXAMPLES:: + + sage: A = octave('[1,2,3,4]') # optional - octave + sage: vector(ZZ, A) # optional - octave + (1, 2, 3, 4) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,I]') # optional - octave + sage: vector(A) # optional - octave + (1.0, 1.0*I) + """ + oc = self.parent() + if not self.isvector(): + raise TypeError('not an octave vector') + if R is None: + R = self._get_sage_ring() + + s = str(self).strip('\n ') + w = s.strip().split(' ') + nrows = len(w) + + if self.iscomplex(): + w = [to_complex(x, R) for x in w] + + from sage.modules.free_module import FreeModule + return FreeModule(R, nrows)(w) + + def _scalar_(self): + """ + Return Sage scalar from this octave element. + + EXAMPLES:: + + sage: A = octave('2833') # optional - octave + sage: As = A.sage(); As # optional - octave + 2833.0 + sage: As.parent() # optional - octave + Real Double Field + + sage: B = sqrt(A) # optional - octave + sage: Bs = B.sage(); Bs # optional - octave + 53.2259 + sage: Bs.parent() # optional - octave + Real Double Field + + sage: C = sqrt(-A) # optional - octave + sage: Cs = C.sage(); Cs # optional - octave + 53.2259*I + sage: Cs.parent() # optional - octave + Complex Double Field + """ + if not self.isscalar(): + raise TypeError("not an octave scalar") + + R = self._get_sage_ring() + if self.iscomplex(): + return to_complex(str(self), R) + else: + return R(str(self)) + + def _sage_(self): + """ + Try to parse the octave object and return a sage object. + + EXAMPLES:: + sage: A = octave('2833') # optional - octave + sage: A.sage() # optional - octave + 2833.0 + sage: B = sqrt(A) # optional - octave + sage: B.sage() # optional - octave + 53.2259 + sage: C = sqrt(-A) # optional - octave + sage: C.sage() # optional - octave + 53.2259*I + sage: A = octave('[1,2,3,4]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.0, 3.0, 4.0) + sage: A = octave('[1,2.3,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3, 4.5) + sage: A = octave('[1,2.3+I,4.5]') # optional - octave + sage: A.sage() # optional - octave + (1.0, 2.3 + 1.0*I, 4.5) + """ + if self.isscalar(): + return self._scalar_() + elif self.isvector(): + return self._vector_() + elif self.ismatrix(): + return self._matrix_() + else: + raise NotImplementedError('octave type is not recognized') # An instance octave = Octave() diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index 0795ca7eec7..1462030975e 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -402,6 +402,22 @@ def _rich_repr_(self, display_manager, **kwds): """ return None + def _repr_option(self, option): + """ + Disable repr option. + + This is necessary because otherwise our :meth:`__getattr__` + would be called. + + EXAMPLES:: + + sage: from sage.repl.rich_output import get_display_manager + sage: m = sage0(4) + sage: m._repr_option('ascii_art') + False + """ + return False + def __getattr__(self, attrname): """ EXAMPLES:: diff --git a/src/sage/libs/pari/paridecl.pxd b/src/sage/libs/pari/paridecl.pxd index fac8f363ccd..8e039a6084a 100644 --- a/src/sage/libs/pari/paridecl.pxd +++ b/src/sage/libs/pari/paridecl.pxd @@ -1435,6 +1435,7 @@ cdef extern from "sage/libs/pari/parisage.h": long fetch_var_higher() GEN fetch_var_value(long vx, GEN t) GEN gp_read_str(char *t) + GEN gp_read_str_multiline(char *t) entree* install(void *f, char *name, char *code) entree* is_entry(char *s) void kill0(char *e) @@ -2203,7 +2204,7 @@ cdef extern from "sage/libs/pari/parisage.h": # classpoly.c - GEN polclass(GEN D, long xvar) + GEN polclass(GEN D, long inv, long xvar) # compile.c @@ -2216,8 +2217,8 @@ cdef extern from "sage/libs/pari/parisage.h": # concat.c - GEN concat(GEN x, GEN y) - GEN concat1(GEN x) + GEN gconcat(GEN x, GEN y) + GEN gconcat1(GEN x) GEN matconcat(GEN v) GEN shallowconcat(GEN x, GEN y) GEN shallowconcat1(GEN x) @@ -3181,7 +3182,101 @@ cdef extern from "sage/libs/pari/parisage.h": GEN rnfkummer(GEN bnr, GEN subgroup, long all, long prec) + # lfun.c + + long is_linit(GEN data) + GEN ldata_get_an(GEN ldata) + long ldata_get_selfdual(GEN ldata) + long ldata_isreal(GEN ldata) + GEN ldata_get_gammavec(GEN ldata) + long ldata_get_degree(GEN ldata) + long ldata_get_k(GEN ldata) + GEN ldata_get_conductor(GEN ldata) + GEN ldata_get_rootno(GEN ldata) + GEN ldata_get_residue(GEN ldata) + GEN ldata_vecan(GEN ldata, long L, long prec) + long ldata_get_type(GEN ldata) + long linit_get_type(GEN linit) + GEN linit_get_ldata(GEN linit) + GEN linit_get_tech(GEN linit) + GEN lfun_get_domain(GEN tech) + GEN lfun_get_dom(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun_get_factgammavec(GEN tech) + GEN lfun_get_step(GEN tech) + GEN lfun_get_pol(GEN tech) + GEN lfun_get_Residue(GEN tech) + GEN lfun_get_k2(GEN tech) + GEN lfun_get_w2(GEN tech) + GEN lfun_get_expot(GEN tech) + long lfun_get_der(GEN tech) + long lfun_get_bitprec(GEN tech) + GEN lfun(GEN ldata, GEN s, long prec) + GEN lfun_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfun0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfun0(GEN ldata, GEN s, long der, long prec) + long lfuncheckfeq(GEN data, GEN t0, long prec) + long lfuncheckfeq_bitprec(GEN data, GEN t0, long bitprec) + GEN lfunconductor(GEN data, GEN maxcond, long flag, long prec) + GEN lfuncreate(GEN obj) + GEN lfunan(GEN ldata, long L, long prec) + GEN lfunhardy(GEN ldata, GEN t, long prec) + GEN lfunhardy_bitprec(GEN ldata, GEN t, long bitprec) + GEN lfuninit(GEN ldata, GEN dom, long der, long prec) + GEN lfuninit_bitprec(GEN ldata, GEN dom, long der, long bitprec) + GEN lfuninit_make(long t, GEN ldata, GEN molin, GEN domain) + long lfunisvgaell(GEN Vga, long flag) + GEN lfunlambda(GEN ldata, GEN s, long prec) + GEN lfunlambda_bitprec(GEN ldata, GEN s, long bitprec) + GEN lfunlambda0(GEN ldata, GEN s, long der, long prec) + GEN lfunlambda0_bitprec(GEN ldata, GEN s, long der, long bitprec) + GEN lfunmisc_to_ldata(GEN ldata) + GEN lfunmisc_to_ldata_shallow(GEN ldata) + long lfunorderzero(GEN ldata, long prec) + long lfunorderzero_bitprec(GEN ldata, long bitprec) + GEN lfunprod_get_fact(GEN tech) + GEN lfunrootno(GEN data, long prec) + GEN lfunrootno_bitprec(GEN data, long bitprec) + GEN lfunrootres(GEN data, long prec) + GEN lfunrootres_bitprec(GEN data, long bitprec) + GEN lfunrtopoles(GEN r) + GEN lfuntheta(GEN data, GEN t, long m, long prec) + GEN lfuntheta_bitprec(GEN data, GEN t, long m, long bitprec) + GEN lfunthetainit(GEN ldata, GEN tinf, long m, long prec) + GEN lfunthetainit_bitprec(GEN ldata, GEN tdom, long m, long bitprec) + GEN lfunthetacheckinit(GEN data, GEN tinf, long m, long *ptbitprec, long fl) + GEN lfunzeros(GEN ldata, GEN lim, long divz, long prec) + GEN lfunzeros_bitprec(GEN ldata, GEN lim, long divz, long bitprec) + int sdomain_isincl(GEN dom, GEN dom0) + GEN theta_get_an(GEN tdata) + GEN theta_get_K(GEN tdata) + GEN theta_get_R(GEN tdata) + long theta_get_bitprec(GEN tdata) + long theta_get_m(GEN tdata) + GEN theta_get_tdom(GEN tdata) + GEN theta_get_sqrtN(GEN tdata) + + # lfunutils.c + + GEN dirzetak(GEN nf, GEN b) + GEN ellmoddegree(GEN e, long prec) + GEN lfunabelianrelinit(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long prec) + GEN lfunabelianrelinit_bitprec(GEN bnfabs, GEN bnf, GEN polrel, GEN dom, long der, long bitprec) + GEN lfunconvol(GEN a1, GEN a2) + GEN lfundiv(GEN ldata1, GEN ldata2, long prec) + GEN lfunzetakinit(GEN pol, GEN dom, long der, long flag, long prec) + GEN lfunzetakinit_bitprec(GEN pol, GEN dom, long der, long flag, long bitprec) + GEN lfunetaquo(GEN ldata) + GEN lfunmfspec(GEN ldata, long prec) + GEN lfunmfpeters(GEN ldata, long prec) + GEN lfunmfpeters_bitprec(GEN ldata, long bitprec) + GEN lfunmul(GEN ldata1, GEN ldata2, long prec) + GEN lfunqf(GEN ldata) + GEN lfunsymsq(GEN ldata, GEN known, long prec) + GEN lfunsymsqspec(GEN ldata, long prec) + # lll.c + GEN ZM_lll_norms(GEN x, double D, long flag, GEN *B) GEN kerint(GEN x) GEN lll(GEN x) diff --git a/src/sage/libs/singular/groebner_strategy.pyx b/src/sage/libs/singular/groebner_strategy.pyx index 231cce0ed21..b4c2be95922 100644 --- a/src/sage/libs/singular/groebner_strategy.pyx +++ b/src/sage/libs/singular/groebner_strategy.pyx @@ -312,7 +312,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) - sage: NCGroebnerStrategy(I) + sage: NCGroebnerStrategy(I) #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} TESTS:: @@ -412,7 +412,7 @@ cdef class NCGroebnerStrategy(SageObject): sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()]) sage: strat = NCGroebnerStrategy(I) - sage: strat # indirect doctest + sage: strat # indirect doctest #random Groebner Strategy for ideal generated by 3 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} """ return "Groebner Strategy for ideal generated by %d elements over %s"%(self._ideal.ngens(),self._parent) diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index be72592dd1f..6e1b3014f6f 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -625,6 +625,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: r1 = A*v1 sage: r0.column(0) == r1 True + + TESTS: + + Check that :trac:`19378` is fixed:: + + sage: m = matrix(GF(2), 11, 0) + sage: v = vector(GF(2), 0) + sage: m * v + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) """ cdef mzd_t *tmp if not isinstance(v, Vector_mod2_dense): @@ -634,6 +643,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse raise ArithmeticError("number of columns of matrix must equal degree of vector") VS = VectorSpace(self._base_ring, self._nrows) + # If the vector is 0-dimensional, the result will be the 0-vector + if not self.ncols(): + return VS.zero() cdef Vector_mod2_dense c = Vector_mod2_dense.__new__(Vector_mod2_dense) c._init(self._nrows, VS) c._entries = mzd_init(1, self._nrows) diff --git a/src/sage/matrix/operation_table.py b/src/sage/matrix/operation_table.py index eca698fd93d..e76a66c0ab8 100644 --- a/src/sage/matrix/operation_table.py +++ b/src/sage/matrix/operation_table.py @@ -419,16 +419,28 @@ def __init__(self, S, operation, names='letters', elements=None): # If not, we'll discover that next in actual use. self._table = [] + + # the elements might not be hashable. But if they are it is much + # faster to lookup in a hash table rather than in a list! + try: + get_row = {e: i for i,e in enumerate(self._elts)}.__getitem__ + except TypeError: + get_row = self._elts.index + for g in self._elts: row = [] for h in self._elts: try: result = self._operation(g, h) - row.append(self._elts.index(result)) - except ValueError: # list/index condition - raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) except Exception: raise TypeError('elements %s and %s of %s are incompatible with operation: %s' % (g,h,S,self._operation)) + + try: + r = get_row(result) + except (KeyError,ValueError): + raise ValueError('%s%s%s=%s, and so the set is not closed' % (g, self._ascii_symbol, h, result)) + + row.append(r) self._table.append(row) def _name_maker(self, names): @@ -559,8 +571,8 @@ def __getitem__(self, pair): TESTS:: sage: from sage.matrix.operation_table import OperationTable - sage: G=DiCyclicGroup(3) - sage: T=OperationTable(G, operator.mul) + sage: G = DiCyclicGroup(3) + sage: T = OperationTable(G, operator.mul) sage: T[G('(1,2)(3,4)(5,6,7)')] Traceback (most recent call last): ... diff --git a/src/sage/matroids/lean_matrix.pyx b/src/sage/matroids/lean_matrix.pyx index 08f0db2ee3a..2c920b21519 100644 --- a/src/sage/matroids/lean_matrix.pyx +++ b/src/sage/matroids/lean_matrix.pyx @@ -741,7 +741,7 @@ cdef class GenericMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: @@ -1039,8 +1039,8 @@ cdef class BinaryMatrix(LeanMatrix): bitset_free(self._temp) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -1666,8 +1666,8 @@ cdef class TernaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2241,8 +2241,8 @@ cdef class QuaternaryMatrix(LeanMatrix): bitset_free(self._u) def __repr__(self): - """ - Print representation string + r""" + Return representation string EXAMPLES:: @@ -2829,7 +2829,7 @@ cdef class IntegerMatrix(LeanMatrix): def __repr__(self): """ - Print representation. + Return representation. EXAMPLES:: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index bd705e8f433..5309ccee5d6 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -5575,9 +5575,10 @@ cdef class Matroid(SageObject): ``True`` if the matroid is 3-connected, ``False`` otherwise. INPUT: + - ``basis`` -- a basis of the matroid. - ``fund_cocircuits`` -- a iterable of some fundamental cocircuits with - respect to ``basis``. It must contain all separating fundamental cocircuits. + respect to ``basis``. It must contain all separating fundamental cocircuits. OUTPUT: @@ -5598,8 +5599,8 @@ cdef class Matroid(SageObject): .. NOTE:: - The function does not check its input at all. You may want to make - sure the matroid is both simple and cosimple. + The function does not check its input at all. You may want to make + sure the matroid is both simple and cosimple. """ # Step 1: base case if self.rank() <= 2: @@ -6731,6 +6732,8 @@ cdef class Matroid(SageObject): True sage: sum(map(len,P))==len(M.groundset()) True + sage: Matroid(matrix([])).partition() + [] ALGORITHM: @@ -6741,7 +6744,8 @@ cdef class Matroid(SageObject): from sage.matroids.union_matroid import MatroidSum, PartitionMatroid if self.loops(): raise ValueError("Cannot partition matroids with loops.") - + if self.size()==0: + return [] # doubling search for minimum independent sets that partitions the groundset n = self.size() r = self.rank() diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index cb1c4fe2e18..dbf8c5f19ac 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -326,9 +326,9 @@ For a typical category, few bases, if any, need to be added to force sage: x.mro == x.mro_standard False sage: x.all_bases_len() - 82 + 90 sage: x.all_bases_controlled_len() - 89 + 97 The following can be used to search through the Sage named categories for any that requires the addition of some bases:: diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 01ee022b37d..8fd9e67f6ff 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -283,7 +283,7 @@ def pyx_preparse(s): def cython(filename, verbose=False, compile_message=False, use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True, create_local_so_file=False): - """ + r""" Compile a Cython file. This converts a Cython file to a C (or C++ file), and then compiles that. The .c file and the .so file are created in a temporary directory. @@ -341,6 +341,11 @@ def cython(filename, verbose=False, compile_message=False, sage: x x^2 + Check that compiling c++ code works:: + + sage: cython("#clang C++\n"+ + ....: "from libcpp.vector cimport vector\n" + ....: "cdef vector[int] * v = new vector[int](4)\n") """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -454,7 +459,6 @@ def cython(filename, verbose=False, compile_message=False, include_dirs = %s) """%(extra_args, name, name, extension, additional_source_files, libs, language, includes) open('%s/setup.py'%build_dir,'w').write(setup) - cython_include = ' '.join(["-I '%s'"%x for x in includes if len(x.strip()) > 0 ]) options = ['-p'] @@ -463,7 +467,12 @@ def cython(filename, verbose=False, compile_message=False, if sage_namespace: options.append('--pre-import sage.all') - cmd = "cd '%s' && cython %s %s '%s.pyx' 1>log 2>err " % (build_dir, ' '.join(options), cython_include, name) + cmd = "cd '{DIR}' && cython {OPT} {INC} {LANG} '{NAME}.pyx' 1>log 2>err ".format( + DIR=build_dir, + OPT=' '.join(options), + INC=cython_include, + LANG='--cplus' if language=='c++' else '', + NAME=name) if create_local_c_file: target_c = '%s/_%s.c'%(os.path.abspath(os.curdir), base) @@ -481,9 +490,6 @@ def cython(filename, verbose=False, compile_message=False, err = subtract_from_line_numbers(open('%s/err'%build_dir).read(), offset) raise RuntimeError("Error converting {} to C:\n{}\n{}".format(filename, log, err)) - if language=='c++': - os.system("cd '%s' && mv '%s.c' '%s.cpp'"%(build_dir,name,name)) - cmd = 'cd %s && python setup.py build 1>log 2>err'%build_dir if verbose: print(cmd) diff --git a/src/sage/misc/rest_index_of_methods.py b/src/sage/misc/rest_index_of_methods.py index 3800ff7f86a..c928f036c62 100644 --- a/src/sage/misc/rest_index_of_methods.py +++ b/src/sage/misc/rest_index_of_methods.py @@ -7,7 +7,9 @@ {INDEX_OF_FUNCTIONS} """ -def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): +from sage.misc.sageinspect import _extract_embedded_position + +def gen_rest_table_index(list_of_entries, names=None, sort=True, only_local_functions=True): r""" Return a ReST table describing a list of functions. @@ -24,6 +26,10 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): deprecated or those starting with an underscore. In the case of a class, note that inherited methods are not displayed. + - ``names`` -- a dictionary associating a name to a function. Takes + precedence over the automatically computed name for the functions. Only + used when ``list_of_entries`` is a list. + - ``sort`` (boolean; ``True``) -- whether to sort the list of methods lexicographically. @@ -57,7 +63,12 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): :widths: 30, 70 :delim: @ - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions... + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + + The table of a class:: @@ -104,33 +115,43 @@ def gen_rest_table_index(list_of_entries, sort=True, only_local_functions=True): :widths: 30, 70 :delim: @ - :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions... + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_rest_table_index` @ Return a ReST table describing a list of functions. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + + sage: print gen_rest_table_index(sage.misc.rest_index_of_methods, only_local_functions=False) .. csv-table:: :class: contentstable :widths: 30, 70 :delim: @ + + :func:`~sage.misc.rest_index_of_methods.doc_index` @ Attribute an index name to a function. + :func:`~sage.misc.rest_index_of_methods.gen_thematic_rest_table_index` @ Return a ReST string of thematically sorted function (or methods) of a module (or class). + :func:`~sage.misc.rest_index_of_methods.list_of_subfunctions` @ Returns the functions (resp. methods) of a given module (resp. class) with their names. + A function that is imported into a class under a different name is listed + under its 'new' name:: + + sage: 'cliques_maximum' in gen_rest_table_index(Graph) + True + sage: 'all_max_cliques`' in gen_rest_table_index(Graph) + False """ import inspect + if names is None: + names = {} # If input is a class/module, we list all its non-private and methods/functions if (inspect.isclass(list_of_entries) or inspect.ismodule(list_of_entries)): root = list_of_entries - def local_filter(f,name): - if only_local_functions: - return inspect.getmodule(root) == inspect.getmodule(f) - else: - return inspect.isclass(list_of_entries) or not (f is gen_rest_table_index) - list_of_entries = [getattr(root,name) for name,f in root.__dict__.items() if - (not name.startswith('_') and # private functions - not hasattr(f,'trac_number') and # deprecated functions - not inspect.isclass(f) and # classes - local_filter(f,name) # possibly filter imported functions - )] + list_of_entries,names = list_of_subfunctions(root, only_local_functions=only_local_functions) + + fname = lambda x:names.get(x,getattr(x,"__name__","")) assert isinstance(list_of_entries,list) @@ -140,20 +161,26 @@ def local_filter(f,name): " :delim: @\n\n") if sort: - list_of_entries.sort(key=lambda x:getattr(x,'__name__','')) + list_of_entries.sort(key=fname) for e in list_of_entries: if inspect.ismethod(e): - link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+e.__name__+"`" + link = ":meth:`~"+str(e.im_class.__module__)+"."+str(e.im_class.__name__)+"."+fname(e)+"`" elif inspect.isfunction(e): - link = ":func:`~"+str(e.__module__)+"."+str(e.__name__)+"`" + link = ":func:`~"+str(e.__module__)+"."+fname(e)+"`" else: continue + # Extract lines injected by cython + doc = e.__doc__ + doc_tmp = _extract_embedded_position(doc) + if doc_tmp: + doc = doc_tmp[0] + # Descriptions of the method/function - if e.__doc__: - desc = e.__doc__.split('\n\n')[0] # first paragraph + if doc: + desc = doc.split('\n\n')[0] # first paragraph desc = " ".join([x.strip() for x in desc.splitlines()]) # concatenate lines desc = desc.strip() # remove leading spaces else: @@ -163,4 +190,120 @@ def local_filter(f,name): return s+'\n' +def list_of_subfunctions(root, only_local_functions=True): + r""" + Returns the functions (resp. methods) of a given module (resp. class) with their names. + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + OUTPUT: + + A pair ``(list,dict)`` where ``list`` is a list of function/methods and + ``dict`` associates to every function/method the name under which it appears + in ``root``. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + import inspect + if inspect.ismodule(root): + ismodule = True + elif inspect.isclass(root): + ismodule = False + superclasses = inspect.getmro(root)[1:] + else: + raise ValueError("'root' must be a module or a class.") + + def local_filter(f,name): + if only_local_functions: + if ismodule: + return inspect.getmodule(root) == inspect.getmodule(f) + else: + return not any(hasattr(s,name) for s in superclasses) + else: + return inspect.isclass(root) or not (f is gen_rest_table_index) + + functions = {getattr(root,name):name for name,f in root.__dict__.items() if + (not name.startswith('_') and # private functions + not hasattr(f,'trac_number') and # deprecated functions + not inspect.isclass(f) and # classes + callable(f) and # e.g. GenericGraph.graphics_array_defaults + local_filter(f,name)) # possibly filter imported functions + } + return functions.keys(),functions + +def gen_thematic_rest_table_index(root,additional_categories=None,only_local_functions=True): + r""" + Return a ReST string of thematically sorted function (or methods) of a module (or class). + + INPUT: + + - ``root`` -- the module, or class, whose elements are to be listed. + + - ``additional_categories`` -- a dictionary associating a category (given as + a string) to a function's name. Can be used when the decorator + :func:`doc_index` does not work on a function. + + - ``only_local_functions`` (boolean; ``True``) -- if ``root`` is a module, + ``only_local_functions = True`` means that imported functions will be + filtered out. This can be useful to disable for making indexes of + e.g. catalog modules such as :mod:`sage.coding.codes_catalog`. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import gen_thematic_rest_table_index, list_of_subfunctions + sage: l = list_of_subfunctions(Graph)[0] + sage: Graph.bipartite_color in l + True + """ + from collections import defaultdict + if additional_categories is None: + additional_categories = {} + + functions,names = list_of_subfunctions(root,only_local_functions=only_local_functions) + theme_to_function = defaultdict(list) + for f in functions: + theme_to_function[getattr(f,"doc_index",additional_categories.get(f,"Unsorted"))].append(f) + s = ["**"+theme+"**\n\n"+gen_rest_table_index(list_of_functions,names=names) + for theme, list_of_functions in sorted(theme_to_function.items())] + return "\n\n".join(s) + +def doc_index(name): + r""" + Attribute an index name to a function. + + This decorator can be applied to a function/method in order to specify in + which index it must appear, in the index generated by + :func:`gen_thematic_rest_table_index`. + + INPUT: + + - ``name`` -- a string, which will become the title of the index in which + this function/method will appear. + + EXAMPLE:: + + sage: from sage.misc.rest_index_of_methods import doc_index + sage: @doc_index("Wouhouuuuu") + ....: def a(): + ....: print "Hey" + sage: a.doc_index + 'Wouhouuuuu' + """ + def hey(f): + setattr(f,"doc_index",name) + return f + return hey + __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index([gen_rest_table_index])) diff --git a/src/sage/misc/superseded.py b/src/sage/misc/superseded.py index 6c13f92a80a..c676ee410c2 100644 --- a/src/sage/misc/superseded.py +++ b/src/sage/misc/superseded.py @@ -274,12 +274,13 @@ class DeprecatedFunctionAlias(object): - Florent Hivert (2009-11-23), with the help of Mike Hansen. - Luca De Feo (2011-07-11), printing the full module path when different from old path """ - def __init__(self, trac_number, func, module): + def __init__(self, trac_number, func, module, instance = None, unbound = None): r""" TESTS:: sage: from sage.misc.superseded import deprecated_function_alias sage: g = deprecated_function_alias(13109, number_of_partitions) + sage: from sage.misc.superseded import deprecated_function_alias sage: g.__doc__ 'Deprecated: Use :func:`number_of_partitions` instead.\nSee :trac:`13109` for details.\n\n' """ @@ -290,7 +291,8 @@ def __init__(self, trac_number, func, module): pass # Cython classes don't have __dict__ self.func = func self.trac_number = trac_number - self.instance = None # for use with methods + self.instance = instance # for use with methods + self.unbound = unbound self.__module__ = module if isinstance(func, type(deprecation)): sphinxrole = "func" @@ -315,7 +317,8 @@ def __name__(self): sage: class cls(object): ....: def new_meth(self): return 42 ....: old_meth = deprecated_function_alias(13109, new_meth) - ....: + sage: cls.old_meth.__name__ + 'old_meth' sage: cls().old_meth.__name__ 'old_meth' @@ -344,11 +347,12 @@ def is_class(gc_ref): is_python_class = '__module__' in gc_ref or '__package__' in gc_ref is_cython_class = '__new__' in gc_ref return is_python_class or is_cython_class - for ref in gc.get_referrers(self): - if is_class(ref): + search_for = self if (self.unbound is None) else self.unbound + for ref in gc.get_referrers(search_for): + if is_class(ref) and ref is not self.__dict__: ref_copy = copy.copy(ref) for key, val in ref_copy.iteritems(): - if val is self: + if val is search_for: return key raise AttributeError("The name of this deprecated function can not be determined") @@ -387,9 +391,27 @@ def __get__(self, inst, cls = None): sage: obj = cls() sage: obj.old_meth.instance is obj True + + :trac:`19125`:: + + sage: from sage.misc.superseded import deprecated_function_alias + sage: class A: + ....: def __init__(self, x): + ....: self.x = x + ....: def f(self, y): + ....: return self.x+y + ....: g = deprecated_function_alias(42, f) + sage: a1 = A(1) + sage: a2 = A(2) + sage: a1.g(a2.g(0)) + doctest:...: DeprecationWarning: g is deprecated. Please use f instead. + See http://trac.sagemath.org/42 for details. + 3 + sage: a1.f(a2.f(0)) + 3 + """ - self.instance = inst - return self + return self if (inst is None) else DeprecatedFunctionAlias(self.trac_number, self.func, self.__module__, instance = inst, unbound = self) def deprecated_function_alias(trac_number, func): diff --git a/src/sage/modular/modform_hecketriangle/abstract_ring.py b/src/sage/modular/modform_hecketriangle/abstract_ring.py index 6577462c05f..9f80cd3de45 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_ring.py +++ b/src/sage/modular/modform_hecketriangle/abstract_ring.py @@ -747,11 +747,11 @@ def diff_alg(self): sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: ModularFormsRing().diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} sage: from sage.modular.modform_hecketriangle.space import CuspForms sage: CuspForms(k=12, base_ring=AA).diff_alg() - Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} + Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dZ*Z: Z*dZ + 1, dY*Y: Y*dY + 1, dX*X: X*dX + 1} """ # We only use two operators for now which do not involve 'd', so for performance diff --git a/src/sage/modular/overconvergent/weightspace.py b/src/sage/modular/overconvergent/weightspace.py index 3e6e55445f4..a1f65f55d25 100644 --- a/src/sage/modular/overconvergent/weightspace.py +++ b/src/sage/modular/overconvergent/weightspace.py @@ -566,6 +566,20 @@ def chi(self): """ return self._chi + def __hash__(self): + r""" + TESTS:: + + sage: w = pAdicWeightSpace(23)(12, DirichletGroup(23, QQ).0) + sage: hash(w) + -2363716619315244394 # 64-bit + 470225558 # 32-bit + """ + if self._chi.is_trivial(): + return hash(self._k) + else: + return hash( (self._k,self._chi.modulus(),self._chi) ) + def _repr_(self): r""" String representation of self. diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index 2b4f43bb14f..749f67a3991 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -343,8 +343,28 @@ def vector(self): try: return self.__vector except AttributeError: self.__vector = self.parent().coordinate_vector(self, reduce=True) + self.__vector.set_immutable() return self.__vector + def __hash__(self): + r""" + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: hash(x) + 3713081631933328131 # 64-bit + 1298787075 # 32-bit + + sage: A = AdditiveAbelianGroup([3]) + sage: hash(A.an_element()) + 3430019387558 # 64-bit + -1659481946 # 32-bit + """ + return hash(self.vector()) + def _vector_(self, base_ring=None): """ Support for conversion to vectors. @@ -367,10 +387,21 @@ def _vector_(self, base_ring=None): (1, 3) sage: vector(CDF, x) (1.0, 3.0) + + TESTS:: + + sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ) + sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) + sage: Q = V/W + sage: x = Q.0 + 3*Q.1 + sage: vector(x).is_mutable() + True + sage: vector(CDF,x).is_mutable() + True """ v = self.vector() if base_ring is None or v.base_ring() is base_ring: - return v + return v.__copy__() else: return v.change_ring(base_ring) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 5d0b8159274..587bc33f032 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4580,8 +4580,21 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): sage: w = vector(R, [], sparse=True) sage: parent(v._dot_product_coerce_(w)) Univariate Polynomial Ring in x over Real Double Field + + TESTS: + + Check that :trac:`19377` is fixed:: + + sage: w = vector(ZZ, (1,2,3), sparse=False) + sage: v = vector(ZZ, (1,2,3), sparse=True) + sage: v._dot_product_coerce_(w) + 14 """ - cdef dict e = (right)._entries + cdef dict e + try: + e = (right)._entries + except TypeError: + e = right.dict() z = left.base_ring().zero() if left.base_ring() is not right.base_ring(): z *= right.base_ring().zero() diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index 7fef15de61c..9872b16c232 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -80,6 +80,23 @@ def __init__(self, F, x, check=True): # TODO: should have some other checks here... raise TypeError("Argument x (= %s) is of the wrong type."%x) + def __hash__(self): + r""" + TESTS:: + + sage: R. = FreeMonoid(2) + sage: hash(x) + 1914282862589934403 # 64-bit + 139098947 # 32-bit + sage: hash(y) + 2996819001369607946 # 64-bit + 13025034 # 32-bit + sage: hash(x*y) + 7114093379175463612 # 64-bit + 2092317372 # 32-bit + """ + return hash(tuple(self._element_list)) + def __iter__(self): """ Returns an iterator which yields tuples of variable and exponent. diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 734d24087bd..e4772f34910 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -371,6 +371,19 @@ def __init__(self, F, x): """ IndexedMonoidElement.__init__(self, F, tuple(map(tuple, x))) + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: hash(F ([(1,2),(0,1)]) ) + 2401565693828035651 # 64-bit + 1164080195 # 32-bit + sage: hash(F ([(0,2),(1,1)]) ) + -3359280905493236379 # 64-bit + -1890405019 # 32-bit + """ + return hash(self._monomial) def _sorted_items(self): """ @@ -486,6 +499,20 @@ def _sorted_items(self): pass return v + def __hash__(self): + r""" + TESTS:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: hash( F([(0,1), (2,2)]) ) + 8087055352805725849 # 64-bit + 250091161 # 32-bit + sage: hash( F([(2,1)]) ) + 5118585357534560720 # 64-bit + 1683816912 # 32-bit + """ + return hash(frozenset(self._monomial.items())) + def _mul_(self, other): """ Multiply ``self`` by ``other``. diff --git a/src/sage/plot/arc.py b/src/sage/plot/arc.py index 04fac945f42..61502267446 100644 --- a/src/sage/plot/arc.py +++ b/src/sage/plot/arc.py @@ -22,6 +22,7 @@ from math import fmod, sin, cos, pi, atan + class Arc(GraphicPrimitive): """ Primitive class for the Arc graphics type. See ``arc?`` for information @@ -87,7 +88,7 @@ def __init__(self, x, y, r1, r2, angle, s1, s2, options): self.s1 = float(s1) self.s2 = float(s2) if self.s2 < self.s1: - self.s1,self.s2=self.s2,self.s1 + self.s1, self.s2 = self.s2, self.s1 GraphicPrimitive.__init__(self, options) def get_minmax_data(self): @@ -126,91 +127,113 @@ def get_minmax_data(self): """ from sage.plot.plot import minmax_data - twopi = 2*pi + twopi = 2 * pi s1 = self.s1 s2 = self.s2 - s = s2-s1 - s1 = fmod(s1,twopi) - if s1 < 0: s1 += twopi - s2 = fmod(s1 + s,twopi) - if s2 < 0: s2 += twopi + s = s2 - s1 + s1 = fmod(s1, twopi) + if s1 < 0: + s1 += twopi + s2 = fmod(s1 + s, twopi) + if s2 < 0: + s2 += twopi r1 = self.r1 r2 = self.r2 - angle = fmod(self.angle,twopi) - if angle < 0: angle += twopi + angle = fmod(self.angle, twopi) + if angle < 0: + angle += twopi epsilon = float(0.0000001) cos_angle = cos(angle) sin_angle = sin(angle) - if cos_angle > 1-epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin = pi; axmax = 0 - aymin = 3*pi/2; aymax = pi/2 - - elif cos_angle < -1+epsilon: - xmin=-r1; ymin=-r2 - xmax=r1; ymax=r2 - axmin=0; axmax=pi - aymin=pi/2; aymax=3*pi/2 - - elif sin_angle > 1-epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = pi/2; axmax = 3*pi/2 - aymin = pi; aymax = 0 - - elif sin_angle < -1+epsilon: - xmin=-r2; ymin=-r1 - xmax=r2; ymax=r1 - axmin = 3*pi/2; axmax = pi/2 - aymin = 0; aymax = pi + if cos_angle > 1 - epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = pi + axmax = 0 + aymin = 3 * pi / 2 + aymax = pi / 2 + + elif cos_angle < -1 + epsilon: + xmin = -r1 + ymin = -r2 + xmax = r1 + ymax = r2 + axmin = 0 + axmax = pi + aymin = pi / 2 + aymax = 3 * pi / 2 + + elif sin_angle > 1 - epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = pi / 2 + axmax = 3 * pi / 2 + aymin = pi + aymax = 0 + + elif sin_angle < -1 + epsilon: + xmin = -r2 + ymin = -r1 + xmax = r2 + ymax = r1 + axmin = 3 * pi / 2 + axmax = pi / 2 + aymin = 0 + aymax = pi else: tan_angle = sin_angle / cos_angle - axmax = atan(-r2/r1*tan_angle) - if axmax < 0: axmax += twopi - xmax = ( - r1 * cos_angle * cos(axmax) - - r2 * sin_angle * sin(axmax)) + axmax = atan(-r2 / r1 * tan_angle) + if axmax < 0: + axmax += twopi + xmax = (r1 * cos_angle * cos(axmax) - + r2 * sin_angle * sin(axmax)) if xmax < 0: xmax = -xmax - axmax = fmod(axmax+pi,twopi) + axmax = fmod(axmax + pi, twopi) xmin = -xmax - axmin = fmod(axmax + pi,twopi) + axmin = fmod(axmax + pi, twopi) - aymax = atan(r2/(r1*tan_angle)) - if aymax < 0: aymax += twopi - ymax = ( - r1 * sin_angle * cos(aymax) + - r2 * cos_angle * sin(aymax)) + aymax = atan(r2 / (r1 * tan_angle)) + if aymax < 0: + aymax += twopi + ymax = (r1 * sin_angle * cos(aymax) + + r2 * cos_angle * sin(aymax)) if ymax < 0: ymax = -ymax - aymax = fmod(aymax+pi,twopi) + aymax = fmod(aymax + pi, twopi) ymin = -ymax aymin = fmod(aymax + pi, twopi) - if s < twopi-epsilon: # bb determined by the sector - def is_cyclic_ordered(x1,x2,x3): - return ( - (x1 < x2 and x2 < x3) or - (x2 < x3 and x3 < x1) or - (x3 < x1 and x1 < x2)) - - x1 = cos_angle*r1*cos(s1) - sin_angle*r2*sin(s1) - x2 = cos_angle*r1*cos(s2) - sin_angle*r2*sin(s2) - y1 = sin_angle*r1*cos(s1) + cos_angle*r2*sin(s1) - y2 = sin_angle*r1*cos(s2) + cos_angle*r2*sin(s2) - - if is_cyclic_ordered(s1,s2,axmin): xmin = min(x1,x2) - if is_cyclic_ordered(s1,s2,aymin): ymin = min(y1,y2) - if is_cyclic_ordered(s1,s2,axmax): xmax = max(x1,x2) - if is_cyclic_ordered(s1,s2,aymax): ymax = max(y1,y2) + if s < twopi - epsilon: # bb determined by the sector + def is_cyclic_ordered(x1, x2, x3): + return ((x1 < x2 and x2 < x3) or + (x2 < x3 and x3 < x1) or + (x3 < x1 and x1 < x2)) + + x1 = cos_angle * r1 * cos(s1) - sin_angle * r2 * sin(s1) + x2 = cos_angle * r1 * cos(s2) - sin_angle * r2 * sin(s2) + y1 = sin_angle * r1 * cos(s1) + cos_angle * r2 * sin(s1) + y2 = sin_angle * r1 * cos(s2) + cos_angle * r2 * sin(s2) + + if is_cyclic_ordered(s1, s2, axmin): + xmin = min(x1, x2) + if is_cyclic_ordered(s1, s2, aymin): + ymin = min(y1, y2) + if is_cyclic_ordered(s1, s2, axmax): + xmax = max(x1, x2) + if is_cyclic_ordered(s1, s2, aymax): + ymax = max(y1, y2) return minmax_data([self.x + xmin, self.x + xmax], [self.y + ymin, self.y + ymax], @@ -226,15 +249,75 @@ def _allowed_options(self): sage: p[0]._allowed_options()['alpha'] 'How transparent the figure is.' """ - return {'alpha':'How transparent the figure is.', - 'thickness':'How thick the border of the arc is.', - 'hue':'The color given as a hue.', - 'rgbcolor':'The color', - 'zorder':'2D only: The layer level in which to draw', - 'linestyle':"2D only: The style of the line, which is one of " + return {'alpha': 'How transparent the figure is.', + 'thickness': 'How thick the border of the arc is.', + 'hue': 'The color given as a hue.', + 'rgbcolor': 'The color', + 'zorder': '2D only: The layer level in which to draw', + 'linestyle': "2D only: The style of the line, which is one of " "'dashed', 'dotted', 'solid', 'dashdot', or '--', ':', '-', '-.', " "respectively."} + def _matplotlib_arc(self): + """ + Return ``self`` as a matplotlib arc object. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: Arc(2,3,2.2,2.2,0,2,3,{})._matplotlib_arc() + + """ + import matplotlib.patches as patches + p = patches.Arc((self.x, self.y), + 2. * self.r1, + 2. * self.r2, + fmod(self.angle, 2 * pi) * (180. / pi), + self.s1 * (180. / pi), + self.s2 * (180. / pi)) + return p + + def bezier_path(self): + """ + Return ``self`` as a Bezier path. + + This is needed to concatenate arcs, in order to + create hyperbolic polygons. + + EXAMPLES:: + + sage: from sage.plot.arc import Arc + sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, + ....: 'linestyle':'--'} + sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() + Graphics object consisting of 1 graphics primitive + + sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") + sage: b = a[0].bezier_path() + sage: b[0] + Bezier path from (1.618..., 0.5877...) to (-0.5176..., 0.9659...) + """ + from sage.plot.bezier_path import BezierPath + from sage.plot.graphics import Graphics + ma = self._matplotlib_arc() + transform = ma.get_transform().get_matrix() + cA, cC, cE = transform[0] + cB, cD, cF = transform[1] + points = [] + for u in ma._path.vertices: + x, y = list(u) + points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] + cutlist = [points[0: 4]] + N = 4 + while N < len(points): + cutlist += [points[N: N + 3]] + N += 3 + g = Graphics() + opt = self.options() + opt['fill'] = False + g.add_primitive(BezierPath(cutlist, opt)) + return g + def _repr_(self): """ String representation of ``Arc`` primitive. @@ -245,7 +328,7 @@ def _repr_(self): sage: print Arc(2,3,2.2,2.2,0,2,3,{}) Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0) """ - return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" %(self.x,self.y,self.r1,self.r2,self.angle,self.s1,self.s2) + return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" % (self.x, self.y, self.r1, self.r2, self.angle, self.s1, self.s2) def _render_on_subplot(self, subplot): """ @@ -254,26 +337,19 @@ def _render_on_subplot(self, subplot): sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ - import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle - options = self.options() - p = patches.Arc( - (self.x,self.y), - 2.*self.r1, - 2.*self.r2, - fmod(self.angle,2*pi)*(180./pi), - self.s1*(180./pi), - self.s2*(180./pi)) + p = self._matplotlib_arc() p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) - z = int(options.pop('zorder',1)) + z = int(options.pop('zorder', 1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) - p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) + p.set_linestyle(get_matplotlib_linestyle(options['linestyle'], + return_type='long')) p.set_edgecolor(c) subplot.add_patch(p) @@ -289,10 +365,11 @@ def plot3d(self): """ raise NotImplementedError + @rename_keyword(color='rgbcolor') -@options(alpha=1, thickness=1, linestyle='solid', zorder=5,rgbcolor='blue', +@options(alpha=1, thickness=1, linestyle='solid', zorder=5, rgbcolor='blue', aspect_ratio=1.0) -def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): +def arc(center, r1, r2=None, angle=0.0, sector=(0.0, 2 * pi), **options): r""" An arc (that is a portion of a circle or an ellipse) @@ -372,19 +449,19 @@ def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' - if len(center)==2: - if r2 is None: r2 = r1 + if len(center) == 2: + if r2 is None: + r2 = r1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) if len(sector) != 2: raise ValueError("the sector must consist of two angles") g.add_primitive(Arc( - center[0],center[1], - r1,r2, + center[0], center[1], + r1, r2, angle, - sector[0],sector[1], + sector[0], sector[1], options)) return g - elif len(center)==3: + elif len(center) == 3: raise NotImplementedError - diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index 1e658fc84d5..5b1ac5d5739 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -7,6 +7,7 @@ """ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien , +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -70,20 +71,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(),z0.imag()), (z3.real(),z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index e6465183e04..3bfbdcb3404 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -8,7 +8,8 @@ """ #***************************************************************************** # Copyright (C) 2011 Hartmut Monien , -# 2014 Vincent Delecroix <20100.delecroix@gmail.com> +# 2014 Vincent Delecroix <20100.delecroix@gmail.com>, +# 2015 Stefan Kraemer # # Distributed under the terms of the GNU General Public License (GPL) # @@ -85,20 +86,21 @@ def _hyperbolic_arc(self, z0, z3, first=False): the hyperbolic arc between the complex numbers z0 and z3 in the hyperbolic plane. """ - if (z0-z3).real() == 0: + z0, z3 = (CC(z0), CC(z3)) + p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 + r = abs(z0-p) + + if abs(z3-z0)/r < 0.1: self.path.append([(z0.real(), z0.imag()), (z3.real(), z3.imag())]) return - z0, z3 = (CC(z0), CC(z3)) + if z0.imag() == 0 and z3.imag() == 0: p = (z0.real()+z3.real())/2 - r = abs(z0-p) zm = CC(p, r) self._hyperbolic_arc(z0, zm, first) self._hyperbolic_arc(zm, z3) return else: - p = (abs(z0)*abs(z0)-abs(z3)*abs(z3))/(z0-z3).real()/2 - r = abs(z0-p) zm = ((z0+z3)/2-p)/abs((z0+z3)/2-p)*r+p t = (8*zm-4*(z0+z3)).imag()/3/(z3-z0).real() z1 = z0 + t*CC(z0.imag(), (p-z0.real())) diff --git a/src/sage/repl/display/fancy_repr.py b/src/sage/repl/display/fancy_repr.py index 31115a7b703..18f0906747b 100644 --- a/src/sage/repl/display/fancy_repr.py +++ b/src/sage/repl/display/fancy_repr.py @@ -317,11 +317,36 @@ def __call__(self, obj, p, cycle): sage: format_list = TallListRepr().format_string sage: format_list([1, 2, identity_matrix(2)]) '[\n [1 0]\n1, 2, [0 1]\n]' + + Check that :trac:`18743` is fixed:: + + sage: class Foo(object): + ....: def __repr__(self): + ....: return '''BBB AA RRR + ....: B B A A R R + ....: BBB AAAA RRR + ....: B B A A R R + ....: BBB A A R R''' + ....: def _repr_option(self, key): + ....: return key == 'ascii_art' + sage: F = Foo() + sage: [F, F] + [ + BBB AA RRR BBB AA RRR + B B A A R R B B A A R R + BBB AAAA RRR BBB AAAA RRR + B B A A R R B B A A R R + BBB A A R R, BBB A A R R + ] """ if not (isinstance(obj, (tuple, list)) and len(obj) > 0): return False ascii_art_repr = False for o in obj: + try: + ascii_art_repr = ascii_art_repr or o._repr_option('ascii_art') + except (AttributeError, TypeError): + pass try: ascii_art_repr = ascii_art_repr or o.parent()._repr_option('element_ascii_art') except (AttributeError, TypeError): diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 7bc122fd509..55e03622122 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -250,13 +250,18 @@ def update(cls): instance._symlink_resources() -def have_prerequisites(): +def have_prerequisites(debug=True): """ - Check that we have all prerequisites to run the IPython notebook. + Check that we have all prerequisites to run the Jupyter notebook. - In particular, the IPython notebook requires OpenSSL whether or + In particular, the Jupyter notebook requires OpenSSL whether or not you are using https. See :trac:`17318`. + INPUT: + + ``debug`` -- boolean (default: ``True``). Whether to print debug + information in case that prerequisites are missing. + OUTPUT: Boolean. @@ -264,15 +269,14 @@ def have_prerequisites(): EXAMPLES:: sage: from sage.repl.ipython_kernel.install import have_prerequisites - sage: have_prerequisites() in [True, False] + sage: have_prerequisites(debug=False) in [True, False] True """ try: from notebook.notebookapp import NotebookApp return True except ImportError: + if debug: + import traceback + traceback.print_exc() return False - - - - diff --git a/src/sage/rings/cfinite_sequence.py b/src/sage/rings/cfinite_sequence.py index a33e1c75d01..972392426db 100644 --- a/src/sage/rings/cfinite_sequence.py +++ b/src/sage/rings/cfinite_sequence.py @@ -429,6 +429,18 @@ def _repr_(self): else: return 'C-finite sequence, generated by ' + str(self.ogf()) + def __hash__(self): + r""" + Hash value for C finite sequence. + + EXAMPLES:: + + sage: C. = CFiniteSequences(QQ) + sage: hash(C((2-x)/(1-x-x^2))) # random + 42 + """ + return hash(self.parent()) ^ hash(self._ogf) + def _add_(self, other): """ Addition of C-finite sequences. diff --git a/src/sage/rings/complex_ball_acb.pyx b/src/sage/rings/complex_ball_acb.pyx index 6823960c101..0c5b9e6ec29 100644 --- a/src/sage/rings/complex_ball_acb.pyx +++ b/src/sage/rings/complex_ball_acb.pyx @@ -583,7 +583,7 @@ cdef class ComplexBall(Element): """ return acb_is_exact(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right``. @@ -607,19 +607,6 @@ cdef class ComplexBall(Element): False sage: a == b # optional - arb False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Compare ``left`` and ``right``. - - For more information, see :mod:`sage.rings.complex_ball_acb`. - - EXAMPLES:: - - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb sage: a = CBF(1, 2) # optional - arb sage: b = CBF(1, 2) # optional - arb sage: a is b # optional - arb @@ -629,70 +616,70 @@ cdef class ComplexBall(Element): TESTS: - Balls whose intersection consists of one point:: - - sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb - sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a > b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a <= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a >= b # optional - arb - Traceback (most recent call last): - ... - TypeError: No order is defined for ComplexBalls. - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = CBF(1/3, 1/3) # optional - arb - sage: b = CBF(1/5, 1/5) # optional - arb - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = CBF(2, 2) # optional - arb - sage: b = CBF(2, 2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False + Balls whose intersection consists of one point:: + + sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb + sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb + sage: a < b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a > b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a <= b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a >= b # optional - arb + Traceback (most recent call last): + ... + TypeError: No order is defined for ComplexBalls. + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Balls with non-trivial intersection:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb + sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + One ball contained in another:: + + sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb + sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Disjoint balls:: + + sage: a = CBF(1/3, 1/3) # optional - arb + sage: b = CBF(1/5, 1/5) # optional - arb + sage: a == b # optional - arb + False + sage: a != b # optional - arb + True + + Exact elements:: + + sage: a = CBF(2, 2) # optional - arb + sage: b = CBF(2, 2) # optional - arb + sage: a.is_exact() # optional - arb + True + sage: b.is_exact() # optional - arb + True + sage: a == b # optional - arb + True + sage: a != b # optional - arb + False """ cdef ComplexBall lt, rt cdef acb_t difference diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 2dbd9b83612..e181c7d8237 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -795,9 +795,13 @@ cdef class ComplexDoubleElement(FieldElement): """ return hash(complex(self)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparison between ``left`` and ``right``. + We order the complex numbers in dictionary order by real parts then + imaginary parts. + + This order, of course, does not respect the field structure, though + it agrees with the usual order on the real numbers. EXAMPLES:: @@ -807,18 +811,8 @@ cdef class ComplexDoubleElement(FieldElement): -1 sage: cmp(CDF(1 + i), CDF(-1 - i)) 1 - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - We order the complex numbers in dictionary order by real parts then - imaginary parts. - This order, of course, does not respect the field structure, though - it agrees with the usual order on the real numbers. - - EXAMPLES:: + :: sage: CDF(2,3) < CDF(3,1) True diff --git a/src/sage/rings/complex_interval.pyx b/src/sage/rings/complex_interval.pyx index 2fc79377f4a..4faeca9a017 100644 --- a/src/sage/rings/complex_interval.pyx +++ b/src/sage/rings/complex_interval.pyx @@ -51,10 +51,9 @@ from complex_number cimport ComplexNumber import complex_interval_field from complex_field import ComplexField import sage.misc.misc -import integer +cimport integer import infinity -import real_mpfi -import real_mpfr +cimport real_mpfi cimport real_mpfr @@ -927,6 +926,23 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): return x + def _complex_mpfr_field_(self, field): + """ + Convert to a complex field. + + EXAMPLES:: + + sage: re = RIF("1.2") + sage: im = RIF(2, 3) + sage: a = ComplexIntervalField(30)(re, im) + sage: CC(a) + 1.20000000018626 + 2.50000000000000*I + """ + cdef ComplexNumber x = field(0) + mpfi_mid(x.__re, self.__re) + mpfi_mid(x.__im, self.__im) + return x + def __int__(self): """ Convert ``self`` to an ``int``. @@ -1000,7 +1016,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): """ return self.real().__nonzero__() or self.imag().__nonzero__() - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): r""" As with the real interval fields this never returns false positives. Thus, `a == b` is ``True`` iff both `a` and `b` represent the same @@ -1037,9 +1053,6 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): sage: CDF(1) >= CDF(1) >= CDF.gen() >= CDF.gen() >= 0 >= -CDF.gen() >= CDF(-1) True """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): cdef ComplexIntervalFieldElement lt, rt lt = left rt = right @@ -1072,7 +1085,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): elif op == 5: #>= return real_diff > 0 or (real_diff == 0 and imag_diff >= 0) - def __cmp__(left, right): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ Intervals are compared lexicographically on the 4-tuple: ``(x.real().lower(), x.real().upper(), @@ -1093,27 +1106,18 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): 0 sage: cmp(b, a) 1 - """ - return (left)._cmp(right) - - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: - """ - Intervals are compared lexicographically on the 4-tuple: - ``(x.real().lower(), x.real().upper(), - x.imag().lower(), x.imag().upper())`` TESTS:: sage: tests = [] sage: for rl in (0, 1): - ... for ru in (rl, rl + 1): - ... for il in (0, 1): - ... for iu in (il, il + 1): - ... tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) + ....: for ru in (rl, rl + 1): + ....: for il in (0, 1): + ....: for iu in (il, il + 1): + ....: tests.append((CIF(RIF(rl, ru), RIF(il, iu)), (rl, ru, il, iu))) sage: for (i1, t1) in tests: - ... for (i2, t2) in tests: - ... assert(cmp(i1, i2) == cmp(t1, t2)) + ....: for (i2, t2) in tests: + ....: assert(cmp(i1, i2) == cmp(t1, t2)) """ cdef int a, b a = mpfi_nan_p(left.__re) diff --git a/src/sage/rings/complex_interval_field.py b/src/sage/rings/complex_interval_field.py index a400f29f278..e147a410be5 100644 --- a/src/sage/rings/complex_interval_field.py +++ b/src/sage/rings/complex_interval_field.py @@ -377,10 +377,15 @@ def __call__(self, x, im=None): 2 + 3*I sage: CIF(pi, e) 3.141592653589794? + 2.718281828459046?*I + sage: ComplexIntervalField(100)(CIF(RIF(2,3))) + 3.? """ if im is None: - if isinstance(x, complex_interval.ComplexIntervalFieldElement) and x.parent() is self: - return x + if isinstance(x, complex_interval.ComplexIntervalFieldElement): + if x.parent() is self: + return x + else: + return complex_interval.ComplexIntervalFieldElement(self, x) elif isinstance(x, complex_double.ComplexDoubleElement): return complex_interval.ComplexIntervalFieldElement(self, x.real(), x.imag()) elif isinstance(x, str): diff --git a/src/sage/rings/complex_number.pyx b/src/sage/rings/complex_number.pyx index 34e941fe73a..8e4de8876a7 100644 --- a/src/sage/rings/complex_number.pyx +++ b/src/sage/rings/complex_number.pyx @@ -1123,11 +1123,10 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): """ return complex(mpfr_get_d(self.__re, rnd), mpfr_get_d(self.__im, rnd)) - # return complex(float(self.__re), float(self.__im)) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparision between ``left`` and ``right``. + Compare ``left`` and ``right``. EXAMPLES:: @@ -1136,9 +1135,6 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): sage: cmp(CC(2, 1), CC(2, 1)) 0 """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int a, b a = mpfr_nan_p(left.__re) b = mpfr_nan_p((right).__re) diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index c94ee5a675f..403b99c8fd0 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -1297,19 +1297,6 @@ cdef class FiniteField_givaroElement(FinitePolyExtElement): return make_FiniteField_givaroElement(cache, cache.objectptr.one) return make_FiniteField_givaroElement(cache, r) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: k. = GF(9); k - Finite Field in a of size 3^2 - sage: a == k('a') # indirect doctest - True - sage: a == a + 1 - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements is correct or equality diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index ba7b6991436..7c890f3ac2f 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -803,10 +803,16 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): from sage.groups.generic import power return power(self,exp) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Comparison of finite field elements. + .. NOTE:: + + Finite fields are unordered. However, we adopt the convention that + an element ``e`` is bigger than element ``f`` if its polynomial + representation is bigger. + EXAMPLES:: sage: k. = GF(2^20) @@ -819,13 +825,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, we adopt the convention that - an element ``e`` is bigger than element ``f`` if its polynomial - representation is bigger. - - EXAMPLES:: + :: sage: K. = GF(2^100) sage: a < a^2 @@ -843,12 +843,6 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: a == a True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Comparison of finite field elements. - """ (left._parent._cache).F.restore() c = GF2E_equal((left).x, (right).x) if c == 1: diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pyx b/src/sage/rings/finite_rings/element_pari_ffelt.pyx index 2ab45e245c5..2401a21b95a 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pyx +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pyx @@ -388,31 +388,13 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): """ Comparison of finite field elements. - TESTS:: - - sage: k. = FiniteField(3^3, impl='pari_ffelt') - sage: a == 1 - False - sage: a^0 == 1 - True - sage: a == a - True - sage: a < a^2 - True - sage: a > a^2 - False - """ - cdef int r - pari_catch_sig_on() - r = cmp_universal(self.val, (other).val) - pari_catch_sig_off() - return r + .. NOTE:: - def __richcmp__(FiniteFieldElement_pari_ffelt left, object right, int op): - """ - Rich comparison of finite field elements. + Finite fields are unordered. However, for the purpose of + this function, we adopt the lexicographic ordering on the + representing polynomials. - EXAMPLE:: + EXAMPLES:: sage: k. = GF(2^20, impl='pari_ffelt') sage: e = k.random_element() @@ -424,13 +406,7 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): sage: e != (e + 1) True - .. NOTE:: - - Finite fields are unordered. However, for the purpose of - this function, we adopt the lexicographic ordering on the - representing polynomials. - - EXAMPLE:: + :: sage: K. = GF(2^100, impl='pari_ffelt') sage: a < a^2 @@ -447,8 +423,26 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): False sage: a == a True + + TESTS:: + + sage: k. = FiniteField(3^3, impl='pari_ffelt') + sage: a == 1 + False + sage: a^0 == 1 + True + sage: a == a + True + sage: a < a^2 + True + sage: a > a^2 + False """ - return (left)._richcmp(right, op) + cdef int r + pari_catch_sig_on() + r = cmp_universal(self.val, (other).val) + pari_catch_sig_off() + return r cpdef ModuleElement _add_(FiniteFieldElement_pari_ffelt self, ModuleElement right): """ diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 04a45c10142..1ca9fd576c1 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -341,16 +341,10 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): """ return self._section_class(self) - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) - cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ A class implementing Frobenius endomorphisms on finite fields. @@ -670,11 +664,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return self.power() == 0 - - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - def __hash__(self): return Morphism.__hash__(self) diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index efc5d11d33b..4b041bba56b 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -1838,10 +1838,6 @@ cdef class IntegerMod_gmp(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_gmp self): """ Returns ``True`` if this is `1`, otherwise @@ -2252,10 +2248,6 @@ cdef class IntegerMod_int(IntegerMod_abstract): else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int self): """ Returns ``True`` if this is `1`, otherwise @@ -3080,10 +3072,6 @@ cdef class IntegerMod_int64(IntegerMod_abstract): elif self.ivalue < (right).ivalue: return -1 else: return 1 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - - cpdef bint is_one(IntegerMod_int64 self): """ Returns ``True`` if this is `1`, otherwise diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 391991b4329..f54ada8f8d5 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -869,9 +869,6 @@ cdef class LaurentSeries(AlgebraElement): return self.prec() return min(self.prec(), f.prec()) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of self and right. diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 502c1ceb976..594cf9ff34e 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -1137,20 +1137,6 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): _slots['__im_gens'] = self.__im_gens return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: cmp(f,g) # indirect doctest - 1 - sage: cmp(g,f) - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1175,6 +1161,14 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): sage: loads(dumps(f2)) == f2 True + :: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: cmp(f,g) # indirect doctest + 1 + sage: cmp(g,f) + -1 + EXAMPLES: A multivariate quotient over a finite field:: @@ -1441,22 +1435,6 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): _slots['__underlying'] = self.__underlying return RingHomomorphism._extra_slots(self, _slots) - def __richcmp__(left, right, int op): - """ - Used internally by the cmp method. - - TESTS:: - - sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) - sage: S. = R[] - sage: fS = S.hom(f,S); gS = S.hom(g,S) - sage: cmp(fS,gS) # indirect doctest - 1 - sage: cmp(gS,fS) # indirect doctest - -1 - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: r""" EXAMPLES: @@ -1480,6 +1458,16 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): sage: f1P == loads(dumps(f1P)) True + TESTS:: + + sage: R. = QQ[]; f = R.hom([x,x+y]); g = R.hom([y,x]) + sage: S. = R[] + sage: fS = S.hom(f,S); gS = S.hom(g,S) + sage: cmp(fS,gS) # indirect doctest + 1 + sage: cmp(gS,fS) # indirect doctest + -1 + EXAMPLES: A matrix ring over a multivariate quotient over a finite field:: @@ -2109,9 +2097,6 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): codomain = self.codomain() return hash((domain, codomain, ('Frob', self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 domain = left.domain() diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 1e3d3306f14..030c8d4dda5 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -709,7 +709,7 @@ cdef class NumberFieldElement(FieldElement): raise IndexError, "index must be between 0 and degree minus 1." return self.polynomial()[n] - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: r""" EXAMPLE:: @@ -719,9 +719,6 @@ cdef class NumberFieldElement(FieldElement): sage: a + 1 < a # indirect doctest False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef NumberFieldElement _right = right return not (ZZX_equal(left.__numerator, _right.__numerator) and ZZ_equal(left.__denominator, _right.__denominator)) diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index 223ed701c1b..1d8ebcf6c2e 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -658,22 +658,15 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): return test return -test - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element _right, int op): r""" - Note: we may implement a more direct way of comparison for integer, - float and quadratic numbers input (ie avoiding coercion). + Rich comparison of elements. TESTS:: sage: K. = QuadraticField(-1) sage: sorted([5*i+1, 2, 3*i+1, 2-i]) [3*i + 1, 5*i + 1, -i + 2, 2] - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element _right, int op): - r""" - C implementation of comparison. TESTS: @@ -796,8 +789,8 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): mpz_clear(j) return rich_to_bool_sgn(op, test) - def __cmp__(left, right): - r""" + cpdef int _cmp_(left, Element _right) except -2: + """ Comparisons of elements. When there is a real embedding defined, the comparisons uses comparison @@ -869,12 +862,6 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: map(CDF, l) == sorted(map(CDF, l)) True """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element _right) except -2: - """ - C implementation of comparison. - """ cdef NumberFieldElement_quadratic right = _right cdef int test diff --git a/src/sage/rings/polynomial/complex_roots.py b/src/sage/rings/polynomial/complex_roots.py index 5056ea53222..42432118f68 100644 --- a/src/sage/rings/polynomial/complex_roots.py +++ b/src/sage/rings/polynomial/complex_roots.py @@ -24,128 +24,25 @@ [(1.167303978261419?, 1), (-0.764884433600585? - 0.352471546031727?*I, 1), (-0.764884433600585? + 0.352471546031727?*I, 1), (0.181232444469876? - 1.083954101317711?*I, 1), (0.181232444469876? + 1.083954101317711?*I, 1)] """ +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + from copy import copy -from sage.rings.real_mpfi import RealIntervalField from sage.rings.complex_field import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.qqbar import AA, QQbar from sage.rings.arith import sort_complex_numbers_for_display +from sage.rings.polynomial.refine_root import refine_root -def refine_root(ip, ipd, irt, fld): - """ - We are given a polynomial and its derivative (with complex - interval coefficients), an estimated root, and a complex interval - field to use in computations. We use interval arithmetic to - refine the root and prove that we have in fact isolated a unique - root. - - If we succeed, we return the isolated root; if we fail, we return - None. - - EXAMPLES:: - - sage: from sage.rings.polynomial.complex_roots import * - sage: x = polygen(ZZ) - sage: p = x^9 - 1 - sage: ip = CIF['x'](p); ip - x^9 - 1 - sage: ipd = CIF['x'](p.derivative()); ipd - 9*x^8 - sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt - 0.76604444311897802? + 0.64278760968653926?*I - sage: ip(irt) - 0.?e-14 + 0.?e-14*I - sage: ipd(irt) - 6.89439998807080? - 5.78508848717885?*I - sage: refine_root(ip, ipd, irt, CIF) - 0.766044443118978? + 0.642787609686540?*I - """ - - # There has got to be a better way to do this, but I don't know - # what it is... - - # We start with a basic fact: if we do an interval Newton-Raphson - # step, and the refined interval is contained in the original interval, - # then the refined interval contains exactly one root. - - # Unfortunately, our initial estimated root almost certainly does not - # contain the actual root (our initial interval is a point, which - # is exactly equal to whatever floating-point estimate we got from - # the external solver). So we need to do multiple Newton-Raphson - # steps, and check this inclusion property on each step. - - # After a few steps of refinement, if the initial root estimate was - # close to a root, we should have an essentially perfect interval - # bound on the root (since Newton-Raphson has quadratic convergence), - # unless either the real or imaginary component of the root is zero. - # If the real or imaginary component is zero, then we could spend - # a long time computing closer and closer approximations to that - # component. (This doesn't happen for non-zero components, because - # of the imprecision of floating-point numbers combined with the - # outward interval rounding; but close to zero, MPFI provides - # extremely precise numbers.) - - # If the root is actually a real root, but we start with an imaginary - # component, we can bounce back and forth between having a positive - # and negative imaginary component, without ever hitting zero. - # To deal with this, on every other Newton-Raphson step, instead of - # replacing the old interval with the new one, we take the union. - - # If the containment check continues to fail many times in a row, - # we give up and return None; we also return None if we detect - # that the slope in our current interval is not bounded away - # from zero at any step. - - # After every refinement step, we check to see if the real or - # imaginary component of our interval includes zero. If so, we - # try setting it to exactly zero. This gives us a good chance of - # detecting real roots. However, we do this replacement at most - # once per component. - - refinement_steps = 10 - - smashed_real = False - smashed_imag = False - - for i in range(refinement_steps): - slope = ipd(irt) - if slope.contains_zero(): - return None - center = fld(irt.center()) - val = ip(center) - - nirt = center - val / slope - # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope - if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): - # If the new diameter is much less than the original diameter, - # then we have not yet converged. (Perhaps we were asked - # for a particularly high-precision result.) So we don't - # return yet. - return nirt - - if i & 1: - irt = nirt - else: - irt = irt.union(nirt) - # If we don't find a root after a while, try (approximately) - # tripling the size of the region. - if i >= 6: - rD = irt.real().absolute_diameter() - iD = irt.imag().absolute_diameter() - md = max(rD, iD) - md_intv = RealIntervalField(rD.prec())(-md, md) - md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) - irt = irt + md_cintv - - if not smashed_real and irt.real().contains_zero(): - irt = irt.parent()(0, irt.imag()) - smashed_real = True - if not smashed_imag and irt.imag().contains_zero(): - irt = irt.parent()(irt.real(), 0) - smashed_imag = True - - return None def interval_roots(p, rts, prec): """ @@ -335,7 +232,7 @@ def complex_roots(p, skip_squarefree=False, retval='interval', min_prec=0): TESTS: - Verify that trac 12026 is fixed:: + Verify that :trac:`12026` is fixed:: sage: f = matrix(QQ, 8, lambda i, j: 1/(i + j + 1)).charpoly() sage: from sage.rings.polynomial.complex_roots import complex_roots diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 59e873fe00d..081b6d4f340 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -255,16 +255,15 @@ def _repr_(self): """ return repr(self._p) - def _hash_(self): + def __hash__(self): """ TESTS:: sage: X. = InfinitePolynomialRing(QQ) sage: a = x[0] + x[1] sage: hash(a) # indirect doctest - -6172640511012239345 # 64-bit - -957478897 # 32-bit - + 971115012877883067 # 64-bit + -2103273797 # 32-bit """ return hash(self._p) diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 091fc1a2fb0..0ef044296ef 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -1332,8 +1332,27 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): sage: R. = LaurentPolynomialRing(QQ) sage: loads(dumps(x1)) == x1 # indirect doctest True + sage: z = x1/x2 + sage: loads(dumps(z)) == z # not tested (bug) + True """ - return self._parent, (self._poly,) + # one should also record the monomial self._mon + return self._parent, (self._poly,) # THIS IS WRONG ! + + def __hash__(self): + r""" + TESTS:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L({(-1,-1):1}) + sage: hash(f) + 1 + sage: f = L({(1,1):1}) + sage: hash(f) + -2021162459040316190 # 64-bit + -1148451614 # 32-bit + """ + return hash(self._poly) cdef _new_c(self): """ @@ -1374,7 +1393,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): else: e = e.emin(k) if len(e.nonzero_positions()) > 0: - self._poly = self._poly / self._poly.parent()({e: 1}) + self._poly = self._poly // self._poly.parent()({e: 1}) self._mon = self._mon.eadd(e) else: e = None @@ -1382,7 +1401,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): if e is None or k[i] < e: e = k[i] if e > 0: - self._poly = self._poly / self._poly.parent().gen(i) + self._poly = self._poly // self._poly.parent().gen(i) self._mon = self._mon.eadd_p(e, i) def _dict(self): @@ -2047,8 +2066,17 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): x^2 - x*y^-1 + y^-2 sage: h * (f // h) == f True + + TESTS: + + Check that :trac:`19357` is fixed:: + + sage: x // y + x*y^-1 """ cdef LaurentPolynomial_mpair ans = self._new_c() + self._normalize() + right._normalize() ans._mon = self._mon.esub((right)._mon) ans._poly = self._poly.__floordiv__((right)._poly) return ans diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index c0e7bce2c1e..da64b370cdb 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -2692,10 +2692,14 @@ def __init__(self, ring, gens, coerce=True, side = "left"): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) # indirect doctest - sage: I + sage: I #random Left Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") + sage: sorted(I.gens(),key=str) + [x^2, y^2, z^2 - 1] + sage: H.ideal([y^2, x^2, z^2-H.one()], side="twosided") #random Twosided Ideal (y^2, x^2, z^2 - 1) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(H.ideal([y^2, x^2, z^2-H.one()], side="twosided").gens(),key=str) + [x^2, y^2, z^2 - 1] sage: H.ideal([y^2, x^2, z^2-H.one()], side="right") Traceback (most recent call last): ... @@ -2726,8 +2730,10 @@ def __call_singular(self, cmd, arg = None): sage: H.inject_variables() Defining x, y, z sage: id = H.ideal(x + y, y + z) - sage: id.std() # indirect doctest + sage: id.std() # indirect doctest # random Left Ideal (z, y, x) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(id.std().gens(),key=str) + [x, y, z] """ from sage.libs.singular.function import singular_function fun = singular_function(cmd) @@ -2748,23 +2754,34 @@ def std(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + If the ideal is a left ideal, then std returns a left Groebner basis. But if it is a two-sided ideal, then the output of std and :meth:`twostd` coincide:: sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL + sage: JL #random Left Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JL.std() + sage: sorted(JL.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JL.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*z^2 + 2*x*z, x^3, y*z^2 - 2*y*z, y^3, z^3 - 4*z] sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT + sage: JT #random Twosided Ideal (x^3, y^3, z^3 - 4*z) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} - sage: JT.std() + sage: sorted(JT.gens(),key=str) + [x^3, y^3, z^3 - 4*z] + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(JT.std().gens(),key=str) + [2*x*y*z - z^2 - 2*z, x*y^2 - y*z, x*z^2 + 2*x*z, x^2*y - x*z - 2*x, x^2*z + 2*x^2, x^3, y*z^2 - 2*y*z, y^2*z - 2*y^2, y^3, z^3 - 4*z] sage: JT.std() == JL.twostd() True @@ -2787,8 +2804,10 @@ def twostd(self): sage: H.inject_variables() Defining x, y, z sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I.twostd() + sage: I.twostd() #random Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field... + sage: sorted(I.twostd().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] ALGORITHM: Uses Singular's twostd command """ @@ -2809,7 +2828,7 @@ def _groebner_strategy(self): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False) - sage: I._groebner_strategy() + sage: I._groebner_strategy() #random Groebner Strategy for ideal generated by 6 elements over Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} @@ -2837,7 +2856,7 @@ def reduce(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: I = H.ideal([y^2, x^2, z^2-H.one()],coerce=False, side='twosided') - sage: Q = H.quotient(I); Q + sage: Q = H.quotient(I); Q #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} by the ideal (y^2, x^2, z^2 - 1) @@ -2847,10 +2866,8 @@ def reduce(self,p): Here, we see that the relation that we just found in the quotient is actually a consequence of the given relations:: - sage: I.std() - Twosided Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) - of Noncommutative Multivariate Polynomial Ring in x, y, z over - Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H.2^2-H.one() in I.std().gens() + True Here is the corresponding direct test:: @@ -2869,10 +2886,10 @@ def _contains_(self,p): sage: A. = FreeAlgebra(QQ, 3) sage: H. = A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) sage: JL = H.ideal([x^3, y^3, z^3 - 4*z]) - sage: JL.std() + sage: JL.std() #random Left Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, 2*x*y*z - z^2 - 2*z, y^3, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} sage: JT = H.ideal([x^3, y^3, z^3 - 4*z], side='twosided') - sage: JT.std() + sage: JT.std() #random Twosided Ideal (z^3 - 4*z, y*z^2 - 2*y*z, x*z^2 + 2*x*z, y^2*z - 2*y^2, 2*x*y*z - z^2 - 2*z, x^2*z + 2*x^2, y^3, x*y^2 - y*z, x^2*y - x*z - 2*x, x^3) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} Apparently, ``x*y^2-y*z`` should be in the two-sided, but not diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 6dc97afb04e..56a572dc4fb 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -161,9 +161,9 @@ class G_AlgFactory(UniqueFactory): TEST:: sage: A. = FreeAlgebra(QQ, 3) - sage: A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) # indirect doctest - Noncommutative Multivariate Polynomial Ring in x, y, z over Rational - Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: H=A.g_algebra({y*x:x*y-z, z*x:x*z+2*x, z*y:y*z-2*y}) + sage: sorted(H.relations().iteritems(),key=str) + [(y*x, x*y - z), (z*x, x*z + 2*x), (z*y, y*z - 2*y)] """ # key = (base_ring,names, c,d, order, category) @@ -1711,10 +1711,13 @@ cdef class NCPolynomial_plural(RingElement): The Groebner basis shows that the result is correct:: - sage: I.std() + sage: I.std() #random Left Ideal (z^2 - 1, y*z - y, x*z + x, y^2, 2*x*y - z - 1, x^2) of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: x*z + 2*x, z*y: y*z - 2*y, y*x: x*y - z} + sage: sorted(I.std().gens(),key=str) + [2*x*y - z - 1, x*z + x, x^2, y*z - y, y^2, z^2 - 1] + """ cdef ideal *_I @@ -2954,8 +2957,12 @@ def ExteriorAlgebra(base_ring, names,order='degrevlex'): EXAMPLES:: sage: from sage.rings.polynomial.plural import ExteriorAlgebra - sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E + sage: E = ExteriorAlgebra(QQ, ['x', 'y', 'z']) ; E #random Quotient of Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field, nc-relations: {z*x: -x*z, z*y: -y*z, y*x: -x*y} by the ideal (z^2, y^2, x^2) + sage: sorted(E.cover().domain().relations().iteritems(),key=str) + [(y*x, -x*y), (z*x, -x*z), (z*y, -y*z)] + sage: sorted(E.cover().kernel().gens(),key=str) + [x^2, y^2, z^2] sage: E.inject_variables() Defining xbar, ybar, zbar sage: x,y,z = (xbar,ybar,zbar) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 527a8aa878b..ebe25afc471 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -4001,6 +4001,72 @@ cdef class Polynomial(CommutativeAlgebraElement): raise NotImplementedError("splitting_field() is only implemented over number fields and finite fields") + def pseudo_quo_rem(self,other): + """ + Compute the pseudo-division of two polynomials. + + INPUT: + + - ``other`` -- a nonzero polynomial + + OUTPUT: + + `Q` and `R` such that `l^{m-n+1} \mathrm{self} = Q \cdot\mathrm{other} + R` + where `m` is the degree of this polynomial, `n` is the degree of + ``other``, `l` is the leading coefficient of ``other``. The result is + such that `\deg(R) < \deg(\mathrm{other})`. + + ALGORITHM: + + Algorithm 3.1.2 in [GTM138]_. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, sparse=True) + sage: p = x^4 + 6*x^3 + x^2 - x + 2 + sage: q = 2*x^2 - 3*x - 1 + sage: (quo,rem)=p.pseudo_quo_rem(q); quo,rem + (4*x^2 + 30*x + 51, 175*x + 67) + sage: 2^(4-2+1)*p == quo*q + rem + True + + sage: S. = R[] + sage: p = (-3*x^2 - x)*T^3 - 3*x*T^2 + (x^2 - x)*T + 2*x^2 + 3*x - 2 + sage: q = (-x^2 - 4*x - 5)*T^2 + (6*x^2 + x + 1)*T + 2*x^2 - x + sage: quo,rem=p.pseudo_quo_rem(q); quo,rem + ((3*x^4 + 13*x^3 + 19*x^2 + 5*x)*T + 18*x^4 + 12*x^3 + 16*x^2 + 16*x, + (-113*x^6 - 106*x^5 - 133*x^4 - 101*x^3 - 42*x^2 - 41*x)*T - 34*x^6 + 13*x^5 + 54*x^4 + 126*x^3 + 134*x^2 - 5*x - 50) + sage: (-x^2 - 4*x - 5)^(3-2+1) * p == quo*q + rem + True + + REFERENCES: + + .. [GTM138] Henri Cohen. A Course in Computational Number Theory. + Graduate Texts in Mathematics, vol. 138. Springer, 1993. + """ + if other.is_zero(): + raise ZeroDivisionError("Pseudo-division by zero is not possible") + + # if other is a constant, then R = 0 and Q = self * other^(deg(self)) + if other in self.parent().base_ring(): + return (self * other**(self.degree()), self.parent().zero()) + + R = self + B = other + Q = self.parent().zero() + e = self.degree() - other.degree() + 1 + d = B.leading_coefficient() + + while not R.degree() < B.degree(): + c = R.leading_coefficient() + diffdeg = R.degree() - B.degree() + Q = d*Q + self.parent()(c).shift(diffdeg) + R = d*R - c*B.shift(diffdeg) + e -= 1 + + q = d**e + return (q*Q,q*R) + @coerce_binop def gcd(self, other): """ @@ -4033,23 +4099,23 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: (2*x).gcd(0) x - One can easily add gcd functionality to new rings by providing a - method ``_gcd_univariate_polynomial``:: + One can easily add gcd functionality to new rings by providing a method + ``_gcd_univariate_polynomial``:: - sage: R. = QQ[] - sage: S. = R[] - sage: h1 = y*x - sage: h2 = y^2*x^2 - sage: h1.gcd(h2) + sage: O = ZZ[-sqrt(5)] + sage: R. = O[] + sage: a = O.1 + sage: p = x + a + sage: q = x^2 - 5 + sage: p.gcd(q) Traceback (most recent call last): ... - NotImplementedError: Univariate Polynomial Ring in x over Rational Field does not provide a gcd implementation for univariate polynomials - sage: T. = QQ[] - sage: R._gcd_univariate_polynomial = lambda f,g: S(T(f).gcd(g)) - sage: h1.gcd(h2) - x*y - sage: del R._gcd_univariate_polynomial - + NotImplementedError: Order in Number Field in a with defining polynomial x^2 - 5 does not provide a gcd implementation for univariate polynomials + sage: S. = O.number_field()[] + sage: O._gcd_univariate_polynomial = lambda f,g : R(S(f).gcd(S(g))) + sage: p.gcd(q) + x + a + sage: del O._gcd_univariate_polynomial """ if hasattr(self.base_ring(), '_gcd_univariate_polynomial'): return self.base_ring()._gcd_univariate_polynomial(self, other) @@ -6883,15 +6949,10 @@ cdef class Polynomial(CommutativeAlgebraElement): False sage: R(0).is_squarefree() False - - This can obviously fail if the ring does not implement ``gcd()``:: - sage: S. = QQ[] sage: R. = S[] - sage: (2*x*y).is_squarefree() # R does not provide a gcd implementation - Traceback (most recent call last): - ... - NotImplementedError: Univariate Polynomial Ring in y over Rational Field does not provide a gcd implementation for univariate polynomials + sage: (2*x*y).is_squarefree() + True sage: (2*x*y^2).is_squarefree() False diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 02d3b9c9fbb..1e11a5e2bbf 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -802,6 +802,73 @@ def quo_rem(self, other): rem = rem[:rem.degree()] - c*other[:d].shift(e) return (quo,rem) + def gcd(self,other,algorithm=None): + """ + Return the gcd of this polynomial and ``other`` + + INPUT: + + - ``other`` -- a polynomial defined over the same ring as this + polynomial. + + ALGORITHM: + + Two algorithms are provided: + + - ``generic``: Uses the generic implementation, which depends on the + base ring being a UFD or a field. + - ``dense``: The polynomials are converted to the dense representation, + their gcd is computed and is converted back to the sparse + representation. + + Default is ``dense`` for polynomials over ZZ and ``generic`` in the + other cases. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ,sparse=True) + sage: p = x^6 + 7*x^5 + 8*x^4 + 6*x^3 + 2*x^2 + x + 2 + sage: q = 2*x^4 - x^3 - 2*x^2 - 4*x - 1 + sage: gcd(p,q) + x^2 + x + 1 + sage: gcd(p, q, algorithm = "dense") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "generic") + x^2 + x + 1 + sage: gcd(p, q, algorithm = "foobar") + Traceback (most recent call last): + ... + ValueError: Unknown algorithm 'foobar' + """ + + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.rings.arith import lcm + + if algorithm is None: + if self.base_ring() == ZZ: + algorithm = "dense" + else: + algorithm = "generic" + if algorithm=="dense": + S = self.parent() + # FLINT is faster but a bug makes the conversion extremely slow, + # so NTL is used in those cases where the conversion is too slow. Cf + # + sd = self.degree() + od = other.degree() + if max(sd,od)<100 or \ + min(len(self.__coeffs)/sd, len(other.__coeffs)/od)>.06: + implementation="FLINT" + else: + implementation="NTL" + D = PolynomialRing(S.base_ring(),'x',implementation=implementation) + g = D(self).gcd(D(other)) + return S(g) + elif algorithm=="generic": + return Polynomial.gcd(self,other) + else: + raise ValueError("Unknown algorithm '%s'" % algorithm) + def reverse(self, degree=None): """ Return this polynomial but with the coefficients reversed. @@ -840,7 +907,6 @@ def truncate(self, n): """ return self[:n] - class Polynomial_generic_domain(Polynomial, IntegralDomainElement): def __init__(self, parent, is_gen=False, construct=False): Polynomial.__init__(self, parent, is_gen=is_gen) diff --git a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx index 9e10335ee2c..46c8e1dcbfc 100644 --- a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +++ b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx @@ -68,7 +68,7 @@ cdef class PolynomialRealDense(Polynomial): TESTS: - Check that errors and interrupts are handled properly (see #10100):: + Check that errors and interrupts are handled properly (see :trac:`10100`):: sage: a = var('a') sage: PolynomialRealDense(RR['x'], [1,a]) @@ -83,7 +83,7 @@ cdef class PolynomialRealDense(Polynomial): sage: sig_on_count() 0 - Test that we don't clean up uninitialized coefficients (#9826):: + Test that we don't clean up uninitialized coefficients (:trac:`9826`):: sage: k. = GF(7^3) sage: P. = PolynomialRing(k) @@ -91,6 +91,11 @@ cdef class PolynomialRealDense(Polynomial): Traceback (most recent call last): ... TypeError: Unable to convert x (='a') to real number. + + Check that :trac:`17190` is fixed:: + + sage: RR['x']({}) + 0 """ Polynomial.__init__(self, parent, is_gen=is_gen) self._base_ring = parent._base @@ -108,11 +113,7 @@ cdef class PolynomialRealDense(Polynomial): elif isinstance(x, (int, float, Integer, Rational, RealNumber)): x = [x] elif isinstance(x, dict): - degree = max(x.keys()) - c = [0] * (degree+1) - for i, a in x.items(): - c[i] = a - x = c + x = self._dict_to_list(x,self._base_ring.zero()) elif isinstance(x, pari_gen): x = [self._base_ring(w) for w in x.list()] elif not isinstance(x, list): @@ -665,9 +666,9 @@ cdef class PolynomialRealDense(Polynomial): sage: f = PolynomialRealDense(RR['x']) sage: f(12) 0.000000000000000 - + TESTS:: - + sage: R. = RR[] # :trac:`17311` sage: (x^2+1)(x=5) 26.0000000000000 @@ -676,13 +677,13 @@ cdef class PolynomialRealDense(Polynomial): xx = args[0] else: return Polynomial.__call__(self, *args, **kwds) - + if not isinstance(xx, RealNumber): if self._base_ring.has_coerce_map_from(parent(xx)): xx = self._base_ring(xx) else: return Polynomial.__call__(self, xx) - + cdef Py_ssize_t i cdef mp_rnd_t rnd = self._base_ring.rnd cdef RealNumber x = xx @@ -713,7 +714,7 @@ cdef class PolynomialRealDense(Polynomial): mpfr_mul(res.value, res.value, x.value, rnd) mpfr_add(res.value, res.value, self._coeffs[i], rnd) return res - + def change_ring(self, R): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/refine_root.pyx b/src/sage/rings/polynomial/refine_root.pyx new file mode 100644 index 00000000000..aafbeb442f3 --- /dev/null +++ b/src/sage/rings/polynomial/refine_root.pyx @@ -0,0 +1,141 @@ +""" +Refine polynomial roots using Newton--Raphson + +This is an implementation of the Newton--Raphson algorithm to +approximate roots of complex polynomials. The implementation +is based on interval arithmetic + +AUTHORS: + +- Carl Witty (2007-11-18): initial version +""" + +#***************************************************************************** +# Copyright (C) 2007 Carl Witty +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.rings.real_mpfi import RealIntervalField +from sage.rings.complex_interval_field import ComplexIntervalField + + +def refine_root(ip, ipd, irt, fld): + """ + We are given a polynomial and its derivative (with complex + interval coefficients), an estimated root, and a complex interval + field to use in computations. We use interval arithmetic to + refine the root and prove that we have in fact isolated a unique + root. + + If we succeed, we return the isolated root; if we fail, we return + None. + + EXAMPLES:: + + sage: from sage.rings.polynomial.refine_root import refine_root + sage: x = polygen(ZZ) + sage: p = x^9 - 1 + sage: ip = CIF['x'](p); ip + x^9 - 1 + sage: ipd = CIF['x'](p.derivative()); ipd + 9*x^8 + sage: irt = CIF(CC(cos(2*pi/9), sin(2*pi/9))); irt + 0.76604444311897802? + 0.64278760968653926?*I + sage: ip(irt) + 0.?e-14 + 0.?e-14*I + sage: ipd(irt) + 6.89439998807080? - 5.78508848717885?*I + sage: refine_root(ip, ipd, irt, CIF) + 0.766044443118978? + 0.642787609686540?*I + """ + + # There has got to be a better way to do this, but I don't know + # what it is... + + # We start with a basic fact: if we do an interval Newton-Raphson + # step, and the refined interval is contained in the original interval, + # then the refined interval contains exactly one root. + + # Unfortunately, our initial estimated root almost certainly does not + # contain the actual root (our initial interval is a point, which + # is exactly equal to whatever floating-point estimate we got from + # the external solver). So we need to do multiple Newton-Raphson + # steps, and check this inclusion property on each step. + + # After a few steps of refinement, if the initial root estimate was + # close to a root, we should have an essentially perfect interval + # bound on the root (since Newton-Raphson has quadratic convergence), + # unless either the real or imaginary component of the root is zero. + # If the real or imaginary component is zero, then we could spend + # a long time computing closer and closer approximations to that + # component. (This doesn't happen for non-zero components, because + # of the imprecision of floating-point numbers combined with the + # outward interval rounding; but close to zero, MPFI provides + # extremely precise numbers.) + + # If the root is actually a real root, but we start with an imaginary + # component, we can bounce back and forth between having a positive + # and negative imaginary component, without ever hitting zero. + # To deal with this, on every other Newton-Raphson step, instead of + # replacing the old interval with the new one, we take the union. + + # If the containment check continues to fail many times in a row, + # we give up and return None; we also return None if we detect + # that the slope in our current interval is not bounded away + # from zero at any step. + + # After every refinement step, we check to see if the real or + # imaginary component of our interval includes zero. If so, we + # try setting it to exactly zero. This gives us a good chance of + # detecting real roots. However, we do this replacement at most + # once per component. + + refinement_steps = 10 + + smashed_real = False + smashed_imag = False + + for i in range(refinement_steps): + slope = ipd(irt) + if slope.contains_zero(): + return None + center = fld(irt.center()) + val = ip(center) + + nirt = center - val / slope + # print irt, nirt, (nirt in irt), nirt.diameter(), irt.diameter(), center, val, slope + if nirt in irt and (nirt.diameter() >= irt.diameter() >> 3 or i >= 8): + # If the new diameter is much less than the original diameter, + # then we have not yet converged. (Perhaps we were asked + # for a particularly high-precision result.) So we don't + # return yet. + return nirt + + if i & 1: + irt = nirt + else: + irt = irt.union(nirt) + # If we don't find a root after a while, try (approximately) + # tripling the size of the region. + if i >= 6: + rD = irt.real().absolute_diameter() + iD = irt.imag().absolute_diameter() + md = max(rD, iD) + md_intv = RealIntervalField(rD.prec())(-md, md) + md_cintv = ComplexIntervalField(rD.prec())(md_intv, md_intv) + irt = irt + md_cintv + + if not smashed_real and irt.real().contains_zero(): + irt = irt.parent()(0, irt.imag()) + smashed_real = True + if not smashed_imag and irt.imag().contains_zero(): + irt = irt.parent()(irt.real(), 0) + smashed_imag = True + + return None diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index e2b3c29cd68..9881351ef45 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -89,24 +89,6 @@ cdef class PowerSeries_poly(PowerSeries): """ return self.__class__, (self._parent, self.__f, self._prec, self.__is_gen) - def __richcmp__(left, right, int op): - """ - Used for comparing power series. - - EXAMPLES:: - - sage: R. = ZZ[[]] - sage: f = 1 + t + t^7 - 5*t^10 - sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) - sage: f == f - True - sage: f < g - False - sage: f == g - True - """ - return (left)._richcmp(right, op) - def polynomial(self): """ Return the underlying polynomial of self. diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index ab3f67a20cf..ae25cd36fea 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -315,18 +315,6 @@ cdef class PowerSeries(AlgebraElement): S = self._parent.change_ring(R) return S(self) - def __cmp__(left, right): - """ - Called by comparison operations. - - EXAMPLES:: - - sage: R. = PowerSeriesRing(ZZ) - sage: 1+x^2 < 2-x - True - """ - return (left)._cmp(right) - cpdef int _cmp_(self, Element right) except -2: r""" Comparison of self and ``right``. @@ -358,9 +346,23 @@ cdef class PowerSeries(AlgebraElement): sage: 1 - 2*q + q^2 +O(q^3) == 1 - 2*q^2 + q^2 + O(q^4) False + :: + + sage: R. = ZZ[[]] + sage: 1 + t^2 < 2 - t + True + sage: f = 1 + t + t^7 - 5*t^10 + sage: g = 1 + t + t^7 - 5*t^10 + O(t^15) + sage: f == f + True + sage: f < g + False + sage: f == g + True + TESTS: - Ticket :trac:`9457` is fixed:: + :trac:`9457` is fixed:: sage: A. = PowerSeriesRing(ZZ) sage: g = t + t^3 + t^5 + O(t^6); g diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 87143860d09..8fc07afb441 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -362,8 +362,8 @@ AA(2) Just for fun, let's try ``sage_input`` on a very complicated expression. The -output of this example changed with the rewritting of polynomial multiplication -algorithms in #10255:: +output of this example changed with the rewriting of polynomial multiplication +algorithms in :trac:`10255`:: sage: rt2 = sqrt(AA(2)) sage: rt3 = sqrt(QQbar(3)) @@ -4553,13 +4553,13 @@ def complex_number(self, field): EXAMPLES:: sage: a = QQbar.zeta(5) - sage: a.complex_number(CIF) + sage: a.complex_number(CC) 0.309016994374947 + 0.951056516295154*I - sage: (a + a.conjugate()).complex_number(CIF) + sage: (a + a.conjugate()).complex_number(CC) 0.618033988749895 - 5.42101086242752e-20*I """ v = self.interval(ComplexIntervalField(field.prec())) - return v.center() + return field(v) def complex_exact(self, field): r""" @@ -5204,21 +5204,7 @@ def real_number(self, field): 1.41421356237309 """ v = self.interval(RealIntervalField(field.prec())) - - mode = field.rounding_mode() - if mode == 'RNDN': - return v.center() - if mode == 'RNDD': - return v.lower() - if mode == 'RNDU': - return v.upper() - if mode == 'RNDZ': - if v > 0: - return field(v.lower()) - elif v < 0: - return field(v.upper()) - else: - return field(0) + return field(v) _mpfr_ = real_number diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 63fa7fa0225..d1680334abe 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -709,9 +709,9 @@ cdef class Rational(sage.structure.element.FieldElement): l = self.continued_fraction_list() return ContinuedFraction_periodic(l) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, sage.structure.element.Element right) except -2: """ - Rich comparison between two rational numbers. + Compare two rational numbers. INPUT: @@ -730,9 +730,6 @@ cdef class Rational(sage.structure.element.FieldElement): sage: 4/5 < 0.8 False """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, sage.structure.element.Element right) except -2: cdef int i i = mpq_cmp((left).value, (right).value) if i < 0: return -1 diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index f7ad8c92f67..7c7722dae9e 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -1392,7 +1392,7 @@ cdef class RealBall(RingElement): """ return arb_is_exact(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right``. @@ -1400,191 +1400,172 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True - sage: a = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb - False - sage: b = RBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb - False - sage: a == b # optional - arb - False - """ - return (left)._richcmp(right, op) + sage: from sage.rings.real_arb import RealBallField # optional - arb + sage: RBF = RealBallField() # optional - arb + sage: a = RBF(1) # optional - arb + sage: b = RBF(1) # optional - arb + sage: a is b # optional - arb + False + sage: a == b # optional - arb + True + sage: a = RBF(1/3) # optional - arb + sage: a.is_exact() # optional - arb + False + sage: b = RBF(1/3) # optional - arb + sage: b.is_exact() # optional - arb + False + sage: a == b # optional - arb + False - cpdef _richcmp_(left, Element right, int op): - """ - Compare ``left`` and ``right``. + TESTS: - For more information, see :mod:`sage.rings.real_arb`. + Balls whose intersection consists of one point:: - EXAMPLES:: + sage: a = RBF(RIF(1, 2)) # optional - arb + sage: b = RBF(RIF(2, 4)) # optional - arb + sage: a < b # optional - arb + False + sage: a > b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb - False - sage: a == b # optional - arb - True + Balls with non-trivial intersection:: - TESTS: + sage: a = RBF(RIF(1, 4)) # optional - arb + sage: a = RBF(RIF(2, 5)) # optional - arb + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False - Balls whose intersection consists of one point:: - - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb - False - sage: a > b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Balls with non-trivial intersection:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: a = RBF(RIF(2, 5)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - One ball contained in another:: - - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: b = RBF(RIF(2, 3)) # optional - arb - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - False - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - False - - Disjoint balls:: - - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/2) # optional - arb - sage: a < b # optional - arb - True - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - False - sage: a == b # optional - arb - False - sage: a != b # optional - arb - True - - Exact elements:: - - sage: a = RBF(2) # optional - arb - sage: b = RBF(2) # optional - arb - sage: a.is_exact() # optional - arb - True - sage: b.is_exact() # optional - arb - True - sage: a < b # optional - arb - False - sage: a <= b # optional - arb - True - sage: a > b # optional - arb - False - sage: a >= b # optional - arb - True - sage: a == b # optional - arb - True - sage: a != b # optional - arb - False - - Special values:: - - sage: inf = RBF(+infinity) # optional - arb - sage: other_inf = RBF(+infinity, 42.r) # optional - arb - sage: neg_inf = RBF(-infinity) # optional - arb - sage: extended_line = 1/RBF(0) # optional - arb - sage: exact_nan = inf - inf # optional - arb - sage: exact_nan.mid(), exact_nan.rad() # optional - arb - (NaN, 0.00000000) - sage: other_exact_nan = inf - inf # optional - arb - - :: - - sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb - (False, False, False) - sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb - (False, False, False) - sage: from operator import eq, ne, le, lt, ge, gt # optional - arb - sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb - sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb - False - sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb - False - - :: - - sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb - True - sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb - True - sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb - True - sage: neg_inf < extended_line or extended_line < inf # optional - arb - False - sage: inf > extended_line or extended_line > neg_inf # optional - arb - False - - :: - - sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb - ....: for b in [inf, neg_inf, other_inf]) - True - sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb - ....: for b2 in [inf, neg_inf, a, extended_line] - ....: if not b1 is b2) - False - sage: all(b1 != b2 and not b1 == b2 # optional - arb - ....: for b1 in [inf, neg_inf, a] - ....: for b2 in [inf, neg_inf, a] - ....: if not b1 is b2) - True - sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb - True - sage: any(inf < b or b > inf # optional - arb - ....: for b in [inf, other_inf, a, extended_line]) - False - sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb - False + One ball contained in another:: + + sage: a = RBF(RIF(1, 4)) # optional - arb + sage: b = RBF(RIF(2, 3)) # optional - arb + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + False + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + False + + Disjoint balls:: + + sage: a = RBF(1/3) # optional - arb + sage: b = RBF(1/2) # optional - arb + sage: a < b # optional - arb + True + sage: a <= b # optional - arb + True + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + False + sage: a == b # optional - arb + False + sage: a != b # optional - arb + True + + Exact elements:: + + sage: a = RBF(2) # optional - arb + sage: b = RBF(2) # optional - arb + sage: a.is_exact() # optional - arb + True + sage: b.is_exact() # optional - arb + True + sage: a < b # optional - arb + False + sage: a <= b # optional - arb + True + sage: a > b # optional - arb + False + sage: a >= b # optional - arb + True + sage: a == b # optional - arb + True + sage: a != b # optional - arb + False + + Special values:: + + sage: inf = RBF(+infinity) # optional - arb + sage: other_inf = RBF(+infinity, 42.r) # optional - arb + sage: neg_inf = RBF(-infinity) # optional - arb + sage: extended_line = 1/RBF(0) # optional - arb + sage: exact_nan = inf - inf # optional - arb + sage: exact_nan.mid(), exact_nan.rad() # optional - arb + (NaN, 0.00000000) + sage: other_exact_nan = inf - inf # optional - arb + + :: + + sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb + (False, False, False) + sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb + (False, False, False) + sage: from operator import eq, ne, le, lt, ge, gt # optional - arb + sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb + sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb + False + sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb + False + + :: + + sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb + True + sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb + True + sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb + True + sage: neg_inf < extended_line or extended_line < inf # optional - arb + False + sage: inf > extended_line or extended_line > neg_inf # optional - arb + False + + :: + + sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb + ....: for b in [inf, neg_inf, other_inf]) + True + sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb + ....: for b2 in [inf, neg_inf, a, extended_line] + ....: if not b1 is b2) + False + sage: all(b1 != b2 and not b1 == b2 # optional - arb + ....: for b1 in [inf, neg_inf, a] + ....: for b2 in [inf, neg_inf, a] + ....: if not b1 is b2) + True + sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb + True + sage: any(inf < b or b > inf # optional - arb + ....: for b in [inf, other_inf, a, extended_line]) + False + sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb + False """ cdef RealBall lt, rt cdef arb_t difference diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index bfe6fe659f0..fce4acfed0d 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -668,6 +668,26 @@ cdef class LazyFieldElement(FieldElement): True sage: RLF(3) == RLF(4) False + sage: RLF(3) < RLF(5/3) + False + + TESTS:: + + sage: from sage.rings.real_lazy import LazyBinop + sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) + False + sage: from sage.rings.real_lazy import LazyWrapper + sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) + False + sage: from sage.rings.real_lazy import LazyUnop + sage: RLF(3) < LazyUnop(RLF, 2, sqrt) + False + sage: from sage.rings.real_lazy import LazyNamedUnop + sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') + False + sage: from sage.rings.real_lazy import LazyConstant + sage: RLF(3) < LazyConstant(RLF, 'e') + False """ left = self try: @@ -679,17 +699,6 @@ cdef class LazyFieldElement(FieldElement): left, right = self.approx(), other.approx() return cmp(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: RLF(3) < RLF(5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -993,18 +1002,6 @@ cdef class LazyWrapper(LazyFieldElement): """ return not not self._value - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyWrapper - sage: LazyWrapper(RLF, 3) < LazyWrapper(RLF, 5/3) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1176,18 +1173,6 @@ cdef class LazyBinop(LazyFieldElement): # We only do a call here because it is a python call. return self._op(left, right) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyBinop - sage: RLF(3) < LazyBinop(RLF, 5, 3, operator.div) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1280,18 +1265,6 @@ cdef class LazyUnop(LazyFieldElement): return ~arg return self._op(self._arg.eval(R)) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyUnop - sage: RLF(3) < LazyUnop(RLF, 2, sqrt) - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1411,18 +1384,6 @@ cdef class LazyNamedUnop(LazyUnop): interval_field = self._parent.interval_field() return self.eval(interval_field._middle_field()) - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyNamedUnop - sage: RLF(3) < LazyNamedUnop(RLF, 0, 'sin') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. @@ -1549,18 +1510,6 @@ cdef class LazyConstant(LazyFieldElement): self._extra_args = args return self - def __richcmp__(left, right, int op): - """ - Perform a rich comparison between ``left`` and ``right``. - - EXAMPLES:: - - sage: from sage.rings.real_lazy import LazyConstant - sage: RLF(3) < LazyConstant(RLF, 'e') - False - """ - return (left)._richcmp(right, op) - def __hash__(self): """ Return the hash value of ``self``. diff --git a/src/sage/rings/real_mpfi.pxd b/src/sage/rings/real_mpfi.pxd index 5996b03d2f5..d90d471627e 100644 --- a/src/sage/rings/real_mpfi.pxd +++ b/src/sage/rings/real_mpfi.pxd @@ -2,15 +2,13 @@ from sage.libs.mpfi cimport * cimport sage.rings.ring -cimport sage.structure.element from sage.structure.element cimport RingElement -from rational import Rational from rational cimport Rational cimport real_mpfr -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) # forward decl +cdef class RealIntervalFieldElement(RingElement) # forward decl cdef class RealIntervalField_class(sage.rings.ring.Field): cdef int __prec @@ -35,7 +33,7 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): cdef RealIntervalFieldElement _new(self) -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): cdef mpfi_t value cdef char init cdef RealIntervalFieldElement _new(self) diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 85d3b750787..398915c3dd3 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -239,6 +239,7 @@ Comparisons with numpy types are right (see :trac:`17758` and :trac:`18076`):: import math # for log import sys +import operator include 'sage/ext/interrupt.pxi' include "sage/ext/cdefs.pxi" @@ -246,32 +247,21 @@ from cpython.mem cimport * from cpython.string cimport * cimport sage.rings.ring -import sage.rings.ring - cimport sage.structure.element from sage.structure.element cimport RingElement, Element, ModuleElement -import sage.structure.element cimport real_mpfr -from real_mpfr cimport RealField_class, RealNumber -from real_mpfr import RealField -import real_mpfr +from real_mpfr cimport RealField_class, RealNumber, RealField +from sage.libs.mpfr cimport MPFR_RNDN, MPFR_RNDZ, MPFR_RNDU, MPFR_RNDD, MPFR_RNDA -import operator - -from integer import Integer from integer cimport Integer - -from real_double import RealDoubleElement from real_double cimport RealDoubleElement import sage.rings.complex_field - import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealIntervalFieldElement(sage.structure.element.RingElement) #***************************************************************************** # @@ -1115,16 +1105,13 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): return self(-1) raise ValueError, "No %sth root of unity in self"%n -R = RealIntervalField() #***************************************************************************** # # RealIntervalFieldElement -- element of Real Field # -# -# #***************************************************************************** -cdef class RealIntervalFieldElement(sage.structure.element.RingElement): +cdef class RealIntervalFieldElement(RingElement): """ A real number interval. """ @@ -3073,33 +3060,59 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): # Conversions ########################################### -# def __float__(self): -# return mpfr_get_d(self.value, (self._parent).rnd) - -# def __int__(self): -# """ -# Returns integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return int(s[:i], 32) + def _mpfr_(self, RealField_class field): + """ + Convert to a real field, honoring the rounding mode of the + real field. -# def __long__(self): -# """ -# Returns long integer truncation of this real number. -# """ -# s = self.str(32) -# i = s.find('.') -# return long(s[:i], 32) + EXAMPLES:: -# def __complex__(self): -# return complex(float(self)) + sage: a = RealIntervalField(30)("1.2") + sage: RR(a) + 1.20000000018626 + sage: b = RIF(-1, 3) + sage: RR(b) + 1.00000000000000 -# def _complex_number_(self): -# return sage.rings.complex_field.ComplexField(self.prec())(self) + With different rounding modes:: -# def _pari_(self): -# return sage.libs.pari.all.pari.new_with_bits_prec(str(self), (self._parent).__prec) + sage: RealField(53, rnd="RNDU")(a) + 1.20000000111759 + sage: RealField(53, rnd="RNDD")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDZ")(a) + 1.19999999925494 + sage: RealField(53, rnd="RNDU")(b) + 3.00000000000000 + sage: RealField(53, rnd="RNDD")(b) + -1.00000000000000 + sage: RealField(53, rnd="RNDZ")(b) + 0.000000000000000 + """ + cdef RealNumber x = field._new() + if field.rnd == MPFR_RNDN: + mpfi_mid(x.value, self.value) + elif field.rnd == MPFR_RNDD: + mpfi_get_left(x.value, self.value) + elif field.rnd == MPFR_RNDU: + mpfi_get_right(x.value, self.value) + elif field.rnd == MPFR_RNDZ: + if mpfi_is_strictly_pos_default(self.value): # interval is > 0 + mpfi_get_left(x.value, self.value) + elif mpfi_is_strictly_neg_default(self.value): # interval is < 0 + mpfi_get_right(x.value, self.value) + else: + mpfr_set_zero(x.value, 1) # interval contains 0 + elif field.rnd == MPFR_RNDA: + # return the endpoint which is furthest from 0 + lo, hi = self.endpoints() + if hi.abs() >= lo.abs(): + mpfi_get_right(x.value, self.value) + else: + mpfi_get_left(x.value, self.value) + else: + raise AssertionError("%s has unknown rounding mode"%field) + return x def unique_sign(self): r""" @@ -3445,11 +3458,10 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return mpfi_nan_p(self.value) - def __richcmp__(left, right, int op): + cpdef _richcmp_(left, Element right, int op): """ - Rich comparison between ``left`` and ``right``. - - For more information, see :mod:`sage.rings.real_mpfi`. + Implements comparisons between intervals. (See the file header + comment for more information on interval comparison.) EXAMPLES:: @@ -3469,13 +3481,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): False sage: RIF(0, 2) > RIF(2, 3) False - """ - return (left)._richcmp(right, op) - - cpdef _richcmp_(left, Element right, int op): - """ - Implements comparisons between intervals. (See the file header - comment for more information on interval comparison.) EXAMPLES:: @@ -3666,7 +3671,7 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): """ return not (mpfr_zero_p(&self.value.left) and mpfr_zero_p(&self.value.right)) - def __cmp__(left, right): + cpdef int _cmp_(left, Element right) except -2: """ Compare two intervals lexicographically. @@ -3694,12 +3699,6 @@ cdef class RealIntervalFieldElement(sage.structure.element.RingElement): sage: cmp(RIF(0, 1), RIF(0, 1/2)) 1 """ - return (left)._cmp(right) - - cpdef int _cmp_(left, Element right) except -2: - """ - Implements the lexicographic total order on intervals. - """ cdef RealIntervalFieldElement lt, rt lt = left diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index c7410b04368..6d2d2653696 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -490,14 +490,14 @@ def __init__(self, g, sink=None): INPUT: - - ``g`` -- dict for directed multigraph with edges weighted by - nonnegative integers (see NOTE), a Graph or DiGraph. + - ``g`` -- dict for directed multigraph with edges weighted by + nonnegative integers (see NOTE), a Graph or DiGraph. - - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the - designated sink are ignored for the purposes of stabilization. It is - assumed that every vertex has a directed path into the sink. If the - ``sink`` argument is omitted, the first vertex in the list of the - Sandpile's vertices is set as the sink. + - ``sink`` -- (optional) A sink vertex. Any outgoing edges from the + designated sink are ignored for the purposes of stabilization. It is + assumed that every vertex has a directed path into the sink. If the + ``sink`` argument is omitted, the first vertex in the list of the + Sandpile's vertices is set as the sink. OUTPUT: @@ -517,41 +517,37 @@ def __init__(self, g, sink=None): sage: G = Sandpile(g,'d') Here is a square with unweighted edges. In this example, the graph is - also undirected. - - :: + also undirected. :: sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]} sage: G = Sandpile(g,3) In the following example, multiple edges and loops in the dictionary - become edge weights in the Sandpile. - - :: - - sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) - sage: s.laplacian() - [ 3 -1 -1 -1] - [-1 4 -3 0] - [-1 -2 3 0] - [ 0 0 0 0] - sage: s.dict() - {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} - - Sandpiles can be created from Graphs and DiGraphs. - - sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) - sage: s = Sandpile(g) - sage: s.dict() - {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} - sage: s.sink() - 0 - sage: s = sandpiles.Cycle(4) - sage: s.laplacian() - [ 2 -1 0 -1] - [-1 2 -1 0] - [ 0 -1 2 -1] - [-1 0 -1 2] + become edge weights in the Sandpile. :: + + sage: s = Sandpile({0:[1,2,3], 1:[0,1,2,2,2], 2:[1,1,0,2,2,2,2]}) + sage: s.laplacian() + [ 3 -1 -1 -1] + [-1 4 -3 0] + [-1 -2 3 0] + [ 0 0 0 0] + sage: s.dict() + {0: {1: 1, 2: 1, 3: 1}, 1: {0: 1, 1: 1, 2: 3}, 2: {0: 1, 1: 2, 2: 4}} + + Sandpiles can be created from Graphs and DiGraphs. :: + + sage: g = DiGraph({0:{1:2,2:4}, 1:{1:3,2:1}, 2:{1:7}}, weighted=True) + sage: s = Sandpile(g) + sage: s.dict() + {0: {1: 2, 2: 4}, 1: {0: 0, 1: 3, 2: 1}, 2: {0: 0, 1: 7}} + sage: s.sink() + 0 + sage: s = sandpiles.Cycle(4) + sage: s.laplacian() + [ 2 -1 0 -1] + [-1 2 -1 0] + [ 0 -1 2 -1] + [-1 0 -1 2] .. NOTE:: diff --git a/src/sage/sat/all.py b/src/sage/sat/all.py new file mode 100644 index 00000000000..d4beeb2a9a1 --- /dev/null +++ b/src/sage/sat/all.py @@ -0,0 +1,2 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.sat.solvers.satsolver', 'SAT') diff --git a/src/sage/sat/converters/polybori.py b/src/sage/sat/converters/polybori.py index bbc3b64ab8c..11b5d295b14 100644 --- a/src/sage/sat/converters/polybori.py +++ b/src/sage/sat/converters/polybori.py @@ -154,7 +154,7 @@ def var(self, m=None, decision=None): INPUT: - ``m`` - something the new variables maps to, usually a monomial - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: diff --git a/src/sage/sat/solvers/sat_lp.py b/src/sage/sat/solvers/sat_lp.py new file mode 100644 index 00000000000..1a74206f162 --- /dev/null +++ b/src/sage/sat/solvers/sat_lp.py @@ -0,0 +1,145 @@ +r""" +Solve SAT problems Integer Linear Programming + +The class defined here is a :class:`~sage.sat.solvers.satsolver.SatSolver` that +solves its instance using :class:`MixedIntegerLinearProgram`. Its performance +can be expected to be slower than when using +:class:`~sage.sat.solvers.cryptominisat.cryptominisat.CryptoMiniSat`. +""" +from satsolver import SatSolver +from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException + +class SatLP(SatSolver): + def __init__(self, solver=None): + r""" + Initializes the instance + + INPUT: + + - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) + solver to be used. If set to ``None``, the default one is used. For + more information on LP solvers and which default solver is used, see + the method + :meth:`solve ` + of the class + :class:`MixedIntegerLinearProgram `. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + SatSolver.__init__(self) + self._LP = MixedIntegerLinearProgram() + self._vars = self._LP.new_variable(binary=True) + + def var(self): + """ + Return a *new* variable. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + """ + nvars = n = self._LP.number_of_variables() + while nvars==self._LP.number_of_variables(): + n += 1 + self._vars[n] # creates the variable if needed + return n + + def nvars(self): + """ + Return the number of variables. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: S.var() + 1 + sage: S.var() + 2 + sage: S.nvars() + 2 + """ + return self._LP.number_of_variables() + + def add_clause(self, lits): + """ + Add a new clause to set of clauses. + + INPUT: + + - ``lits`` - a tuple of integers != 0 + + .. note:: + + If any element ``e`` in ``lits`` has ``abs(e)`` greater + than the number of variables generated so far, then new + variables are created automatically. + + EXAMPLE:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + sage: for u,v in graphs.CycleGraph(6).edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + """ + if 0 in lits: + raise ValueError("0 should not appear in the clause: {}".format(lits)) + p = self._LP + p.add_constraint(p.sum(self._vars[x] if x>0 else 1-self._vars[-x] for x in lits) + >=1) + + def __call__(self): + """ + Solve this instance. + + OUTPUT: + + - If this instance is SAT: A tuple of length ``nvars()+1`` + where the ``i``-th entry holds an assignment for the + ``i``-th variables (the ``0``-th entry is always ``None``). + + - If this instance is UNSAT: ``False`` + + EXAMPLE:: + + sage: def is_bipartite_SAT(G): + ....: S=SAT(solver="LP"); S + ....: for u,v in G.edges(labels=False): + ....: u,v = u+1,v+1 + ....: S.add_clause((u,v)) + ....: S.add_clause((-u,-v)) + ....: return S + sage: S = is_bipartite_SAT(graphs.CycleGraph(6)) + sage: S() # random + [None, True, False, True, False, True, False] + sage: True in S() + True + sage: S = is_bipartite_SAT(graphs.CycleGraph(7)) + sage: S() + False + """ + try: + self._LP.solve() + except MIPSolverException: + return False + + b = self._LP.get_values(self._vars) + n = max(b) + return [None]+[bool(b.get(i,0)) for i in range(1,n+1)] + + def __repr__(self): + """ + TESTS:: + + sage: S=SAT(solver="LP"); S + an ILP-based SAT Solver + """ + return "an ILP-based SAT Solver" diff --git a/src/sage/sat/solvers/satsolver.pyx b/src/sage/sat/solvers/satsolver.pyx index 13e0e892607..734393d4800 100644 --- a/src/sage/sat/solvers/satsolver.pyx +++ b/src/sage/sat/solvers/satsolver.pyx @@ -14,6 +14,7 @@ AUTHORS: - Martin Albrecht (2012): first version """ +from sage.misc.package import PackageNotFoundError cdef class SatSolver: def __cinit__(self, *args, **kwds): @@ -33,7 +34,7 @@ cdef class SatSolver: INPUT: - - ``decision`` - is this variable a deicison variable? + - ``decision`` - is this variable a decision variable? EXAMPLE:: @@ -275,3 +276,60 @@ cdef class SatSolver: """ return ["gens"] +def SAT(solver=None): + r""" + Return a :class:`SatSolver` instance. + + Through this class, one can define and solve `SAT + `__ problems. + + INPUT: + + - ``solver`` (string) -- select a solver. Admissible values are: + + - ``"cryptominisat"`` -- note that the cryptominisat package must be + installed. + + - ``"LP"`` -- use :class:`~sage.sat.solvers.sat_lp.SatLP` to solve the + SAT instance. + + - ``None`` (default) -- use CryptoMiniSat if available, and a LP solver + otherwise. + + EXAMPLE:: + + sage: SAT(solver="LP") + an ILP-based SAT Solver + + TESTS:: + + sage: SAT(solver="Wouhouuuuuu") + Traceback (most recent call last): + ... + ValueError: Solver 'Wouhouuuuuu' is not available + + Forcing CryptoMiniSat:: + + sage: SAT(solver="cryptominisat") # optional - cryptominisat + CryptoMiniSat + #vars: 0, #lits: 0, #clauses: 0, #learnt: 0, #assigns: 0 + + """ + if solver is None: + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + solver = "cryptominisat" + except ImportError: + solver = "LP" + + if solver == 'cryptominisat': + try: + from sage.sat.solvers.cryptominisat.cryptominisat import CryptoMiniSat + except ImportError: + raise PackageNotFoundError("cryptominisat") + return CryptoMiniSat() + elif solver == "LP": + from sat_lp import SatLP + return SatLP() + else: + raise ValueError("Solver '{}' is not available".format(solver)) diff --git a/src/sage/sets/cartesian_product.py b/src/sage/sets/cartesian_product.py index 6a2a358d5d0..0afc8910e2d 100644 --- a/src/sage/sets/cartesian_product.py +++ b/src/sage/sets/cartesian_product.py @@ -320,3 +320,17 @@ def __iter__(self): 1 """ return iter(self.value) + + def cartesian_factors(self): + r""" + Return the tuple of elements that compose this element. + + EXAMPLES:: + + sage: A = cartesian_product([ZZ, RR]) + sage: A((1, 1.23)).cartesian_factors() + (1, 1.23000000000000) + sage: type(_) + + """ + return self.value diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 5b71a088b05..b0d93cb1035 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -128,6 +128,8 @@ underscores). # by any element. Derived class must call __init__ ################################################################## +from libc.limits cimport LONG_MAX, LONG_MIN + include "sage/ext/python.pxi" from sage.ext.stdsage cimport * @@ -3279,10 +3281,26 @@ cdef class InfinityElement(RingElement): return ZZ(0) cdef class PlusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(+infinity) + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + """ + return LONG_MAX cdef class MinusInfinityElement(InfinityElement): - pass + def __hash__(self): + r""" + TESTS:: + + sage: hash(-infinity) + -9223372036854775808 # 64-bit + -2147483648 # 32-bit + """ + return LONG_MIN ################################################################################# diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index fdcf079a4ec..67c72e5a8fb 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -251,6 +251,18 @@ cdef class SymbolicRing(CommutativeRing): sage: bool(si == CC.0) True + Polynomial ring element factorizations:: + + sage: R. = QQ[] + sage: SR(factor(5*x^2 - 5)) + 5*(x + 1)*(x - 1) + sage: R. = QQ[] + sage: SR(factor(x^2 - y^2)) + (x + y)*(x - y) + sage: R. = QQ[] + sage: SR(factor(x^2*y^3 + x^2*y^2*z - x*y^3 - x*y^2*z - 2*x*y*z - 2*x*z^2 + 2*y*z + 2*z^2)) + (x*y^2 - 2*z)*(x - 1)*(y + z) + Asymptotic expansions:: sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^ZZ', coefficient_ring=ZZ) @@ -288,6 +300,7 @@ cdef class SymbolicRing(CommutativeRing): from sage.rings.infinity import (infinity, minus_infinity, unsigned_infinity) from sage.rings.asymptotic.asymptotic_ring import AsymptoticExpansion + from sage.structure.factorization import Factorization if isinstance(x, (Integer, RealNumber, float, long, complex)): GEx_construct_pyobject(exp, x) @@ -303,6 +316,9 @@ cdef class SymbolicRing(CommutativeRing): return x.symbolic_expression() elif isinstance(x, (RingElement, Matrix)): GEx_construct_pyobject(exp, x) + elif isinstance(x, Factorization): + from sage.misc.all import prod + return prod([SR(p)**e for p,e in x], SR(x.unit())) else: raise TypeError diff --git a/src/sage/tensor/coordinate_patch.py b/src/sage/tensor/coordinate_patch.py index 88df32a42d7..908e1f9d972 100644 --- a/src/sage/tensor/coordinate_patch.py +++ b/src/sage/tensor/coordinate_patch.py @@ -81,20 +81,18 @@ def __init__(self, coordinates, metric = None): INPUT: - ``coordinates`` -- a set of symbolic variables that serve - as coordinates on this space. + as coordinates on this space. - - ``metric`` (default: None) -- a metric tensor on this - coordinate patch. Providing anything other than ``None`` - is currently not defined. + - ``metric`` (default: ``None``) -- a metric tensor on this + coordinate patch. Providing anything other than ``None`` + is currently not defined. EXAMPLES:: sage: x, y, z = var('x, y, z') sage: S = CoordinatePatch((x, y, z)); S Open subset of R^3 with coordinates x, y, z - """ - from sage.symbolic.ring import is_SymbolicVariable if not all(is_SymbolicVariable(c) for c in coordinates): @@ -107,8 +105,6 @@ def __init__(self, coordinates, metric = None): if metric is not None: raise NotImplementedError("Metric geometry not supported yet.") - - def __eq__(self, other): """ Return equality if and only if other has the same coordinates diff --git a/src/sage/tensor/differential_form_element.py b/src/sage/tensor/differential_form_element.py index e109d133ec4..87abcc47173 100644 --- a/src/sage/tensor/differential_form_element.py +++ b/src/sage/tensor/differential_form_element.py @@ -44,7 +44,7 @@ def sort_subscript(subscript): INPUT: - ``subscript`` -- a subscript, i.e. a range of not necessarily - distinct integers + distinct integers OUTPUT: @@ -396,16 +396,14 @@ def __init__(self, parent, degree, fun = None): if degree == 0 and fun is not None: self.__setitem__([], fun) - def __getitem__(self, subscript): r""" Return a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer - or a list of integers. - + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: @@ -426,7 +424,6 @@ def __getitem__(self, subscript): sage: df[2] 0 """ - if isinstance(subscript, (Integer, int)): subscript = (subscript, ) else: @@ -447,14 +444,14 @@ def __getitem__(self, subscript): else: return 0 - def __setitem__(self, subscript, fun): r""" Modify a given component of the differential form. INPUT: - - ``subscript``: subscript of the component. Must be an integer or a list of integers. + - ``subscript`` -- subscript of the component. Must be an integer + or a list of integers. EXAMPLES:: diff --git a/src/sage/tensor/differential_forms.py b/src/sage/tensor/differential_forms.py index 6f367db317b..d4d2332b19e 100644 --- a/src/sage/tensor/differential_forms.py +++ b/src/sage/tensor/differential_forms.py @@ -76,11 +76,13 @@ class DifferentialForms(Algebra): def __init__(self, coordinate_patch = None): """ Construct the algebra of differential forms on a given coordinate patch. + See ``DifferentialForms`` for details. INPUT: - ``coordinate_patch`` -- Coordinate patch where the algebra lives. + If no coordinate patch is given, a default coordinate patch with coordinates (x, y, z) is used. @@ -91,9 +93,7 @@ def __init__(self, coordinate_patch = None): Open subset of R^2 with coordinates p, q sage: F = DifferentialForms(U); F Algebra of differential forms in the variables p, q - """ - from sage.categories.graded_algebras_with_basis \ import GradedAlgebrasWithBasis from sage.structure.parent_gens import ParentWithGens diff --git a/src/sage/version.py b/src/sage/version.py index 3b8f0aa4ca7..13ac1cb1b05 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.9.rc0' -date = '2015-09-25' +version = '6.10.beta0' +date = '2015-10-15' diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index f4d8a04d823..639673bd024 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -47,7 +47,7 @@ def pari_share(): return os.path.join(SAGE_LOCAL, "share", "pari") paren_re = re.compile(r"[(](.*)[)]") -argname_re = re.compile(r"[ {]*([A-Za-z0-9_]+)") +argname_re = re.compile(r"[ {]*([A-Za-z_][A-Za-z0-9_]*)") def read_pari_desc(): """